Inyección SQL que evita mysql_real_escape_string ()

644

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

Considere esta situación de muestra. SQL se construye en PHP así:

$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 mysql_real_escape_string()función utilizada. ¿Pero no puedo pensar en ninguna posible hazaña?

Inyecciones clásicas como esta:

aaa' OR 1=1 --

No funcionan.

¿Conoces alguna posible inyección que atraviese el código PHP anterior?

Richard Knop
fuente
34
@ThiefMaster: prefiero no dar errores detallados como usuario / contraseña no válidos ... le dice a los comerciantes de fuerza bruta que tienen una identificación de usuario válida, y es solo la contraseña que deben adivinar
Mark Baker,
18
Sin embargo, es horrible desde el punto de vista de la usabilidad. A veces no puede usar su apodo / nombre de usuario / dirección de correo electrónico principal y olvidarse de esto después de un tiempo o el sitio eliminó su cuenta por inactividad. Entonces es extremadamente molesto si continúas probando contraseñas y tal vez incluso bloquees tu IP aunque solo sea tu nombre de usuario el que no sea válido.
ThiefMaster
50
Por favor, no use mysql_*funciones en el nuevo código . Ya no se mantienen y el proceso de desaprobación ha comenzado en él. ¿Ves el cuadro rojo ? Aprenda sobre las declaraciones preparadas y use PDO o MySQLi ; este artículo lo ayudará a decidir cuál. Si elige PDO, aquí hay un buen tutorial .
tereško
13
@machineaddict, desde 5.5 (que se lanzó recientemente) las mysql_*funciones ya producen E_DEPRECATEDadvertencia. La ext/mysqlextensión no se ha mantenido por más de 10 años. ¿Eres realmente tan delirante?
tereško
13
@machineaddict Acaban de eliminar esa extensión en PHP 7.0 y aún no es 2050.
GGG

Respuestas:

379

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 use comillas simples ( ' ') alrededor de sus variables dentro de su consulta es lo que lo protege contra esto. Lo siguiente también es una opción:

$iId = (int)"1 OR 1=1";
$sSql = "SELECT * FROM table WHERE id = $iId";
Wesley van Opdorp
fuente
99
Pero esto no sería un problema real, porque mysql_query()no ejecuta múltiples declaraciones, ¿no?
Pekka
11
@Pekka, aunque el ejemplo habitual es DROP TABLE, en la práctica es más probable que el atacante lo haga SELECT passwd FROM users. En el último caso, la segunda consulta generalmente se ejecuta mediante el uso de una UNIONcláusula.
Jacco
58
(int)mysql_real_escape_string- esto no tiene sentido. No difiere (int)en absoluto. Y producirán el mismo resultado para cada entrada
zerkms
28
Esto es más un mal uso de la función que cualquier otra cosa. Después de todo, se llama mysql_real_escape_string, no mysql_real_escape_integer. No es para ser utilizado con campos enteros.
NullUserException
11
@ircmaxell, Sin embargo, la respuesta es totalmente engañosa. Obviamente, la pregunta es preguntar sobre los contenidos dentro de las comillas. "Las citas no están ahí" no es la respuesta a esta pregunta.
Pacerier
629

La respuesta corta es sí, sí, hay una manera de moversemysql_real_escape_string() .

¡¡¡PARA CASOS DE BORDES MUY OCULARES

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á sucediendo aquí:

  1. Seleccionar 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 tanto para codificar 'como en ASCII, es decir, 0x27 como para tener algún carácter cuyo byte final sea un ASCII, \es decir 0x5c. Como resultado, hay 5 dichas codificaciones soportadas en MySQL 5.6 por defecto: big5, cp932, gb2312, gbky sjis. Seleccionaremos gbkaquí.

    Ahora, es muy importante tener en cuenta el uso de SET NAMESaquí. Esto establece el conjunto de caracteres EN EL SERVIDOR . Si usáramos la llamada a la función C API 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 vamos a utilizar para esta inyección comienza con la secuencia de bytes 0xbf27. En gbk, ese es un carácter multibyte no válido; adentro latin1, es la cuerda ¿'. Tenga en cuenta que en latin1 y gbk , 0x27por sí solo es un 'carácter literal .

    Hemos elegido esta carga útil porque, si la solicitamos addslashes(), insertaríamos un ASCII , \es decir 0x5c, antes del 'carácter. Así que terminaríamos con 0xbf5c27, que gbkes una secuencia de dos caracteres: 0xbf5cseguida de 0x27. O, en otras palabras, un carácter válido seguido de un no escapado '. Pero no estamos usando addslashes(). Entonces, al siguiente paso ...

  3. mysql_real_escape_string ()

    La llamada a la API C mysql_real_escape_string()difiere 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 el servidor espera. Sin embargo, hasta este punto, el cliente piensa que todavía estamos usando latin1la conexión, porque nunca le dijimos lo contrario. Le dijimos al servidor que estamos usando gbk, pero el cliente todavía piensa que es así latin1.

    Por lo tanto, la llamada a mysql_real_escape_string()insertar la barra diagonal inversa, ¡y tenemos un 'personaje que cuelga libremente en nuestro contenido "escapado"! De hecho, si tuviéramos que mirar $varen el gbkconjunto de caracteres, veríamos:

    OR '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 representada:

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

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

El malo

Se pone peor. PDOel valor predeterminado es emular declaraciones preparadas con MySQL. Eso significa que en el lado del cliente, básicamente realiza un sprintf 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 deshabilitando las declaraciones preparadas emuladas:

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

Esto generalmente 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 DOP silenciosamente repliegue a emular las declaraciones que MySQL no puede preparar de forma nativa: los que puede se enumeran en el manual, pero cuidado para seleccionar la versión del servidor apropiado).

El feo

Al principio dije que podríamos haber evitado todo esto si hubiéramos usado en mysql_set_charset('gbk')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 de MySQL anterior, a continuación, un fallo en mysql_real_escape_string()significaban que los caracteres de varios bytes no válidos como los de nuestra carga útil fueron tratados como bytes individuales para escapar de los propósitos , incluso si el cliente había sido informado correctamente de la codificación de la conexión y por lo que este ataque sería Todavía tener éxito. El error se corrigió en MySQL 4.1.20 , 5.0.22 y 5.1.11 .

Pero la peor parte es que PDOno expuso la API de C mysql_set_charset()hasta 5.3.6, por lo que en versiones anteriores no puede evitar 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 de la base de datos debe codificarse utilizando un conjunto de caracteres vulnerable. noutf8mb4 es vulnerable y, sin embargo, puede admitir todos los caracteres Unicode: por lo que puede optar por usarlo en su lugar, 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 NO_BACKSLASH_ESCAPESmodo SQL, que (entre otras cosas) altera el funcionamiento de mysql_real_escape_string(). Con este modo habilitado, 0x27será reemplazado por 0x2727algo en lugar de 0x5c27y, por lo tanto, el proceso de escape no puede crear caracteres válidos en ninguna de las codificaciones vulnerables donde no existían anteriormente ( 0xbf27es decir, todavía es 0xbf27etc.), 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 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 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.

Terminando

Si tu:

  • Utilice versiones modernas de MySQL (finales de 5.1, todas 5.5, 5.6, etc.) Y mysql_set_charset() / $mysqli->set_charset()/ Parámetro de conjunto 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 usandomysql_real_escape_string() ...

ircmaxell
fuente
3
PDO emulando declaraciones de preparación para MySQL, ¿en serio? No veo ninguna razón por la que lo haría, ya que el controlador lo admite de forma nativa. ¿No?
netcoder
16
Lo hace. Dicen en la documentación que no. Pero en el código fuente, es claramente visible y fácil de arreglar. Lo atribuyo a la incompetencia de los desarrolladores.
Theodore R. Smith
55
@ TheodoreR.Smith: No es tan fácil de arreglar. He estado trabajando para cambiar el valor predeterminado, pero falla una carga de pruebas cuando se cambia. Entonces es un cambio más grande de lo que parece. Todavía espero terminarlo a las 5,5 ...
ircmaxell
14
@shadyyx: No, la vulnerabilidad del artículo descrito era sobre addslashes. Me basé esta vulnerabilidad en que uno. Inténtalo tú mismo. Ve a buscar MySQL 5.0, ejecuta este exploit y compruébalo por ti mismo. En cuanto a cómo poner eso en PUT / GET / POST, es TRIVIAL. Los datos de entrada son solo secuencias de bytes. char(0xBF)es solo una forma legible de generar un byte. He demostrado esta vulnerabilidad en vivo frente a múltiples conferencias. Confía en mí en esto ... Pero si no lo haces, pruébalo tú mismo. Funciona ...
ircmaxell
55
@shadyyx: En cuanto a pasar tal funkiness en $ _GET ... ?var=%BF%27+OR+1=1+%2F%2Aen la URL, $var = $_GET['var'];en el código, y Bob es tu tío.
cHao
183

TL; DR

mysql_real_escape_string()no proporcionará protección de ningún tipo (y además podría bloquear sus datos) si:

  • El NO_BACKSLASH_ESCAPESmodo SQL de MySQL está habilitado (que podría estarlo, a menos que seleccione explícitamente otro modo SQL cada vez que se conecte ); y

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

Esto se archivó como error n. ° 72458 y se ha corregido en MySQL v5.7.6 (consulte la sección titulada " The Saving Grace ", a continuación).

Este es otro, ¿quizás menos?

En homenaje a la excelente respuesta de @ircmaxell (en realidad, ¡se supone que esto es adulación y no 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 de la testtabla. Una disección:

  1. Seleccionar un modo SQL

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

    Como se documenta en String Literals :

    Hay varias formas de incluir comillas dentro de una cadena:

    • Un " '" dentro de una cadena entre comillas con " '" puede escribirse como " ''".

    • Un " "" dentro de una cadena entre comillas con " "" puede escribirse como " """.

    • Preceda el carácter de comillas por un carácter de escape (" \").

    • Un " '" dentro de una cadena entre comillas con " "" no necesita ningún tratamiento especial y no necesita duplicarse o escapar. Del mismo modo, " "" dentro de una cadena entre comillas con " '" no necesita ningún tratamiento especial.

    Si el modo SQL del servidor incluye NO_BACKSLASH_ESCAPES, entonces la tercera de estas opciones, que es el enfoque habitual adoptado por, mysql_real_escape_string()no está disponible: una de las dos primeras opciones debe usarse en su lugar. 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 mezclar los datos.

  2. La carga útil

    " OR 1=1 -- 

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

  3. mysql_real_escape_string ()

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

    Afortunadamente, mysql_real_escape_string()verifica 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 escape_quotes_for_mysql(), se invoca una función subyacente diferente, si el NO_BACKSLASH_ESCAPESmodo SQL está en uso. Como se mencionó anteriormente, dicha función necesita saber qué carácter se usará para citar el literal para repetirlo sin hacer que el otro carácter de cita se repita literalmente.

    Sin embargo, esta función supone arbitrariamente que la cadena se citará utilizando el 'carácter de comillas simples . 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++= '\'';
        }

    Por lo tanto, deja "intactos los caracteres de comillas dobles (y dobla todos los 'caracteres de comillas simples ) independientemente del carácter real que se utilice para citar el literal . En nuestro caso $varpermanece exactamente igual como el argumento de que se proporcionó a mysql_real_escape_string()-Es como si hay escape ha tenido lugar en absoluto .

  4. La consulta

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

    Algo formal, la consulta representada es:

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

Como dijo mi amigo erudito: felicidades, usted atacó 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 tampoco mysqli::real_escape_string(), ya que es solo un contenedor diferente alrededor de esta misma función.

El problema, si aún no es obvio, es que la llamada a mysql_real_escape_string() no puede saber con qué carácter se citará el literal, ya que eso le corresponde al desarrollador decidir más adelante. Entonces, en el NO_BACKSLASH_ESCAPESmodo, literalmente no hay forma de que esta función pueda escapar de forma segura de cada entrada para su uso con citas arbitrarias (al menos, no sin duplicar caracteres que no requieren duplicación y, por lo tanto, mezclar sus datos).

El feo

Se pone peor. NO_BACKSLASH_ESCAPESpuede 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 de gramática y la falta de un significado especial dado a la barra invertida). Además, su uso se recomendó explícitamente como una solución al error ( solucionado hace mucho tiempo) que describe la publicación de ircmaxell. Quién sabe, algunos DBA incluso podrían configurarlo para que esté activado de forma predeterminada como un 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 un SUPERusuario puede cambiar en cualquier momento); por lo tanto, para estar seguro del comportamiento del servidor, siempre debe especificar explícitamente el modo deseado después de conectarse.

La gracia salvadora

Siempre que establezca explícitamente el modo SQL para que no incluya NO_BACKSLASH_ESCAPESo cite literales de cadena MySQL usando el carácter de comillas simples, este error no puede criar su cabeza fea: respectivamente escape_quotes_for_mysql(), no se usará, o su suposición sobre qué caracteres de comillas requieren repetición será ser correcto

Por esta razón, recomiendo que cualquiera que use NO_BACKSLASH_ESCAPEStambién habilite el ANSI_QUOTESmodo, ya que forzará el uso habitual de literales de cadena entre comillas simples. Tenga en cuenta que esto no impide 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 PDO, tanto su función equivalente PDO::quote()como su emulador de declaración preparado invocan, lo mysql_handle_quoter()que hace exactamente esto: asegura que el literal escapado se cita 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 solucionado. Ver registro de cambios :

Funcionalidad agregada o modificada

Ejemplos seguros

Tomados junto con el error explicado por ircmaxell, los siguientes ejemplos son completamente seguros (asumiendo que uno está usando MySQL más tarde que 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 nuestra cadena literal con comillas simples.

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

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

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

... porque la quote()función de PDO no solo escapa al literal, sino que también lo cita (en 'caracteres de comillas simples ); tenga en cuenta que para evitar el error de ircmaxell en este caso, debe estar usando PHP≥5.3.6 y haber configurado correctamente el conjunto 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 de MySQLi son seguras.

Terminando

Por lo tanto, si usted:

  • usar declaraciones preparadas nativas

O

  • usar MySQL v5.7.6 o posterior

O

  • en adición al empleo de una de las soluciones en resumen ircmaxell de, el uso de al menos uno de:

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

... entonces deberías estar completamente seguro (vulnerabilidades fuera del alcance de la cadena escapando a un lado).

eggyal
fuente
10
Entonces, TL; DR sería como "hay un modo de servidor mysql NO_BACKSLASH_ESCAPES que puede causar una inyección si no está usando comillas simples.
Su sentido común el
No puedo acceder a bugs.mysql.com/bug.php?id=72458 ; Acabo de obtener una página de acceso denegado. ¿Se está ocultando al público por ser un problema de seguridad? Además, ¿entiendo correctamente de esta respuesta que usted es el descubridor de la vulnerabilidad? Si es así, felicidades.
Mark Amery
1
La gente no debería usar "cuerdas en primer lugar. SQL dice que es para identificadores. Pero eh ... solo otro ejemplo de MySQL diciendo "estándares de tornillo, haré lo que quiera". (Afortunadamente, se puede incluir ANSI_QUOTESen el modo de solucionar el quebrantamiento citando el abierto desconocimiento de las normas, sin embargo, es un problema más grande que podría requerir medidas más severas..)
Chao
2
@DanAllen: mi respuesta fue un poco más amplia, ya que puede evitar este error en particular a través de la quote()función de PDO, pero las declaraciones preparadas son una forma mucho más segura y apropiada de evitar la inyección en general. Por supuesto, si ha concatenado directamente variables no escaneadas en su SQL, entonces seguramente será vulnerable a la inyección, sin importar qué métodos use a partir de entonces.
eggyal
1
@eggyall: Nuestro sistema se basa en el segundo ejemplo de seguridad anterior. Hay errores en los que se ha omitido mysql_real_escape_string. Arreglarlos en un modo de emergencia parece ser el camino prudente, con la esperanza de que no nos ataquen antes de las correcciones. Mi justificación es que la conversión a declaraciones preparadas será un proceso mucho más largo que tendrá que venir después. ¿La razón por la que las declaraciones preparadas es más segura es el hecho de que los errores no crean vulnerabilidades? En otras palabras, ¿se implementa correctamente el segundo ejemplo anterior es tan seguro como las declaraciones preparadas?
DanAllen
18

Bueno, realmente no hay nada que pueda pasar por eso, aparte del %comodín. Podría ser peligroso si usara una LIKEdeclaración, ya que el atacante podría poner solo %como inicio de sesión si no lo filtra, y tendría que forzar la contraseña de cualquiera de sus usuarios. Las personas a menudo sugieren usar declaraciones preparadas para que sea 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);

Slava
fuente
2
+1, pero los comodines son para la cláusula LIKE, no para la simple igualdad.
Dor
77
¿En qué medida considera un reemplazo simple more efficientque el uso de declaraciones preparadas? (Las declaraciones preparadas siempre funcionan, la biblioteca se puede corregir rápidamente en caso de ataques, no expone el error humano [como escribir mal la cadena completa de reemplazo] y tiene importantes beneficios de rendimiento si la declaración se reutiliza)
MatBailie
77
@Slava: está limitando efectivamente los nombres de usuario y las contraseñas solo a caracteres de palabras. La mayoría de las personas que saben algo de seguridad lo considerarían una mala idea, ya que reduce considerablemente el espacio de búsqueda. Por supuesto, también considerarían una mala idea almacenar contraseñas de texto sin cifrar en la base de datos, pero no necesitamos agravar el problema. :)
cHao
2
@ cHao, mi sugerencia se refiere solo a inicios de sesión. Obviamente no necesita filtrar las contraseñas, lo siento, no está claramente establecido en mi respuesta. Pero en realidad eso podría ser una buena idea. Usar "espacio de árbol ignorante de piedra" en lugar de difícil de recordar y escribir "a4üua3! @V \" ä90; 8f "sería mucho más difícil de aplicar. Incluso usando un diccionario de, digamos 3000 palabras para ayudarte, sabiendo usaste exactamente 4 palabras, eso todavía sería aproximadamente 3.3 * 10 ^ 12 combinaciones. :)
Slava
2
@Slava: He visto esa idea antes; ver xkcd.com/936 . El problema es que las matemáticas no lo confirman. Su contraseña de 17 caracteres podría tener 96 ^ 17 posibilidades, y eso si olvida las diéresis y se limita a ASCII imprimible. Eso es aproximadamente 4.5x10 ^ 33. Estamos hablando literalmente mil millones de billones de veces más de trabajo para la fuerza bruta. Incluso una contraseña ASCII de 8 caracteres tendría 7,2x10 ^ 15 posibilidades, 3 mil veces más.
cHao