¿Htmlspecialchars y mysql_real_escape_string mantienen mi código PHP a salvo de la inyección?

Respuestas:

241

Cuando se trata de consultas de bases de datos, siempre intente utilizar consultas parametrizadas preparadas. Las bibliotecas mysqliy lo PDOadmiten. Esto es infinitamente más seguro que usar funciones de escape como mysql_real_escape_string.

Sí, mysql_real_escape_stringes efectivamente solo una función de escape de cadenas. No es una fórmula mágica. Todo lo que hará es escapar de los caracteres peligrosos para que sean seguros de usar en una sola cadena de consulta. Sin embargo, si no desinfecta sus entradas de antemano, será vulnerable a ciertos vectores de ataque.

Imagina el siguiente SQL:

$result = "SELECT fields FROM table WHERE id = ".mysql_real_escape_string($_POST['id']);

Debería poder ver que esto es vulnerable a la explotación.
Imagina que el idparámetro contiene el vector de ataque común:

1 OR 1=1

No hay caracteres de riesgo para codificar, por lo que pasará directamente a través del filtro de escape. Dejándonos:

SELECT fields FROM table WHERE id= 1 OR 1=1

Que es un hermoso vector de inyección SQL y permitiría al atacante devolver todas las filas. O

1 or is_admin=1 order by id limit 1

que produce

SELECT fields FROM table WHERE id=1 or is_admin=1 order by id limit 1

Lo que permite al atacante devolver los datos del primer administrador en este ejemplo completamente ficticio.

Si bien estas funciones son útiles, deben usarse con cuidado. Debe asegurarse de que todas las entradas web estén validadas hasta cierto punto. En este caso, vemos que podemos ser explotados porque no verificamos que una variable que estábamos usando como número fuera en realidad numérica. En PHP debería utilizar ampliamente un conjunto de funciones para comprobar que las entradas sean enteros, flotantes, alfanuméricos, etc. Pero cuando se trata de SQL, preste más atención al valor de la declaración preparada. El código anterior habría sido seguro si fuera una declaración preparada, ya que las funciones de la base de datos habrían sabido que 1 OR 1=1no es un literal válido.

En cuanto a htmlspecialchars(). Ese es un campo minado en sí mismo.

Existe un problema real en PHP ya que tiene una selección completa de diferentes funciones de escape relacionadas con html y no hay una guía clara sobre exactamente qué funciones hacen qué.

En primer lugar, si está dentro de una etiqueta HTML, está en un verdadero problema. Mirar

echo '<img src= "' . htmlspecialchars($_GET['imagesrc']) . '" />';

Ya estamos dentro de una etiqueta HTML, por lo que no necesitamos <o> para hacer nada peligroso. Nuestro vector de ataque podría serjavascript:alert(document.cookie)

Ahora el HTML resultante parece

<img src= "javascript:alert(document.cookie)" />

El ataque llega directo.

Se pone peor. ¿Por qué? porque htmlspecialchars(cuando se llama de esta manera) solo codifica comillas dobles y no simples. Entonces si tuviéramos

echo "<img src= '" . htmlspecialchars($_GET['imagesrc']) . ". />";

Nuestro malvado atacante ahora puede inyectar parámetros completamente nuevos

pic.png' onclick='location.href=xxx' onmouseover='...

Nos da

<img src='pic.png' onclick='location.href=xxx' onmouseover='...' />

En estos casos, no existe una fórmula mágica, solo tienes que corregir la entrada tú mismo. Si intentas filtrar los personajes malos, seguramente fallarás. Adopte un enfoque de lista blanca y deje pasar solo los caracteres que sean buenos. Mire la hoja de trucos de XSS para ver ejemplos sobre cómo diversos vectores pueden ser

Incluso si utiliza htmlspecialchars($string)etiquetas fuera de HTML, sigue siendo vulnerable a los vectores de ataque de conjuntos de caracteres de varios bytes.

Lo más eficaz que puede ser es utilizar una combinación de mb_convert_encoding y htmlentities de la siguiente manera.

$str = mb_convert_encoding($str, 'UTF-8', 'UTF-8');
$str = htmlentities($str, ENT_QUOTES, 'UTF-8');

Incluso esto deja a IE6 vulnerable, debido a la forma en que maneja UTF. Sin embargo, puede recurrir a una codificación más limitada, como ISO-8859-1, hasta que el uso de IE6 disminuya.

Para un estudio más profundo de los problemas multibyte, consulte https://stackoverflow.com/a/12118602/1820

Cheekysoft
fuente
24
Lo único que se perdió aquí es que el primer ejemplo para la consulta DB ... un simple intval () resolvería la inyección. Utilice siempre intval () en lugar de mysqlescape ... () cuando necesite un número y no una cadena.
Robert K
11
y recuerda que el uso de consultas parametrizadas te permitirá tener siempre los datos tratados como datos y no como código. Utilice una biblioteca como PDO y utilice consultas parametrizadas siempre que sea posible.
Cheekysoft
9
Dos comentarios: 1. En el primer ejemplo, estaría seguro si también colocara comillas alrededor del parámetro, como $result = "SELECT fields FROM table WHERE id = '".mysql_real_escape_string($_POST['id'])."'";2. En el segundo caso (atributo que contiene la URL), no hay ningún uso para htmlspecialchars; en estos casos, debe codificar la entrada usando un esquema de codificación de URL, por ejemplo, using rawurlencode. De esa manera, un usuario no puede insertar javascript:et al.
Marcel Korpel
7
“Htmlspecialchars solo codifica comillas dobles y no simples”: eso no es cierto, depende de las banderas que se establezcan, consulte sus parámetros .
Marcel Korpel
2
Esto debe estar en negrita: Take a whitelist approach and only let through the chars which are good.una lista negra siempre perderá algo. +1
Jo Smo
10

Además de la excelente respuesta de Cheekysoft:

  • Sí, lo mantendrán a salvo, pero solo si se usan absolutamente correctamente. Úselos incorrectamente y seguirá siendo vulnerable y puede tener otros problemas (por ejemplo, corrupción de datos)
  • En su lugar, utilice consultas parametrizadas (como se indicó anteriormente). Puede usarlos, por ejemplo, a través de PDO o mediante un contenedor como PEAR DB
  • Asegúrese de que magic_quotes_gpc y magic_quotes_runtime estén apagados en todo momento y nunca se enciendan accidentalmente, ni siquiera brevemente. Estos son un intento temprano y profundamente equivocado de los desarrolladores de PHP para prevenir problemas de seguridad (que destruyen los datos).

En realidad, no existe una fórmula mágica para prevenir la inyección de HTML (por ejemplo, secuencias de comandos entre sitios), pero es posible que pueda lograrlo más fácilmente si está utilizando una biblioteca o un sistema de plantillas para generar HTML. Lea la documentación para saber cómo escapar de las cosas de manera adecuada.

En HTML, las cosas deben escaparse de manera diferente según el contexto. Esto es especialmente cierto en el caso de cadenas que se colocan en Javascript.

MarkR
fuente
3

Definitivamente estaría de acuerdo con las publicaciones anteriores, pero tengo una pequeña cosa que agregar en respuesta a la respuesta de Cheekysoft, específicamente:

Cuando se trata de consultas de bases de datos, siempre intente utilizar consultas preparadas parametrizadas. Las bibliotecas mysqli y PDO admiten esto. Esto es infinitamente más seguro que usar funciones de escape como mysql_real_escape_string.

Sí, mysql_real_escape_string es efectivamente solo una función de escape de cadenas. No es una fórmula mágica. Todo lo que hará es escapar de los caracteres peligrosos para que sean seguros de usar en una sola cadena de consulta. Sin embargo, si no desinfecta sus entradas de antemano, será vulnerable a ciertos vectores de ataque.

Imagina el siguiente SQL:

$ resultado = "SELECCIONAR campos DE la tabla DONDE id =" .mysql_real_escape_string ($ _ POST ['id']);

Debería poder ver que esto es vulnerable a la explotación. Imagine que el parámetro id contiene el vector de ataque común:

1 O 1 = 1

No hay caracteres de riesgo para codificar, por lo que pasará directamente a través del filtro de escape. Dejándonos:

SELECCIONE campos DE la tabla DONDE id = 1 O 1 = 1

Codifiqué una pequeña función rápida que puse en mi clase de base de datos que eliminará todo lo que no sea un número. Utiliza preg_replace, por lo que es probable que haya una función un poco más optimizada, pero funciona en un apuro ...

function Numbers($input) {
  $input = preg_replace("/[^0-9]/","", $input);
  if($input == '') $input = 0;
  return $input;
}

Entonces en lugar de usar

$ resultado = "SELECCIONAR campos DE la tabla DONDE id =" .mysqlrealescapestring ("1 O 1 = 1");

yo usaría

$ resultado = "SELECCIONAR campos DE la tabla DONDE id =" .Números ("1 O 1 = 1");

y ejecutaría la consulta de forma segura

SELECCIONAR campos DE la tabla DONDE id = 111

Claro, eso simplemente impidió que mostrara la fila correcta, pero no creo que sea un gran problema para quien esté tratando de inyectar sql en su sitio;)

BrillanteInvierno
fuente
1
¡Perfecto! Este es exactamente el tipo de desinfección que necesita. El código inicial falló porque no validó que un número fuera numérico. Tu código hace esto. debe llamar a Numbers () en todas las variables de uso entero cuyos valores se originan fuera del código base.
Cheekysoft
1
Vale la pena mencionar que intval () funcionará perfectamente bien para esto, ya que PHP convierte automáticamente los enteros en cadenas por usted.
Adam Ernst
11
Prefiero intval. Se convierte 1abc2 en 1, no 12.
jmucchiello
1
intval es mejor, especialmente en ID. La mayoría de las veces, si está dañado, es como se muestra arriba, 1 o 1 = 1. Realmente no debería filtrar la identificación de otras personas. Entonces, intval devolverá la ID correcta. Después de eso, debe verificar si los valores originales y limpios son los mismos. Es una excelente manera no solo de detener los ataques, sino también de encontrar a los atacantes.
triunenature
2
La fila incorrecta sería desastrosa si muestra datos personales, ¡verá la información de otro usuario! en su lugar, sería mejor verificarreturn preg_match('/^[0-9]+$/',$input) ? $input : 0;
Frank Forte
2

Una pieza importante de este rompecabezas son los contextos. Alguien que envíe "1 OR 1 = 1" como ID no es un problema si cita todos los argumentos en su consulta:

SELECT fields FROM table WHERE id='".mysql_real_escape_string($_GET['id'])."'"

Lo que resulta en:

SELECT fields FROM table WHERE id='1 OR 1=1'

que es ineficaz. Dado que está escapando de la cadena, la entrada no puede salir del contexto de la cadena. He probado esto hasta la versión 5.0.45 de MySQL, y el uso de un contexto de cadena para una columna de número entero no causa ningún problema.

Lucas Omán
fuente
15
y luego comenzaré mi vector de ataque con el carácter multibyte 0xbf27 que en su base de datos latin1 será convertido por la función de filtro como 0xbf5c27, que es un solo carácter multibyte seguido de una comilla simple.
Cheekysoft
8
Trate de no protegerse contra un solo vector de ataque conocido. Terminarás persiguiéndote hasta el final del tiempo aplicando parche tras parche a tu código. Retroceder y observar los casos generales conducirá a un código más seguro y una mentalidad mejor centrada en la seguridad.
Cheekysoft
Estoy de acuerdo; idealmente, OP utilizará declaraciones preparadas.
Lucas Oman
1
Si bien la cita de los argumentos sugeridos por esta publicación no es infalible, mitigará muchos de los ataques comunes de tipo 1 O 1 = 1, por lo que es digno de mención.
Night Owl
2
$result = "SELECT fields FROM table WHERE id = ".(INT) $_GET['id'];

Funciona bien, incluso mejor en sistemas de 64 bits. Sin embargo, tenga cuidado con las limitaciones de su sistema al abordar grandes números, pero para los identificadores de bases de datos esto funciona muy bien el 99% del tiempo.

También debería utilizar una única función / método para limpiar sus valores. Incluso si esta función es solo un contenedor para mysql_real_escape_string (). ¿Por qué? Porque un día, cuando se encuentra un exploit para su método preferido de limpieza de datos, solo tiene que actualizarlo en un lugar, en lugar de buscar y reemplazar en todo el sistema.

cnizzardini
fuente
-3

¿Por qué, oh POR QUÉ, no incluirías comillas alrededor de la entrada del usuario en tu declaración sql? parece bastante tonto no hacerlo! incluir comillas en su declaración SQL haría que "1 o 1 = 1" sea un intento infructuoso, ¿no?

así que ahora, dirá, "¿y si el usuario incluye una cita (o comillas dobles) en la entrada?"

bueno, solución fácil para eso: simplemente elimine las comillas ingresadas por el usuario. por ejemplo: input =~ s/'//g;. ahora, me parece que de todos modos, la entrada del usuario estaría asegurada ...

Jarett L
fuente
"¿Por qué, oh POR QUÉ, no incluirías comillas alrededor de la entrada del usuario en tu declaración SQL?" - La pregunta no dice nada sobre no citar la entrada del usuario.
Quentin
1
"Bueno, solución fácil para eso" - Terrible solución para eso. Eso tira los datos. La solución mencionada en la pregunta en sí es un mejor enfoque.
Quentin
aunque estoy de acuerdo en que la pregunta no aborda la citación de la entrada del usuario, todavía parece no citar la entrada. y prefiero tirar datos que ingresar datos incorrectos. en general, en un ataque de inyección, NO quieres esos datos de todos modos ... ¿verdad?
Jarett L
"aunque estoy de acuerdo en que la pregunta no se refiere a citar la entrada del usuario, todavía parece que todavía no citar la entrada". - No, no es así. La pregunta no lo demuestra de una forma u otra.
Quentin
1
@JarettL Acostúmbrese a usar declaraciones preparadas o acostúmbrese a que Bobby Tables destruya sus datos todos los martes . SQL parametrizado es la mejor manera de protegerse contra la inyección de SQL. No es necesario realizar "comprobaciones de inyección SQL" si está utilizando una declaración preparada. Son extremadamente fáciles de implementar (y en mi opinión, hacen que el código sea MUCHO más fácil de leer), protegen de varias idiosincrasias de concatenación de cadenas e inyección de sql y, lo mejor de todo, no es necesario reinventar la rueda para implementarlas. .
Siyual