sql >> Base de Datos >  >> RDS >> Mysql

¿Cómo puedo evitar la inyección de SQL en PHP?

El correcto Una forma de evitar los ataques de inyección SQL, independientemente de la base de datos que utilice, es separar los datos de SQL , para que los datos sigan siendo datos y nunca se interpreten como comandos del analizador SQL. Es posible crear una declaración SQL con partes de datos formateadas correctamente, pero si no lo hace totalmente comprender los detalles, siempre debe usar declaraciones preparadas y consultas parametrizadas. Estas son declaraciones SQL que el servidor de la base de datos envía y analiza por separado de cualquier parámetro. De esta manera es imposible que un atacante inyecte SQL malicioso.

Básicamente tienes dos opciones para lograr esto:

  1. Usando PDO (para cualquier controlador de base de datos compatible):

     $stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name');
    
     $stmt->execute([ 'name' => $name ]);
    
     foreach ($stmt as $row) {
         // Do something with $row
     }
    
  2. Usando MySQLi (para MySQL):

     $stmt = $dbConnection->prepare('SELECT * FROM employees WHERE name = ?');
     $stmt->bind_param('s', $name); // 's' specifies the variable type => 'string'
    
     $stmt->execute();
    
     $result = $stmt->get_result();
     while ($row = $result->fetch_assoc()) {
         // Do something with $row
     }
    

Si se está conectando a una base de datos que no sea MySQL, hay una segunda opción específica del controlador que puede consultar (por ejemplo, pg_prepare() y pg_execute() para PostgreSQL). PDO es la opción universal.

Configurando correctamente la conexión

Tenga en cuenta que al usar PDO para acceder a una base de datos MySQL real las declaraciones preparadas no se utilizan de forma predeterminada . Para solucionar esto, debe deshabilitar la emulación de declaraciones preparadas. Un ejemplo de cómo crear una conexión usando PDO es:

$dbConnection = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8', 'user', 'password');

$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

En el ejemplo anterior, el modo de error no es estrictamente necesario, pero se recomienda agregarlo . De esta manera, el script no se detendrá con un Fatal Error cuando algo sale mal. Y le da al desarrollador la oportunidad de catch cualquier error(es) que sean throw n como PDOException s.

Qué es obligatorio , sin embargo, es el primer setAttribute() línea, que le dice a PDO que deshabilite las declaraciones preparadas emuladas y use real declaraciones preparadas. Esto asegura que la declaración y los valores no sean analizados por PHP antes de enviarlos al servidor MySQL (dando a un posible atacante la posibilidad de inyectar SQL malicioso).

Aunque puede establecer el charset en las opciones del constructor, es importante tener en cuenta que las versiones 'anteriores' de PHP (anteriores a la 5.3.6) ignoró silenciosamente el parámetro charset en el DSN.

Explicación

La instrucción SQL que pasa a prepare es analizado y compilado por el servidor de la base de datos. Especificando parámetros (ya sea un ? o un parámetro con nombre como :name en el ejemplo anterior) le dice al motor de la base de datos dónde desea filtrar. Luego, cuando llamas a execute , la instrucción preparada se combina con los valores de los parámetros que especifique.

Lo importante aquí es que los valores de los parámetros se combinan con la declaración compilada, no con una cadena SQL. La inyección de SQL funciona engañando al script para que incluya cadenas maliciosas cuando crea SQL para enviar a la base de datos. Entonces, al enviar el SQL real por separado de los parámetros, limita el riesgo de terminar con algo que no pretendía.

Cualquier parámetro que envíe cuando use una declaración preparada se tratará como cadenas (aunque el motor de la base de datos puede hacer alguna optimización, por lo que los parámetros también pueden terminar como números, por supuesto). En el ejemplo anterior, si $name la variable contiene 'Sarah'; DELETE FROM employees el resultado sería simplemente una búsqueda de la cadena "'Sarah'; DELETE FROM employees" , y no terminará con una mesa vacía .

Otro beneficio de usar sentencias preparadas es que si ejecuta la misma sentencia muchas veces en la misma sesión, solo se analizará y compilará una vez, lo que le permitirá ganar algo de velocidad.

Ah, y como preguntaste cómo hacerlo para una inserción, aquí hay un ejemplo (usando PDO):

$preparedStatement = $db->prepare('INSERT INTO table (column) VALUES (:column)');

$preparedStatement->execute([ 'column' => $unsafeValue ]);

¿Se pueden usar declaraciones preparadas para consultas dinámicas?

Si bien aún puede usar declaraciones preparadas para los parámetros de consulta, la estructura de la consulta dinámica en sí no se puede parametrizar y ciertas funciones de consulta no se pueden parametrizar.

Para estos escenarios específicos, lo mejor que puede hacer es usar un filtro de lista blanca que restrinja los valores posibles.

// Value whitelist
// $dir can only be 'DESC', otherwise it will be 'ASC'
if (empty($dir) || $dir !== 'DESC') {
   $dir = 'ASC';
}