Saltar al contenido

Inyección SQL que evita mysql_real_escape_string ()

Solución:

La respuesta corta es si, si hay una manera de moverse mysql_real_escape_string(). # ¡¡¡PARA CASOS DE BORDE MUY OBSCURO !!!

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

El ataque

Entonces, comencemos mostrando el ataque …

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

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

  1. Seleccionar un juego 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 ie 0x27 y tener algún carácter cuyo byte final sea un ASCII es decir 0x5c. Resulta que hay 5 codificaciones de este tipo admitidas en MySQL 5.6 de forma predeterminada: big5, cp932, gb2312, gbk y sjis. Seleccionaremos gbk aquí.

    Ahora bien, 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 versiones de MySQL desde 2006). Pero más sobre por qué en un minuto …

  2. La carga útil

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

    Hemos elegido esta carga útil porque, si llamamos addslashes() en él, insertaríamos un ASCII es decir 0x5c, antes de ' personaje. Así que terminaríamos con 0xbf5c27, En cual gbk es una secuencia de dos caracteres: 0xbf5c seguido por 0x27. O en otras palabras, un válido personaje seguido de un '. Pero no estamos usando addslashes(). Así que pasemos al siguiente paso …

  3. mysql_real_escape_string ()

    La llamada API de C a mysql_real_escape_string() difiere de addslashes() ya que conoce el juego 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 momento, el cliente cree que todavía estamos usando latin1 para la conexión, porque nunca 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 lo que requiere el ataque.

  4. La consulta

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

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

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

El malo

Se pone peor. PDO predeterminado a emulando 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 resultará en una inyección exitosa:

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

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

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

Esta voluntad generalmente dan como resultado una declaración preparada verdadera (es decir, los datos se envían en un paquete separado de la consulta). Sin embargo, tenga en cuenta que PDO recurrirá silenciosamente a la emulación de declaraciones que MySQL no puede preparar de forma nativa: las que sí pueden se enumeran 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') 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 multibyte no válidos, como los de nuestra carga útil, se trataban como bytes únicos para fines de escape incluso si el cliente ha sido informado correctamente de la codificación de la conexión por lo que este ataque aún tendría é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 expuso la API de C para mysql_set_charset() hasta 5.3.6, por lo que en versiones anteriores no poder ¡Evita este ataque con cada comando posible! Ahora está expuesto como un parámetro de DSN.

La gracia salvadora

Como dijimos al principio, para que este ataque funcione, la conexión a la base de datos debe estar codificada mediante un juego de caracteres vulnerable. utf8mb4 es No vulnerable y sin embargo puede apoyar cada Carácter Unicode: por lo que podría optar por utilizarlo en su lugar, pero solo ha estado disponible desde MySQL 5.5.3. Una alternativa es utf8, cual es también No vulnerable y puede admitir todo el plano multilingüe básico Unicode.

Alternativamente, puede habilitar el NO_BACKSLASH_ESCAPES Modo SQL, que (entre otras cosas) altera el funcionamiento de mysql_real_escape_string(). Con este modo habilitado, 0x27 será reemplazado con 0x2727 en vez de 0x5c27 y así el proceso de escape no poder crear caracteres válidos en cualquiera de las codificaciones vulnerables donde no existían previamente (es decir, 0xbf27 es todavía 0xbf27 etc.), por lo que el servidor seguirá rechazando la cadena como no válida. Sin embargo, vea la respuesta de @ eggyal para una vulnerabilidad diferente que puede surgir al usar este modo SQL.

Ejemplos seguros

Los siguientes ejemplos son seguros:

mysql_query('SET NAMES utf8');
$var = mysql_real_escape_string("xbfx27 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("xbfx27 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("xbfx27 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("xbfx27 OR 1=1 /*"));

Porque hemos configurado el juego de caracteres correctamente.

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

Porque MySQLi hace declaraciones preparadas verdaderas todo el tiempo.

Terminando

Si tu:

  • Utilice versiones modernas de MySQL (posteriores a 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 conexión (solo usa utf8 / latin1 / ascii / etc)

Estás 100% seguro.

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

Considere la siguiente consulta:

$iId = mysql_real_escape_string("1 OR 1=1");    
$sSql = "SELECT * FROM table WHERE id = $iId";

mysql_real_escape_string() no te protegerá contra esto.
El hecho de que utilice comillas simples (' ') alrededor de sus variables dentro de su consulta es lo que lo protege contra esto. La siguiente también es una opción:

$iId = (int)"1 OR 1=1";
$sSql = "SELECT * FROM table WHERE id = $iId";

TL; DR

mysql_real_escape_string() voluntad no brindan protección alguna (y, además, podría mutilar sus datos) si:

  • MySQL NO_BACKSLASH_ESCAPES El modo SQL está habilitado (que podría ser, a menos que tu explícitamente seleccione otro modo SQL cada vez que te conectas); y

  • sus literales de cadena SQL se citan con comillas dobles " caracteres.

Esto se archivó como error # 72458 y se ha corregido en MySQL v5.7.6 (consulte la sección titulada “La gracia salvadora“, debajo).

Este es otro, (¿quizás menos?) CASO EDGE oscuro !!!

En homenaje a la excelente respuesta de @ ircmaxell (¡en realidad, se supone que esto es un halago y no un plagio!), Adoptaré su formato:

El ataque

Comenzando con una demostración …

mysql_query('SET SQL_MODE="NO_BACKSLASH_ESCAPES"'); // could already be set
$var = mysql_real_escape_string('" OR 1=1 -- ');
mysql_query('SELECT * FROM test WHERE name = "'.$var.'" LIMIT 1');

Esto devolverá todos los registros del test mesa. Una disección:

  1. Seleccionar un modo SQL

    mysql_query('SET SQL_MODE="NO_BACKSLASH_ESCAPES"');
    

    Como se documenta en Literales de cadena:

    Hay varias formas de incluir comillas dentro de una cadena:

    • A “'“Dentro de una cadena entre comillas”'“Puede escribirse como”''”.

    • A “"“Dentro de una cadena entre comillas”"“Puede escribirse como”""”.

    • Anteponga un carácter de escape al carácter de cita (“”).

    • A “'“Dentro de una cadena entre comillas”"”No necesita un tratamiento especial y no es necesario duplicarlo o escapar. De la misma manera, “"“Dentro de una cadena entre comillas”'”No necesita un tratamiento especial.

    Si el modo SQL del servidor incluye NO_BACKSLASH_ESCAPES, luego la tercera de estas opciones, que es el enfoque habitual adoptado por mysql_real_escape_string()—No está disponible: debe utilizarse una de las dos primeras opciones. Tenga en cuenta que el efecto de la cuarta viñeta es que uno debe conocer necesariamente el carácter que se utilizará para citar el literal para evitar confundir los datos.

  2. La carga útil

    " OR 1=1 -- 
    

    La carga útil inicia esta inyección literalmente con el " personaje. Sin codificación particular. Sin caracteres especiales. Sin bytes extraños.

  3. mysql_real_escape_string ()

    $var = mysql_real_escape_string('" OR 1=1 -- ');
    

    Afortunadamente, mysql_real_escape_string() comprueba el modo SQL y ajusta su comportamiento en consecuencia. Ver libmysql.c:

    ulong STDCALL
    mysql_real_escape_string(MYSQL *mysql, char *to,const char *from,
                 ulong length)
    {
      if (mysql->server_status & SERVER_STATUS_NO_BACKSLASH_ESCAPES)
        return escape_quotes_for_mysql(mysql->charset, to, 0, from, length);
      return escape_string_for_mysql(mysql->charset, to, 0, from, length);
    }
    

    Por lo tanto, una función subyacente diferente, escape_quotes_for_mysql(), se invoca si el NO_BACKSLASH_ESCAPES El modo SQL está en uso. Como se mencionó anteriormente, dicha función necesita saber qué carácter se utilizará para citar el literal a fin de repetirlo sin que el otro carácter de cita se repita literalmente.

    Sin embargo, esta función arbitrariamente asume que la cadena se cotizará con comillas simples ' personaje. Ver charset.c:

    /*
      Escape apostrophes by doubling them up
    
    // [ deletia 839-845 ]
    
      DESCRIPTION
        This escapes the contents of a string by doubling up any apostrophes that
        it contains. This is used when the NO_BACKSLASH_ESCAPES SQL_MODE is in
        effect on the server.
    
    // [ deletia 852-858 ]
    */
    
    size_t escape_quotes_for_mysql(CHARSET_INFO *charset_info,
                                   char *to, size_t to_length,
                                   const char *from, size_t length)
    {
    // [ deletia 865-892 ]
    
        if (*from == ''')
        {
          if (to + 2 > to_end)
          {
            overflow= TRUE;
            break;
          }
          *to++= ''';
          *to++= ''';
        }
    

    Entonces, deja comillas dobles " caracteres intactos (y duplica todos los comillas simples ' caracteres) independientemente del carácter real que se utilice para citar el literal! En nuestro caso $var sigue siendo exactamente el mismo que el argumento que se proporcionó a mysql_real_escape_string()—Es como si no hubiera habido escape en absoluto.

  4. La consulta

    mysql_query('SELECT * FROM test WHERE name = "'.$var.'" LIMIT 1');
    

    Algo de una formalidad, la consulta renderizada es:

    SELECT * FROM test WHERE name = "" OR 1=1 -- " LIMIT 1
    

Como lo expresó mi erudito amigo: felicitaciones, acaba de atacar con éxito un programa usando mysql_real_escape_string()

El malo

mysql_set_charset() no puede ayudar, ya que esto no tiene nada que ver con los conjuntos de caracteres; ni puede mysqli::real_escape_string(), ya que es una envoltura diferente alrededor de esta misma función.

El problema, si no es ya obvio, es que la llamada a mysql_real_escape_string() no puedo saber con qué carácter se citará el literal, ya que se deja al desarrollador decidir en un momento posterior. Entonces, en NO_BACKSLASH_ESCAPES modo, hay literalmente de ninguna manera que esta función puede escapar de forma segura de cada entrada para su uso con comillas arbitrarias (al menos, no sin duplicar los caracteres que no requieren duplicar y, por lo tanto, manipular sus datos).

El feo

Se pone peor. NO_BACKSLASH_ESCAPES puede no ser tan poco común en la naturaleza debido a la necesidad de su uso para la compatibilidad con SQL estándar (por ejemplo, consulte la sección 5.3 de la especificación SQL-92, es decir, la <quote symbol> ::= <quote><quote> producción gramatical y falta de un significado especial dado a la barra invertida). Además, su uso se recomendó explícitamente como una solución al error (arreglado hace mucho tiempo) que la publicación de ircmaxell describe. Quién sabe, algunos DBA podrían incluso configurarlo para que esté activado de forma predeterminada como medio para desalentar el uso de métodos de escape incorrectos como addslashes().

Además, el servidor establece el modo SQL de una nueva conexión de acuerdo con su configuración (que SUPER el usuario puede cambiar en cualquier momento); por lo tanto, para estar seguro del comportamiento del servidor, debe siempre especifique explícitamente su modo deseado después de conectarse.

La gracia salvadora

Mientras tu siempre explícitamente establecer el modo SQL para no incluir NO_BACKSLASH_ESCAPES, o cite literales de cadena MySQL usando el carácter de comillas simples, este error no puede mostrar su fea cabeza: respectivamente escape_quotes_for_mysql() no se utilizará, o su suposición sobre qué caracteres de comillas requieren repetición será correcta.

Por esta razón, recomiendo que cualquiera que use NO_BACKSLASH_ESCAPES también permite ANSI_QUOTES modo, ya que forzará el uso habitual de cadenas literales entre comillas simples. Tenga en cuenta que esto no evita la inyección de SQL en el caso de que se utilicen literales entre comillas dobles; simplemente reduce la probabilidad de que eso suceda (porque las consultas normales no maliciosas fallarían).

En DOP, tanto su función equivalente PDO::quote() y su emulador de declaraciones preparado llaman a mysql_handle_quoter()—Que hace exactamente esto: asegura que el literal de escape se cite entre comillas simples, por lo que puede estar seguro de que PDO siempre es inmune a este error.

A partir de MySQL v5.7.6, este error se ha corregido. Ver registro de cambios:

Funcionalidad agregada o modificada

  • Cambio incompatible: Una nueva función de API C, mysql_real_escape_string_quote(), se ha implementado como reemplazo de mysql_real_escape_string() porque la última función puede fallar al codificar correctamente los caracteres cuando el NO_BACKSLASH_ESCAPES El modo SQL está habilitado. En este caso, mysql_real_escape_string() no puede escapar de los caracteres de las comillas excepto duplicándolos, y para hacer esto correctamente, debe conocer más información sobre el contexto de las citas de la que está disponible. mysql_real_escape_string_quote() toma un argumento adicional para especificar el contexto de las citas. Para obtener detalles de uso, consulte mysql_real_escape_string_quote ().

    Nota

    Las aplicaciones deben modificarse para usar mysql_real_escape_string_quote(), en lugar de mysql_real_escape_string(), que ahora falla y produce un CR_INSECURE_API_ERR error si NO_BACKSLASH_ESCAPES está habilitado.

    Referencias: consulte también el error n.o 19211994.

Ejemplos seguros

Tomados junto con el error explicado por ircmaxell, los siguientes ejemplos son completamente seguros (asumiendo que uno está usando MySQL posterior a 4.1.20, 5.0.22, 5.1.11; o que uno no está usando una codificación de conexión GBK / Big5) :

mysql_set_charset($charset);
mysql_query("SET SQL_MODE=''");
$var = mysql_real_escape_string('" OR 1=1 /*');
mysql_query('SELECT * FROM test WHERE name = "'.$var.'" LIMIT 1');

… porque hemos seleccionado explícitamente un modo SQL que no incluye NO_BACKSLASH_ESCAPES.

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

… porque estamos citando nuestro literal de cadena con comillas simples.

$stmt = $pdo->prepare('SELECT * FROM test WHERE name = ? LIMIT 1');
$stmt->execute(["' OR 1=1 /*"]);

… porque las declaraciones preparadas por PDO son inmunes a esta vulnerabilidad (y también a las de ircmaxell, siempre que esté usando PHP≥5.3.6 y el conjunto de caracteres se haya configurado correctamente en el DSN; o que la emulación de declaraciones preparadas se haya deshabilitado) .

$var  = $pdo->quote("' OR 1=1 /*");
$stmt = $pdo->query("SELECT * FROM test WHERE name = $var LIMIT 1");

… porque los DOP quote() La función no solo escapa del literal, sino que también lo cita (en comillas simples ' caracteres); tenga en cuenta que para evitar el error de ircmaxell en este caso, debe estar usando PHP≥5.3.6 y haya configurado correctamente el juego de caracteres en el DSN.

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

… porque las declaraciones preparadas por MySQLi son seguras.

Terminando

Por lo tanto, si usted:

  • utilizar declaraciones nativas preparadas

O

  • use MySQL v5.7.6 o posterior

O

  • en adición para emplear una de las soluciones en el resumen de ircmaxell, use al menos una de las siguientes:

    • DOP;
    • literales de cadena entre comillas simples; o
    • un modo SQL establecido explícitamente que no incluye NO_BACKSLASH_ESCAPES

…entonces tú deberían ser completamente seguro (dejando de lado las vulnerabilidades fuera del alcance del escape de cadenas).

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *