[Php] Inyección SQL que se mueve por mysql_real_escape_string ()



Answers

La respuesta corta es sí, sí, hay una forma de evitar mysql_real_escape_string() .

¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡¡CUADROS MUY OLIVOS !!!

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. Seleccionar un conjunto de caracteres

    mysql_query('SET NAMES gbk');
    

    Para que funcione este ataque, necesitamos la codificación que el servidor espera en la conexión para codificar ' como en ASCII, es decir, 0x27 y para tener algún carácter cuyo byte final sea ASCII \ ie 0x5c . Como resultado, hay 5 de tales codificaciones soportadas en MySQL 5.6 por defecto: 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 conjunto de caracteres EN EL SERVIDOR . Si mysql_set_charset() la llamada a la función C API mysql_set_charset() , estaríamos bien (en los lanzamientos 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 inválido; en latin1 , es la cadena ¿' . Tenga en cuenta que en gbk y gbk , 0x27 en sí mismo es un carácter literal.

    Hemos elegido esta carga útil porque, si llamamos a addslashes() en ella, insertaríamos un ASCII \ ie 0x5c , antes del carácter ' . Entonces terminamos con 0xbf5c27 , que en gbk es una secuencia de dos caracteres: 0xbf5c seguido de 0x27 . O en otras palabras, un personaje válido seguido de un ' guardado ' . Pero no estamos usando addslashes() . Continúa con el siguiente paso ...

  3. mysql_real_escape_string ()

    La llamada de API 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 juego de caracteres que espera el servidor. Sin embargo, hasta este punto, el cliente piensa que todavía estamos usando latin1 para la conexión, porque nunca le dijimos lo contrario. Le dijimos al servidor que 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 diagonal inversa, ¡y tenemos un carácter ' colgado ' en nuestro contenido "escapado"! De hecho, si tuviéramos que ver $var en el gbk caracteres gbk , veríamos:

    縗' OR 1=1 /*

    Que es exactamente lo que 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
    

Felicidades, acabas de atacar con éxito un programa usando mysql_real_escape_string() ...

El malo

Se pone peor. PDO adopta por defecto la emulación de 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 emuladas preparadas:

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

Esto generalmente dará como resultado una declaración verdaderamente preparada (es decir, los datos enviados en un paquete separado de la consulta). Sin embargo, tenga en cuenta que PDO recurrirá silenciosamente a las declaraciones de emulación que MySQL no puede preparar de forma nativa: las que sí pueden listed en el manual, pero tenga cuidado de seleccionar la versión de servidor adecuada).

El feo

Dije al principio que podríamos haber evitado todo esto si hubiéramos usado mysql_set_charset('gbk') lugar de SET NAMES gbk . Y eso es cierto siempre que esté utilizando un lanzamiento de MySQL desde 2006.

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

Pero la peor parte es que PDO no expone la API de C para mysql_set_charset() hasta 5.3.6, por lo que en versiones anteriores no puede evitar este ataque para todos los comandos posibles. Ahora está expuesto como un parámetro DSN .

La gracia salvadora

Como dijimos al principio, para que este ataque funcione, la conexión de la base de datos debe codificarse utilizando un conjunto de caracteres vulnerable. utf8mb4 no es vulnerable y, sin embargo, puede admitir todos los caracteres Unicode: por lo tanto, puede optar por usarlo, pero solo ha estado disponible desde MySQL 5.5.3. Una alternativa es utf8 , que tampoco es vulnerable y puede soportar todo el plano multilingüe básico de Unicode.

Alternativamente, puede habilitar el modo SQL NO_BACKSLASH_ESCAPES , que (entre otras cosas) altera el funcionamiento de mysql_real_escape_string() . Con este modo habilitado, 0x27 se reemplazará con 0x2727 lugar de 0x5c27 y, por lo tanto, el proceso de escape no puede crear caracteres válidos en ninguna de las codificaciones vulnerables donde no existían anteriormente (es decir, 0xbf27 sigue siendo 0xbf27 etc.), por lo que el servidor rechazar la cadena como inválida Sin embargo, vea 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 juego 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 declaraciones 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 establecido el juego 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.

Terminando

Si tu:

  • Utilice las versiones modernas de MySQL (último 5.1, todos los 5.5, 5.6, etc.) AND mysql_set_charset() / $mysqli->set_charset() / PDO's DSN charset parameter (en PHP ≥ 5.3.6)

O

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

Estás 100% seguro

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

Question

¿Existe una posibilidad de inyección SQL incluso cuando se usa la función mysql_real_escape_string() ?

Considere esta situación de muestra. SQL está construido en PHP de esta manera:

$login = mysql_real_escape_string(GetFromPost('login'));
$password = mysql_real_escape_string(GetFromPost('password'));

$sql = "SELECT * FROM table WHERE login='$login' AND password='$password'";

He escuchado a muchas personas decirme que un código como ese sigue siendo peligroso y posible piratear incluso con la función mysql_real_escape_string() utilizada. Pero no puedo pensar en ningún posible exploit?

Inyecciones clásicas como esta:

aaa' OR 1=1 --

No funcionan.

¿Conoces alguna inyección posible que pueda obtener a través del código PHP anterior?




Bueno, no hay nada realmente que pueda atravesar eso, que no sea % comodín. Podría ser peligroso si estuviera usando una declaración LIKE , ya que el atacante podría poner solo % como inicio de sesión si no filtra eso, y tendría que aplicar una contraseña a cualquiera de sus usuarios. La gente a menudo sugiere usar declaraciones preparadas para hacerlo 100% seguro, ya que los datos no pueden interferir con la consulta de esa manera. Pero para consultas tan simples, probablemente sería más eficiente hacer algo como $login = preg_replace('/[^a-zA-Z0-9_]/', '', $login);




Links