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

Inyección de SQL que evita mysql_real_escape_string()

La respuesta corta es sí, sí, hay una forma de sortear mysql_real_escape_string() .#Para CASOS DE BORDE MUY OSCUROS!!!

La respuesta larga no es tan fácil. Se basa en un ataque demostrado aquí .

El ataque

Entonces, comencemos mostrando el ataque...

mysql_query('SET NAMES gbk');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");

En ciertas circunstancias, eso devolverá más de 1 fila. Analicemos lo que está pasando aquí:

  1. Selección de un conjunto de caracteres

    mysql_query('SET NAMES gbk');
    

    Para que este ataque funcione, necesitamos la codificación que el servidor espera en la conexión para codificar ' como en ASCII, es decir, 0x27 y tener algún carácter cuyo byte final sea un ASCII \ es decir, 0x5c . Resulta que MySQL 5.6 admite 5 codificaciones de este tipo de forma predeterminada:big5 , cp932 , gb2312 , gbk y sjis . Seleccionaremos gbk aquí.

    Ahora, es muy importante tener en cuenta el uso de SET NAMES aquí. Esto establece el juego de caracteres EN EL SERVIDOR . Si usamos la llamada a la función API de C mysql_set_charset() , estaríamos bien (en las versiones de MySQL desde 2006). Pero más sobre por qué en un minuto...

  2. La carga útil

    La carga útil que vamos a usar para esta inyección comienza con la secuencia de bytes 0xbf27 . En gbk , ese es un carácter multibyte no válido; en latin1 , es la cadena ¿' . Tenga en cuenta que en latin1 y gbk , 0x27 por sí solo es un ' literal personaje.

    Hemos elegido este payload porque, si llamamos a addslashes() en él, insertaríamos un ASCII \ es decir, 0x5c , antes del ' personaje. Entonces terminaríamos con 0xbf5c27 , que en gbk es una secuencia de dos caracteres:0xbf5c seguido de 0x27 . O en otras palabras, un válido carácter seguido de un ' sin escape . Pero no estamos usando addslashes() . Así que al siguiente paso...

  3. mysql_real_escape_string()

    La llamada de la API de C a mysql_real_escape_string() difiere de addslashes() en que conoce el conjunto de caracteres de conexión. Por lo tanto, puede realizar el escape correctamente para el conjunto de caracteres que espera el servidor. Sin embargo, hasta este punto, el cliente cree que todavía estamos usando latin1 por la conexión, porque nunca le dijimos lo contrario. Le dijimos al servidor estamos usando gbk , pero el cliente todavía piensa que es latin1 .

    Por lo tanto, la llamada a mysql_real_escape_string() inserta la barra invertida, y tenemos un ' colgante libre personaje en nuestro contenido "escapado"! De hecho, si tuviéramos que mirar $var en el gbk conjunto de caracteres, veríamos:

    縗' OR 1=1 /*

    Que es exactamente qué requiere el ataque.

  4. La consulta

    Esta parte es solo una formalidad, pero aquí está la consulta procesada:

    SELECT * FROM test WHERE name = '縗' OR 1=1 /*' LIMIT 1
    

Felicitaciones, acaba de atacar con éxito un programa usando mysql_real_escape_string() ...

Lo malo

Se pone peor. PDO el valor predeterminado es emular declaraciones preparadas con MySQL. Eso significa que en el lado del cliente, básicamente hace un sprintf a través de mysql_real_escape_string() (en la biblioteca C), lo que significa que lo siguiente dará como resultado una inyección exitosa:

$pdo->query('SET NAMES gbk');
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));

Ahora, vale la pena señalar que puede evitar esto al deshabilitar las declaraciones preparadas emuladas:

$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

Esto normalmente dar como resultado una declaración preparada verdadera (es decir, los datos que se envían en un paquete separado de la consulta). Sin embargo, tenga en cuenta que PDO silenciosamente fallback para emular declaraciones que MySQL no puede preparar de forma nativa:las que sí puede son listadas en el manual, pero tenga cuidado de seleccionar la versión de servidor adecuada).

El feo

Dije desde el principio que podríamos haber evitado todo esto si hubiéramos usado mysql_set_charset('gbk') en lugar de SET NAMES gbk . Y eso es cierto siempre que esté utilizando una versión de MySQL desde 2006.

Si está utilizando una versión anterior de MySQL, entonces un error en mysql_real_escape_string() significaba que los caracteres de varios bytes no válidos, como los de nuestra carga útil, se trataban como bytes individuales con fines de escape incluso si el cliente había sido informado correctamente de la codificación de la conexión y así este ataque todavía tendría éxito. El error se solucionó en MySQL 4.1.20 , 5.0.22 y 5.1.11 .

Pero lo peor es que PDO no expuso la API de C para mysql_set_charset() hasta 5.3.6, por lo que en versiones anteriores no ¡evite este ataque para cada comando posible! Ahora está expuesto como un Parámetro DSN .

La gracia salvadora

Como dijimos al principio, para que este ataque funcione, la conexión a la base de datos debe codificarse utilizando un juego de caracteres vulnerable. utf8mb4 es no vulnerable y, sin embargo, puede admitir todos Carácter Unicode:por lo que podría elegir usarlo en su lugar, pero solo ha estado disponible desde MySQL 5.5.3. Una alternativa es utf8 , que tampoco es no vulnerable y puede admitir todo el Unicode Plano multilingüe básico .

Alternativamente, puede habilitar NO_BACKSLASH_ESCAPES Modo SQL, que (entre otras cosas) altera el funcionamiento de mysql_real_escape_string() . Con este modo habilitado, 0x27 será reemplazado por 0x2727 en lugar de 0x5c27 y por lo tanto el proceso de escape no puede crear caracteres válidos en cualquiera de las codificaciones vulnerables donde no existían previamente (es decir, 0xbf27 sigue siendo 0xbf27 etc.), por lo que el servidor aún rechazará la cadena como no válida. Sin embargo, consulte la respuesta de @eggyal para una vulnerabilidad diferente que puede surgir del uso de este modo SQL.

Ejemplos seguros

Los siguientes ejemplos son seguros:

mysql_query('SET NAMES utf8');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");

Porque el servidor espera utf8 ...

mysql_set_charset('gbk');
$var = mysql_real_escape_string("\xbf\x27 OR 1=1 /*");
mysql_query("SELECT * FROM test WHERE name = '$var' LIMIT 1");

Porque hemos configurado correctamente el conjunto de caracteres para que el cliente y el servidor coincidan.

$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$pdo->query('SET NAMES gbk');
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));

Porque hemos desactivado las sentencias preparadas emuladas.

$pdo = new PDO('mysql:host=localhost;dbname=testdb;charset=gbk', $user, $password);
$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(array("\xbf\x27 OR 1=1 /*"));

Porque hemos configurado el conjunto de caracteres correctamente.

$mysqli->query('SET NAMES gbk');
$stmt = $mysqli->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$param = "\xbf\x27 OR 1=1 /*";
$stmt->bind_param('s', $param);
$stmt->execute();

Porque MySQLi hace verdaderas declaraciones preparadas todo el tiempo.

Conclusión

Si tu:

  • Usar versiones modernas de MySQL (últimas 5.1, todas las 5.5, 5.6, etc.) Y mysql_set_charset() / $mysqli->set_charset() / Parámetro de juego de caracteres DSN de PDO (en PHP ≥ 5.3.6)

O

  • No use un juego de caracteres vulnerable para la codificación de la conexión (solo use utf8 / latin1 / ascii / etc.)

Estás 100 % seguro.

De lo contrario, eres vulnerable aunque estés usando mysql_real_escape_string() ...