7 days of WordPress plugins, themes & templates - for free!* Unlimited asset downloads! Start 7-Day Free Trial
Advertisement
  1. Code
  2. ASP.NET

Previniendo la inyección de código

Scroll to top
Read Time: 14 mins

Spanish (Español) translation by Ana Paulina Figueroa (you can also view the original English article)

A menudo, los sitios web parecen existir principalmente para colocar algo en una base de datos con el fin de extraerlo más tarde. Si bien otros métodos de bases de datos, tales como NoSQL, han ganado popularidad en los últimos años, la información de muchos sitios web aún reside en la base de datos SQL tradicional. Estos datos a menudo son información personal valiosa, por ejemplo números de tarjetas de crédito y otros datos personales de interés para criminales y ladrones de identidad. Por este motivo los hackers siempre intentan obtener esta información. Uno de los blancos más comunes de estos ataques son las bases de datos SQL que se encuentran detrás de muchas aplicaciones web, a través de un proceso de inyección de comandos SQL.

Un ataque por inyección funciona haciendo que la aplicación envíe información no confiable al intérprete. Recientemente, 40,000 registros de clientes fueron tomados de Bell Canada como resultado de un ataque por inyección de comandos SQL. A fines de 2013, los hackers robaron más de $100,000 de un ISP con sede en California usando inyección de SQL.

El Open Web Application Security Project (OWASP, o Proyecto abierto de seguridad de aplicaciones web) eligió al ataque por inyección como el riesgo de seguridad número uno para las aplicaciones en su top 10 del año 2013, en base a su prevalencia y al riesgo para los sistemas web atacados. Desafortunadamente, también obtuvo la posición número uno en el reporte anterior del año 2010. Entonces, ¿qué es un ataque por inyección de SQL?. En este tutorial discutiré cómo funcionan y qué puedes hacer para proteger a tu aplicación de estos ataques.

¿Qué es un ataque por inyección?

Cualquier sistema interpretado detrás de un servidor web puede ser blanco de un ataque por inyección. Los blancos más comunes son los servidores de bases de datos SQL que se encuentran detrás de muchos sitios web. La inyección de SQL no es resultado directo de las debilidades de la base de datos, pero usa aperturas en la aplicación para permitir que el atacante ejecute instrucciones de su elección en el servidor. Un ataque por inyección de SQL hace que la base de datos comparta más información que la que la aplicación está diseñada para proporcionar. Veamos una llamada a una base de datos SQL que puedes escribir en ASP.NET.

¿Qué está mal con este código? tal vez nada. El problema es esa cadena id que estamos usando. ¿De dónde viene ese valor? si estamos generándolo internamente o desde una fuente confiable, entonces este código podría funcionar sin problemas. Sin embargo, si estamos obteniendo el valor de un usuario y lo usamos sin modificaciones, acabamos de abrirnos a la posibilidad de una inyección de SQL.

Tomemos un caso común en el que estamos obteniendo el parámetro a buscar como parte del URL. Toma el URL http://www.example.com/user/details?id=123. En ASP.NET usando C# podemos obtener el valor que está siendo enviado usando este código:

Este código combinado con la llamada anterior nos deja vulnerables a un ataque. Si el usuario envía un id de usuario como 123 según lo esperado, todo funciona bien. Sin embargo, nada de lo que hemos hecho aquí garantiza que ese sea el caso. Digamos que el atacante intenta acceder al URL http://www.example.com/user/details?id=0; SELECT * FROM userdata.

En vez de simplemente enviar un valor como se espera, hemos dado un valor y luego añadido un punto y coma, lo que termina una instrucción SQL. Luego se agrega una segunda instrucción SQL que el atacante querría ejecutar. En este caso devolvería todos los registros de la tabla userdata. Dependiendo del resto del código de nuestra página, ésta podría devolver un error o tal vez mostrar todos los registros de la base de datos al atacante. Incluso es posible usar un error con consultas cuidadosamente construidas para crear una vista de la base de datos. Peor aún, imagina si el atacante visita un URL http://www.example.com/user/details?id=0; DROP TABLE userdata. Ahora todos tus usuarios se habrán perdido.

En este ejemplo, el atacante puede ejecutar cualquier código que desee en el servidor de la base de datos. Si la cuenta en la que se ejecutan las llamadas a la base de datos tiene control total sobre la misma, lo cuál es un escenario demasiado común, entonces la eliminación de tablas y registros facilita el derribe de un sitio. Incluso si el atacante solamente puede leer y escribir información en la base de datos, entonces obtener los datos solamente es cuestión de paciencia y cuidado.

database-view

Toma una base de datos simple llamada "Products" que consta de tres columnas. La primera columna contiene el id del producto, la segunda contiene el nombre del mismo y la tercera el precio. Para nuestra consulta normal vamos a intentar encontrar todos los productos que tengan la cadena "widget" en su nombre. La instrucción SQL para hacer esto con el mismo patrón que hemos mostrado se vería así:

Un sitio web típico para acceder a esto se vería como http://www.example.com/product?search=widget. Aquí la página web simplemente recorrerá cada registro devuelto y lo mostrará en la pantalla. En este caso vemos nuestro producto "widget".

Cambiemos nuestra consulta por http://www.example.com/product?search=widget' OR 1=1;-- y ejecutemos la misma instrucción. Podemos ver algo más. De hecho veremos todos los registros de la tabla.

Hemos engañado al intérprete para que ejecute el código SQL de nuestra elección. El SQL ejecutado resultante será:

El resultado serán dos instrucciones:

Al añadir la igualdad 1=1, que siempre es verdadera, la cláusula where será verdadera para cada fila de la tabla y la consulta resultante devolverá todas las filas. El -- al inicio de la segunda instrucción convierte al resto del comando SQL en un comentario, lo que evita que aparezca el mensaje de error que de otra forma veríamos.

Los cambios en el despliegue de los datos no pueden prevenir esta instrucción. Un atacante paciente puede usar incluso el simple hecho de que un valor es devuelto o no y generar consultas cuidadosamente para mapear lentamente tu base de datos y posiblemente obtener información, aún si solamente ve un mensaje de error. Existen herramientas como sqlmap para automatizar el proceso.

Mitigando la inyección de SQL

Puedes prevenir la inyección de SQL impidiendo que entradas no confiables lleguen a la base de datos SQL o a otro intérprete. Cualquier entrada exterior al sistema debe considerarse como no confiable. Incluso los datos de otros sistemas asociados deben ser considerados como no confiables, ya que no tienes manera de garantizar que el otro sistema no sufre de problemas de seguridad que permitirían la inserción de datos arbitrarios que luego serían enviados a tu aplicación.

Regresando a nuestro ejemplo anterior, si sabemos que el parámetro id siempre debe ser un entero podemos intentar convertirlo a entero y mostrar un error si esto falla. Una aplicación MVC de ASP.NET haría esto usando código similar a este:

Esto intentará convertir la cadena a un entero. Si la conversión falla, el código redirige a una acción que mostrará un mensaje de invalidez.

Esto puede prevenirse si estamos esperando un parámetro entero. No ayudaría si estamos esperando texto, como en el caso de la búsqueda de productos anterior. La técnica preferida en este caso es usar expresiones regulares o reemplazos de cadenas para permitir solamente caracteres necesarios en el valor enviado.

Esto puede lograrse creando listas blancas, que es el proceso de eliminar cualquier carácter que no esté en un conjunto especificado, o listas negras, que es el proceso de eliminar de la cadena cualquier miembro de un conjunto especificado. Crear listas blancas es más confiable, ya que puedes especificar solamente caracteres permitidos. Para eliminar cualquier cosa que no sean letras y números en una cadena, podemos usar código como el siguiente:

Deberás evaluar cada entrada respectivamente. Algunas consultas de bases de datos pueden necesitar una atención más especializada. Toma caracteres que puedan ser significativos en un comando SQL pero que también puedan ser caracteres válidos en una llamada a una base de datos. Por ejemplo, el carácter de comilla simple ' se usa para iniciar y terminar una cadena en SQL, pero también puede ser parte del nombre de una persona, por ejemplo O'Conner. En este caso reemplazar la comilla simple ' con comillas simples consecutivas '' puede solucionar el problema.

Procedimientos almacenados

Los procedimientos almacenados a menudo son vistos como la solución a este problema, y pueden ser parte de la solución. Sin embargo, un procedimiento almacenado mal escrito no te salvará. Toma este procedimiento almacenado que incluye código similar al que ya hemos visto para generar una consulta:

Aquí el problema es la concatenación de cadenas. Hagamos un intento simple de establecer una condición que siempre sea verdadera para mostrar todas las filas de la tabla y enviar el mismo `widget' OR 1=1;--`` como en la consulta que vimos anteriormente. El resultado también es el mismo:

Si se envían datos que no sean de confianza, obtendremos el mismo resultado que el que recibiríamos si la llamada hubiera sido creada en nuestro código. El hecho de que la concatenación de cadenas ocurra dentro de un procedimiento almacenado en vez de en nuestro código no proporciona ninguna protección.

Parametrización

La siguiente pieza del rompecabezas en cuanto a la protección contra ataques por inyección es el uso de la parametrización. Al generar consultas SQL por concatenación de cadenas y enviar posteriormente el código terminado no permite indicarle a la base de datos qué parte de la cadena es un parámetro y qué es parte del comando. Podemos ayudar a la protección contra ataques creando llamadas SQL de una manera que permita hacer una distinción entre instrucciones y valores.

Podemos volver a escribir el procedimiento almacenado mostrado anteriormente para que use parámetros y así generar una llamada más segura. En vez de concatenar los caracteres % que representan comodines, vamos a crear una nueva cadena añadiendo estos caracteres y luego la enviaremos como parámetro a la instrucción SQL. El nuevo procedimiento almacenado se ve así:

La ejecución de este procedimiento almacenado únicamente con la palabra widget funciona como se esperaba.

Y si enviamos nuestro parámetro con el valor widget'' OR 1=1;-- no recibiremos ningún resultado, lo que demuestra que ya no somos vulnerables a este ataque.

fixed-sp-callfixed-sp-callfixed-sp-call

La parametrización no requiere procedimientos almacenados. También puedes aprovecharla con consultas generadas dentro del código. Éste es un segmento corto en C# para conectar y ejecutar una consulta en Microsoft SQL server usando un parámetro.

Mínimo privilegio

Hasta este punto he mostrado cómo mitigar los ataques a las bases de datos. Otra capa de defensa importante minimiza el daño causado en caso de que el atacante consiga superar las otras defensas. El concepto de mínimo privilegio indica que un módulo de código, que en este caso son nuestras llamadas a la base de datos, solamente debe tener acceso a la información y a los recursos necesarios para sus propósitos.

Cuando se ejecuta un comando de la base de datos, éste corre bajo los permisos de una cuenta de usuario. Obtenemos seguridad dando a la cuenta bajo la que se ejecutan las llamadas a la base de datos solamente los permisos para llevar a cabo las acciones que normalmente necesita hacer. Si las llamadas de una base de datos solamente deben leer información de una tabla, entonces solamente hay que darle a la cuenta permisos para hacer consultas select en la tabla y no insert ni delete. Si hay tablas específicas que necesita actualizar, por ejemplo una tabla de órdenes, entonces hay que darle permisos para ejecutar consultas insert y update en esa tabla pero no en otras, por ejemplo en aquellas que contengan información de los usuarios.

La cancelación de órdenes puede gestionarse a través de una cuenta separada usada solamente en la página que lleve a cabo esa tarea. Esto volvería más difícil eliminar una orden de la tabla en otro lugar. El uso de una cuenta de base de datos separada para las funciones administrativas del sitio, con los permisos necesarios que son superiores a los que la cuenta pública usa, puede hacer mucho para evitar los daños que ocurrirían si un usuario encontrara una apertura para un ataque por inyección.

Esto no va a prevenir todos los ataques. No haría nada para evitar la devolución de resultados adicionales, como en el ejemplo anterior que muestra todo el contenido de la tabla, pero evitaría que los ataques actualizaran o eliminaran información.

Conclusión

La inyección de SQL es el ataque más peligroso, especialmente si se toma en cuenta la vulnerabilidad de los sitios web ante él y el gran potencial que tiene este tipo de ataque para causar grandes daños. Aquí he descrito los ataques por inyección SQL y he demostrado el daño que uno de ellos puede hacer. Afortunadamente no es tan difícil proteger tus proyectos web de esta vulnerabilidad siguiendo algunas reglas simples.

Nunca confíes en ningún tipo de información externa que haya ingresado a tu aplicación. Los datos deben ser validados usando una lista blanca de entradas válidas antes de seguir procesándolos. Esto puede involucrar la confirmación de que un parámetro entero verdaderamente sea un entero o que una fecha tenga un valor de tipo fecha válido. También puede ser necesaria la validación de textos para que solamente incluyan los caracteres que el parámetro necesitaría. Para una búsqueda de texto a menudo puedes permitir únicamente letras y números y excluir la puntuación que pueda ser problemática, por ejemplo el signo igual o un punto y coma.

Usa la parametrización y evita la concatenación de cadenas al crear llamadas SQL. Los procedimientos almacenados no son una panacea, ya que también pueden ser vulnerables si se usa una concatenación de cadenas simple. La parametrización evita muchos de los problemas de la concatenación de cadenas.

El código que accede a la base de datos debe ejecutarse con los privilegios mínimos requeridos para completar las tareas necesarias. Bajo algunas circunstancias las llamadas a la base de datos usadas por la aplicación web necesitarán hacer cambios a la estructura de la base de datos, por ejemplo eliminando o alterando tablas. Puedes añadir una capa de protección adicional ejecutando partes separadas del sitio web usando cuentas diferentes. La cuenta de la base de datos usada para las acciones normales del usuario probablemente no tenga motivos para modificar la tabla que contiene los roles o permisos de los usuarios. Ejecutar las partes administrativas del sitio usando una cuenta con más privilegios y usar una cuenta con menos permisos para la sección del usuario final puede hacer mucho para mitigar las posibilidades de que el código que supere las barreras pueda causar otros problemas.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.