Las inyecciones ocupan la tercera plaza en el podio de las vulnerabilidades web más relevantes, según la clasificación de OWASP (Open Web Application Security Project), en su último ranking publicado en 2021.
Una inyección es un ataque que se produce cuando un agente malicioso consigue pasar información no validada a un intérprete, como parte de una petición. El receptor, pensando que los datos son legítimos, las ejecuta, o permite el acceso a elementos privilegiados. Los ataques más comunes de inyección son en SQL, NoSQL, comandos del sistema operativo y LDAP.
En este artículo encontraréis herramientas para poder detectar la existencia de inyecciones en nuestro código, y recomendaciones para poder prevenirlas, mitigando así las posibles consecuencias.
Se puede considerar que una aplicación es vulnerable a un ataque de este tipo en las siguientes situaciones:
- Los datos proporcionados por el usuario no son validados, filtrados, ni saneados por el sistema.
- Se invocan consultas dinámicas o no parametrizadas, sin codificar los parámetros de forma adecuada según el contexto.
- Se utilizan datos maliciosos dentro de los parámetros de búsqueda en consultas Object-Relational Mapping (ORM), para extraer registros adicionales sensibles.
- Se usan datos maliciosos directamente, o se concatenan. De forma que la sentencia SQL o comando resultante contiene información, y estructuras con consultas dinámicas o procedimientos alterados.
Las acciones recomendadas para prevenir este problema son las siguientes:
- Emplear una API segura, que evite totalmente el uso de un intérprete o proporcione una interfaz parametrizada.
- Implementar validaciones de entradas de datos en el servidor, utilizando entrada positiva o «whitelist».
- Hacer uso de LIMIT y otros controles SQL sobre el número y el tipo de consultas, para evitar la divulgación masiva de registros en caso de inyección SQL.
- Usar la sintaxis de escape específica, para evitar el uso de caracteres especiales que validan una consulta.
- Filtrar según la semántica de la aplicación, por ejemplo:
- Barcelona como nombre válido de ciudad.
- <b>destacado</b> puede resultar un comentario válido si la aplicación permite un campo con texto enriquecido, pero no si no es así.
A continuación, se muestra un ejemplo de escenario donde sería posible realizar un ataque por inyección.
Un caso de inyección se podría producir al recibir datos del usuario y utilizarlos para hacer sentencias sobre una base de datos, sin que estos pasen por ningún tipo de proceso de validación.
El código de la imagen corresponde a un fragmento de una función Java vulnerable. En ella se utilizan parámetros extraídos directamente de la consulta, sin ningún procedimiento de saneamiento, para realizar una query SQL donde se obtiene el usuario con nombre y contraseña iguales a los recibos. Esto permitiría a un atacante enviar sentencias SQL maliciosas, como por ejemplo ‘username=admin;– –‘. El programa, al recibir este valor para el nombre de usuario, obtiene la entrada vinculada de la base de datos sin importar las credenciales válidas, y devuelve true como resultado. Esto sucede porque como no se escapa el elemento, este puede pasar a formar parte de la sentencia, en vez de únicamente ser el atributo de un campo. Entonces lo que se ejecuta finalmente es «SELECT * FROM usuarios WHERE id_usuario=’admin’;– -» de forma que la parte «AND password='» + password ‘+ «‘»; se ignora por encontrarse ahora comentada. El atacante, conociendo el nombre de usuario del administrador, y suponiendo que esta función es la que se utiliza para gestionar el inicio de sesión, conseguiría acceso privilegiado al sistema.
Para solucionar el problema en esta situación concreta, podemos usar prepared statement para hacer la petición, puesto que esta función separa lo que ha enviado el usuario de la query, proporcionando así una validación de los datos. Al recibir el mismo valor que en el caso anterior, “userrname=admin;– -”, esta nueva implementación no sufriría el mismo problema. El código en este caso escaparía el parámetro, de forma que “admin;– -” no se entendería como una parte de la sentencia, sino que únicamente sería el valor del campo id_usuario.
En cualquier otro caso, el procedimiento sería el mismo, se tendría que buscar la forma de conseguir aislar aquello que recibimos del front-end de las conexiones con la base de datos. Para hacerlo podemos utilizar un procedimiento ya existente, como el ejemplo, o bien realizar las comprobaciones necesarias por nuestra cuenta, de manera cuidadosa, para no dejarse ningún caso que pueda comprometer el sistema.
Seguidamente, se adjunta un ejemplo más visual de las consecuencias que tendría hacer uso de un código inseguro como el anterior en una aplicación. Para esta demostración se ha utilizado la OWASP Juice Shop, una aplicación insegura creada para poder practicar la explotabilidad de las vulnerabilidades en un entorno seguro.
Aunque se pueden encontrar agujeros de todo tipo, el ejemplo se centrará en un panel de inicio de sesión vulnerable a inyecciones. El objetivo es hacer login como un usuario administrador sin conocer sus credenciales.
La primera tarea a realizar es descubrir una dirección electrónica privilegiada. Inspeccionando los productos en la página principal se pueden ver reseñas de los usuarios, donde se expone su correo como se puede apreciar en la imagen anterior. Como admin@juice-sh.op es casi con total seguridad la dirección del usuario admin, intentaremos iniciar sesión en el panel utilizando esta nueva información, pero todavía sin saber su contraseña.
Se ha utilizado la técnica descrita en el anterior ejemplo: en el campo email se coloca el correo al cual se quiere acceder admin@juice-sh.op, en este caso, seguido de la condición SQL OR 1=1– –. El servidor, al recibir estos valores por parte del cliente y no realizar ninguna validación, entenderá los campos como parte de la sentencia SQL que se realiza sobre la base a datos. De esta forma se busca el usuario que tiene esta dirección (finalizando el contenido del campo con la ‘ ) y se ejecuta la cláusula OR 1=1, que al evaluarse es true. Finalmente, se incluye «– -« para comentar el resto de la query, porque si se ejecutara, se haría igualmente la comprobación de la contraseña devolviendo false. En conclusión, aquello que se ejecutaría en el servidor sería muy parecido a: SELECT * FROM Usuarios WHERE email = ‘mail enviado’ OR 1=1 – – (AND contraseña = ‘contraseña enviada’; comentado, no se ejecuta). Siempre devolverá true si existe algún usuario con la dirección enviada, sin importar la contraseña que se adjunte en el formulario, consiguiendo acceder a su cuenta sin conocer las credenciales.
Cómo se ve en la imagen, los valores anteriores nos han permitido obtener acceso a la cuenta del administrador, ahora nos encontramos logueados como el usuario admin.
Se podría pensar que protegiendo el cliente con algún tipo de script que denegara insertar sentencias maliciosas sería suficiente para garantizar la seguridad del sistema. La realidad es que las comprobaciones en el ámbito del navegador son fácilmente evitables, y resultan totalmente inútiles si no se toman las medidas adecuadas junto al servidor, como las descritas en el primer ejemplo donde se muestra el código.
Un atacante podría evadir el control en el client side interceptando las peticiones con un proxy, y modificando los campos con los valores deseados fuera del buscador.
Como ejemplo, se supone que el panel bloquea los valores, y estos se modifican por los anteriores interceptando los datos con BURPSUITE.
Ahora se ha introducido en el login los valores «SANTI», «SANTI» como se puede ver en la primera imagen. Al enviar los datos se ha interceptado la petición con el proxy y, como se puede apreciar en la segunda imagen, los campos email y password son equivalentes a los del formulario. El siguiente paso será cambiar los valores para realizar la misma sentencia maliciosa que en un panel sin protección.
En esta imagen se muestra el valor malicioso para el campo email. Como el servidor no hace ninguna comprobación, el resultado será el mismo: conseguir acceso a la cuenta administradora, sin importar las nuevas medidas al navegador.
En conclusión, los ataques por inyección suceden al confiar en los datos enviados por cualquier usuario con acceso a la aplicación. Por lo tanto, es importante protegerse haciendo comprobaciones sobre todo aquello que se recibe. Teniendo en cuenta siempre que, aunque las protecciones en el lado del cliente pueden resultar útiles para la experiencia del usuario legítimo, las medidas de seguridad siempre se tienen que tomar en el lado del servidor, donde la información es realmente procesada.