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.