Cuidado con el SQL Injection

November 10, 2006

Un reciente post de Joel explicaba como hacer SQL Injection. Voy a extender aquí un poco la información.
Imaginemos que somos muy malos programadores y tenemos el siguiente código:

String query=”select user, pass from user where user='”+user+”‘ “;

Si introducimos como usuario pepito, tenemos que se ejecuta:

select user, pass from user where user=’pepito’

¡Fenomenal! …¿Fenomenal? y si en vez de usar pepito pongo ‘;drop table xxxx; — , ¿Qué pasa?

select user, pass from user where user=”; drop table xxxx; — ‘

¡Fenomenal! Nos acabamos de cepillar la tabla xxxx;

Vale, entonces es cuando pensamos, pues filtramos todos las comillas simples. Y entonces es cuando viene nuestro mejor cliente, el señor O’Brian y decide que si nuestro sistema no acepta su nombre quizás es que no merecemos que se gaste dinero en nosotros. Así que bueno, aceptamos ‘, pero sustituimos ‘ por ” en todos los literales.

Encones tenemos algo así:

insert into xxxx values … O”Brian ….

Y en BD queda como O’Brian.

Joder. ¡Somos buenísimos! Hemos arreglado el problema del SQL Injection con sustituir ‘ por ” doble… ¿o no?

Imaginamos que una vez tenemos un usuario en nuestra BD, luego utilizamos dicho dato para hacer otras queries a su vez (Ej.:”Dame todas las compras del usuario X”). Como solo securizamos las queries que tenían entrada de usuario, resulta que seguimos teniendo la siguiente query:

query=”select * from compras where usuario='”+usuario+”‘”;

¡Y ya la tenemos montada otra vez!

Resulta que como protegemos la query de entrada pero, una vez almacenado el registro, guardamos el texto sin proteger, tenemos otra vez el mismo problema.

Conclusión 1 : Protege TODAS LAS QUERIES, no solo las que crees que son de entrada.

¿Filtramos solo las comillas simples?

Bueno, quizás tengas algo así:

String id= xxxxx

query=”select * from tabla where id=” +id;

Siendo el id numérico en la BD. Pues ya la tenemos montada, no nos hacen falta comillas para nada.

Podemos dar a id el valor de id, o algo peor id; drop table xxxx; o quitamos el resto del filtrado con — o /** **/ (Comentario en SQL) ,etc.

Otra: en mysql por ejemplo 9e3 es 9000, cuidado con pensar que limitando el tamaño del parámetro podemos conseguir algo…

Conclusión 2: Son vulnerables TODOS los campos, repasa todos y cada uno de los campos

Otro error común es pensar, bueno, nuestro modelo de datos es complejo, nadie va a adivinar el nombre de las tablas y de los campos…¡MAL! Pueden sacarlos si los errores de BD se sacan directamente al usuario. Ahh vale, tu siempre rediriges a una página de error, estas a salvo…¡EEEERRROOOOOR! Se pueden sacar por inferencia. ¿Cómo? Con sentencias condicionales:

select case when condicion then ‘menor’ else ‘mayor’ end;

Es decir, devolvemos un texto o otro dependiendo de la condición.

Así la condición puede ser, dame el primer bit de cierto campo. Y el then/else un elemento de diferenciación. Donde la “diferenciación” pueden ser varias cosas:

  • Esperas de tiempo diferente
  • Variar la respuesta
  • Devolver error en uno y en otro no (Ejemplo: el campo a devolver es un numérico y en uno devolvemos 3 y en otro ‘caca’ o más fácil: “select case when condición then 37 else 37/0”)

Además pueden atacar a las tablas de metadatos donde se almacenan las descripciones de las tablas, etc.

Por tanto…

Conclusión 3: Nuestro sistema no es invulnerable porque “ocultemos” los nombres de tablas y campos.

Hay unos cuantos caracteres más que filtrar. SQL es un lenguaje y por tanto hay muchas formas de hacer las cosas, así que si usas Java lo mejor es utilizar Prepared Statements.

Un presentación sobre el tema aquí.

UPDATE: Rubén me envía este enlace. Guía para desarrollo Web seguro. Imprescindible.