Situación actual: PHP2Shell. Dos vulnerabilidades críticas de PHP (CVSS 9.2) permiten ejecutar código malicioso sin autenticación. Parches desde el 21/10/2026.Qué hacer ahora →

Evitar la inyección SQL en PHP

Cómo excluyen de forma fiable los desarrolladores la inyección SQL en aplicaciones PHP con consultas preparadas, listas blancas y permisos mínimos en la base de datos.

Actualizado 6 min de lecturaGuías

En resumen: la inyección SQL en PHP se evita de forma fiable no incorporando nunca valores en los textos SQL, sino pasándolos siempre mediante consultas preparadas con marcadores de posición. Para los nombres de columnas y tablas, que no pueden pasarse como marcadores, rige una lista blanca fija. Además, un usuario de base de datos con permisos mínimos limita el daño si aun así queda alguna vulnerabilidad.

Por qué la inyección SQL no debe perderse de vista tras PHP2Shell

En octubre de 2026 gran parte de la atención gira en torno a PHP2Shell, dos vulnerabilidades críticas en el intérprete de PHP, corregidas desde el 21/10/2026 en PHP 8.5.12, 8.4.27, 8.3.36 y 8.2.35. Muchos equipos han actualizado servidores y reiniciado procesos esos días. Es lo correcto y lo urgente.

Pero una versión actual de PHP no dice nada sobre el código propio. La inyección SQL es desde hace años una de las debilidades más frecuentes en aplicaciones web, y el OWASP Top 10 la recoge en la categoría de inyección. Quien ya esté planificando revisiones de seguridad debería incluir de paso los accesos a la base de datos de su propia aplicación.

La buena noticia: la inyección SQL es una de las pocas debilidades que se pueden excluir casi por completo con una regla sencilla aplicada de forma consecuente.

¿Cómo surge la inyección SQL en PHP?

La inyección SQL surge cuando una aplicación trata las entradas del usuario como parte de un comando SQL en lugar de como datos puros. Lo típico es componer una consulta con fragmentos de texto y variables. Si un visitante puede determinar el contenido de esa variable, en ciertas circunstancias puede alterar el significado del comando.

Las consecuencias van desde leer datos de clientes ajenos hasta modificar precios o permisos o borrar tablas enteras. Según la base de datos y los permisos, una vulnerabilidad puede incluso permitir accesos más allá de la base de datos. Si están afectados datos personales, puede surgir una obligación de notificación según el RGPD.

Las entradas no son solo los campos de formulario. También los parámetros de URL, las cookies, las cabeceras HTTP, los archivos subidos, las respuestas de interfaces ajenas y los datos ya almacenados en la propia base de datos se consideran no fiables.

Consultas preparadas con PDO y mysqli

Las consultas preparadas envían a la base de datos el comando SQL y los valores por separado. Los valores nunca se interpretan como parte del comando. Con PDO, una consulta segura se ve así:

<?php
$pdo = new PDO($dsn, $benutzer, $passwort, [
    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_EMULATE_PREPARES   => false,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);

$stmt = $pdo->prepare('SELECT id, name, email FROM kunden WHERE id = :id AND status = :status');
$stmt->execute([
    'id'     => (int) $_GET['id'],
    'status' => 'aktiv',
]);
$kunde = $stmt->fetch();

La opción PDO::ATTR_EMULATE_PREPARES => false hace que la base de datos use consultas preparadas reales, siempre que el controlador lo admita. Con mysqli rige el mismo principio:

<?php
$stmt = $mysqli->prepare('SELECT id, name FROM produkte WHERE kategorie = ? AND preis <= ?');
$stmt->bind_param('sd', $kategorie, $maxPreis);
$stmt->execute();
$ergebnis = $stmt->get_result();

También las listas para IN (...) se pueden representar de forma segura creando un marcador propio para cada valor y pasando los valores por separado.

<?php
$ids = array_map('intval', $ausgewaehlteIds);
$platzhalter = implode(',', array_fill(0, count($ids), '?'));
$stmt = $pdo->prepare("SELECT id, name FROM produkte WHERE id IN ($platzhalter)");
$stmt->execute($ids);

Casos especiales: nombres de columnas, ordenación y SQL en bruto

Los marcadores de posición solo funcionan para valores, no para nombres de tablas, nombres de columnas ni sentidos de ordenación. Justo aquí surgen en la práctica muchas vulnerabilidades, por ejemplo en tablas ordenables o en filtros. La solución es una lista fija de valores permitidos:

<?php
$erlaubteSpalten = ['name' => 'name', 'preis' => 'preis', 'datum' => 'erstellt_am'];
$spalte   = $erlaubteSpalten[$_GET['sort'] ?? ''] ?? 'name';
$richtung = (($_GET['dir'] ?? '') === 'desc') ? 'DESC' : 'ASC';

$stmt = $pdo->query("SELECT id, name, preis FROM produkte ORDER BY $spalte $richtung");

Los frameworks y los CMS incorporan sus propias herramientas. En WordPress, $wpdb->prepare() asume esa tarea; en Laravel y Symfony, los constructores de consultas y los ORM. En cuanto utilice allí SQL en bruto, por ejemplo mediante funciones con «raw» en el nombre, vuelven a regir las reglas de este apartado. Encontrará indicaciones sobre Laravel en Proteger aplicaciones Laravel.

Consejo

Busque en el código, de forma específica, los textos SQL en los que se insertan variables. Un comienzo sencillo es buscar query( y ->raw en combinación con $_GET, $_POST o $_REQUEST.

Localizar la inyección SQL en el código existente

En software nuevo, la regla se puede mantener desde el principio. Es más difícil en aplicaciones que han crecido a lo largo de años. Allí merece la pena una búsqueda sistemática de los puntos donde se componen textos SQL con variables:

# Comandos SQL en los que se insertan variables
grep -rnE "(SELECT|INSERT|UPDATE|DELETE)[^;]*\\\$[a-zA-Z_]" src/ --include="*.php" | head -50
# Uso directo de datos de la petición en funciones de consulta
grep -rnE "(query|exec|raw)\s*\(.*\\\$_(GET|POST|REQUEST|COOKIE)" src/ --include="*.php"

No toda coincidencia es una vulnerabilidad, por ejemplo cuando la variable solo contiene un nombre de tabla fijo. Pero cada hallazgo debería revisarse una vez de forma consciente y, si hace falta, pasarse a consultas preparadas. Las herramientas de análisis estático de código ayudan a detectar estos patrones de forma permanente en el pipeline de compilación, de modo que los nuevos errores ni siquiera lleguen a producción.

Limitar el daño: permisos, mensajes de error, revisión

El código seguro es la primera línea de defensa. Otros niveles limitan el daño si se pasa por alto alguna vulnerabilidad:

  • Permisos mínimos: el usuario de base de datos del sitio web suele necesitar solo SELECT, INSERT, UPDATE y DELETE sobre su propia base de datos, sin permisos para crear usuarios ni para acceder a otras bases de datos.
  • Sin mensajes de error hacia fuera: display_errors = Off en php.ini. Los errores de la base de datos pertenecen al registro, no al navegador.
  • Separación por aplicación: cada sitio web recibe su propio usuario de base de datos.
  • Mantener actualizadas las dependencias: también hay vulnerabilidades en las bibliotecas, como muestra la guía composer audit.
  • Hacer revisar: la revisión de código y el análisis automatizado encuentran muchos puntos; una prueba de penetración muestra qué es realmente accesible desde fuera.
-- Ejemplo: usuario de base de datos solo con los permisos necesarios
CREATE USER 'shop_app'@'localhost' IDENTIFIED BY '<starkes Passwort>';
GRANT SELECT, INSERT, UPDATE, DELETE ON shop.* TO 'shop_app'@'localhost';

Una debilidad relacionada afecta a la salida de datos en el navegador. Cómo protegerla lo explica la guía Evitar el cross-site scripting en PHP.

¿Cómo ayuda PHP SOS en concreto?

  • Revisamos de forma sistemática los accesos a la base de datos de su aplicación en busca de consultas inseguras.
  • Priorizamos los hallazgos según su accesibilidad y los datos afectados y mostramos correcciones concretas.
  • Llevamos los permisos de la base de datos y la gestión de errores a un nivel seguro.
  • Volvemos a probar tras la corrección y documentamos el resultado.

Una revisión independiente la describe el servicio Auditoría de seguridad y prueba de penetración.

Preguntas frecuentes

¿Basta con tratar las entradas con addslashes o real_escape_string?

No, es propenso a errores. El escapado depende del juego de caracteres y del contexto, y es fácil olvidarlo en algún punto. Las consultas preparadas separan por principio el comando y los datos, y por eso son el camino recomendado.

¿Protege un ORM automáticamente frente a la inyección SQL?

En gran medida, mientras utilice las funciones de consulta del ORM. En cuanto escriba SQL en bruto o inserte valores en ordenaciones y nombres de columnas, rigen las mismas reglas que sin ORM.

¿Ayuda un firewall de aplicaciones web contra la inyección SQL?

Un WAF puede interceptar patrones conocidos y ganar tiempo. Pero no sustituye a un código seguro, porque las reglas basadas en los datos de la petición se pueden eludir y a veces afectan a entradas legítimas.

¿Cierra la actualización de PHP contra PHP2Shell también las vulnerabilidades de inyección SQL?

No. PHP2Shell afecta al intérprete de PHP. La inyección SQL es un fallo del código de la aplicación y persiste con independencia de la versión de PHP que se ejecute.

Fuentes

Contacto inmediato

Cada hora cuenta. Escríbanos ahora.

Describa brevemente el sistema, la versión de PHP y lo que ha observado. Nos pondremos en contacto, aclararemos la situación y trabajaremos en remoto hasta que el incidente quede cerrado y documentado.

Ayuda inmediata