A menudo, los sitios web parecen existir principalmente para colocar algo en una base de datos con el fin de sacarlos más tarde. Mientras que otros métodos de base de datos, como NoSQL, han ganado popularidad en los últimos años, los datos de muchos sitios web aún residen en la base de datos SQL tradicional. Estos datos a menudo consisten en información personal valiosa, como números de tarjetas de crédito y otra información personal de interés para los ladrones de identidad y delincuentes. Los hackers por lo tanto siempre buscan obtener estos datos. Uno de los objetivos más comunes de estos ataques es la base de datos SQL que se encuentra detrás de muchas aplicaciones web a través de un proceso de inyección de SQL..
Un ataque de inyección funciona al hacer que la aplicación pase información no confiable al intérprete. Recientemente, 40,000 registros de clientes tomados de Bell Canada son el resultado de un ataque de inyección de SQL. A fines de 2013, los piratas informáticos robaron más de $ 100,000 de un ISP con base en California usando inyección SQL.
El Proyecto de seguridad de aplicaciones web abiertas (OWASP) eligió el ataque de inyección como el riesgo de seguridad de la aplicación número uno en su top ten de 2013, según su prevalencia y el riesgo para los sistemas web atacados. Desafortunadamente, también ocupó el puesto número uno en el informe anterior de 2010. Entonces, ¿qué es un ataque de inyección de SQL? En este tutorial, explicaré cómo funcionan y qué puede hacer para proteger su aplicación de estos ataques..
Cualquier sistema interpretado detrás de un servidor web puede ser el objetivo de un ataque de inyección. Los objetivos más comunes son los servidores de bases de datos SQL detrás de muchos sitios web. La inyección de SQL no es el resultado directo de las debilidades en la base de datos, pero utiliza aperturas en la aplicación para permitir que el atacante ejecute declaraciones de la elección del atacante en el servidor. Un ataque de inyección SQL hace que la base de datos comparta más información de la que la aplicación está diseñada para proporcionar. Veamos una llamada de base de datos SQL que podría escribir en ASP.NET.
SqlCommand command = new SqlCommand ("SELECT * FROM userdata WHERE UserId =" + id); SqlDataReader reader = command.ExecuteReader ();
¿Qué hay de malo con este código? Quizás nada. El problema es que carné de identidad
cadena que estamos utilizando. ¿De dónde viene ese valor? Si lo estamos generando internamente o desde una fuente confiable, entonces este código podría funcionar sin problemas. Sin embargo, si obtenemos el valor de un usuario y lo utilizamos sin modificaciones, nos abrimos a la inyección SQL..
Tomemos un caso común donde obtenemos el parámetro para buscarlo como parte de la URL. Toma la URL http://www.example.com/user/details?id=123
. En ASP.NET usando C # podemos obtener ese valor pasado usando este código:
string id = Request.QueryString ["id"];
Este código combinado con la llamada anterior nos deja abiertos a un ataque. Si el usuario pasa un ID de usuario como 123 como se esperaba, todo funciona bien. Sin embargo, nada de lo que hemos hecho aquí garantiza que este sea el caso. Digamos que el atacante intenta acceder a la URL. http://www.example.com/user/details?id=0; SELECT * FROM userdata
.
En lugar de simplemente pasar un valor como se esperaba, le dimos un valor y luego agregamos un punto y coma, que termina una declaración SQL. Luego agrega una segunda declaración SQL que el atacante desea ejecutar. En este caso, devolvería todos los registros en la tabla de datos de usuario. Dependiendo del resto del código en nuestra página, puede devolver un error o tal vez mostrar cada registro en la base de datos al atacante. Incluso un error puede usarse con consultas cuidadosamente construidas para construir una vista de la base de datos. Peor aún, imagina que el atacante va a una URL de http://www.example.com/user/details?id=0; DROP TABLE userdata
. Ahora todos tus usuarios están perdidos.
En este ejemplo, el atacante puede ejecutar cualquier código que desee en el servidor de la base de datos. Si la cuenta a la que se ejecutan las llamadas de la base de datos tiene el control total de la base de datos, un escenario demasiado común, el hecho de eliminar tablas y eliminar registros hace que sea una forma fácil de desactivar un sitio. Incluso si el atacante solo puede leer y escribir datos en la base de datos, el procesamiento de datos es solo una cuestión de paciencia y cuidado..
Tome una base de datos simple llamada "Productos" que consta de tres columnas. La primera columna contiene la identificación del producto, la segunda contiene el nombre del producto y la tercera contiene el precio del producto. Para nuestra consulta normal, intentaremos encontrar cada producto que contenga un widget en el nombre. El SQL para hacer esto en el mismo patrón que hemos mostrado se vería así:
string sql = "SELECT * FROM Products WHERE productname LIKE '%" + searchterm + "%'";
Un sitio web típico para acceder a este 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..
| productid | productname | precio | | ----------- | 1 | Widget | 100.00 |
Cambiemos nuestra consulta a http://www.example.com/product?search=widget 'OR 1 = 1;--
y ejecutar la misma consulta. ver algo mas De hecho veremos cada registro en la tabla..
| productid | nombre de producto | precio | | --- | 1 | Widget | 100.00 | | 2 | Thingy | 50.00 | | 3 | Boxy | 125.00 |
Hemos engañado al intérprete para que ejecute el código SQL de nuestra elección. El SQL ejecutado resultante será:
SELECCIONAR * DE PRODUCTOS DONDE el nombre de producto LIKE '% widget' O 1 = 1; -% '
El resultado serán dos afirmaciones:
SELECCIONAR * DE PRODUCTOS DONDE nombre de producto ME GUSTA '% widget' O 1 = 1; -% '
Añadiendo el 1 = 1
, lo que siempre es verdadero, la cláusula where será verdadera para cada fila en la tabla y la consulta resultante devolverá cada fila. los --
al comienzo de la segunda declaración, el resto de la declaración SQL se convierte en un comentario que evita el mensaje de error que, de lo contrario, veríamos.
Los cambios en la pantalla no pueden evitar esta declaración. Un atacante paciente no puede usar nada más que el hecho de que se devuelva o no un valor y consultas cuidadosamente diseñadas para mapear lentamente su base de datos y posiblemente recuperar datos incluso si solo ven un mensaje de error. Existen herramientas como sqlmap para automatizar el proceso..
Evita la inyección de SQL al evitar que la entrada no confiable llegue a la base de datos de SQL u otro intérprete. Cualquier entrada desde fuera del sistema debe considerarse no confiable. Incluso los datos de otros sistemas asociados deben considerarse no confiables, ya que no tiene forma de garantizar que el otro sistema no sufra problemas de seguridad que permitan la inserción de datos arbitrarios que luego pasan a su aplicación..
Volviendo a nuestro ejemplo anterior, si sabemos que el parámetro id siempre debe ser un entero, podemos intentar convertirlo en un entero y mostrar un error si esto falla. Una aplicación MVC de ASP.NET haría esto usando un código similar a este:
cantidad int if (! int.TryParse (Request.QueryString ["qty"], fuera de la cantidad)) return RedirectToAction ("Invalid");
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 no válido.
Esto se puede evitar si estamos buscando un parámetro entero. No ayudaría si estamos esperando un texto como en la búsqueda de productos anterior. La técnica preferida en este caso es usar expresiones regulares o reemplazos de cadenas para permitir solo los caracteres necesarios en el valor pasado.
Esto se puede hacer mediante la inclusión en la lista blanca, el proceso de eliminar cualquier carácter que no sea un conjunto específico, o la inclusión en la lista negra, el proceso de eliminar un miembro de un conjunto específico de la cadena. La lista blanca es más confiable ya que solo se especifican los caracteres permitidos. Para eliminar cualquier cosa que no sea letras y números en una cadena, podemos usar código como:
Regex regEx = new Regex ("[^ a-zA-Z0-9 -]"); string filterString = regEx (originalString, "");
Tendrá que evaluar cualquier entrada respectivamente. Algunas consultas de bases de datos pueden necesitar atención más especializada. Tome los caracteres que pueden ser significativos en un comando SQL, pero también podría ser un carácter válido en una llamada de base de datos. Por ejemplo, el carácter de comillas simples 'se usa para iniciar y finalizar una cadena en SQL, pero también podría ser parte del nombre de una persona como O'Conner. En este caso sustituyendo la comilla simple. '
con comillas simples consecutivas "
puede eliminar el problema.
Los procedimientos almacenados a menudo son vistos como la solución para este problema y pueden ser parte de la solución. Sin embargo, un procedimiento almacenado mal escrito no lo salvará. Tome este procedimiento almacenado que incluye código similar a lo que ya hemos visto para crear una consulta:
ALTER PROCEDURE [dbo]. [SearchProducts] @searchterm VARCHAR (50) = "COMO COMENZO A DECLARAR @query VARCHAR (100) SET @query = 'SELECT * FROM Products DONDE productname LIKE"%' + @searchterm + '% "; EXEC (@query) FIN
La concatenación de cadenas aquí es el problema. Intentemos un intento simple en el que intentemos establecer una condición siempre verdadera para mostrar todas las filas de la tabla y pasar el mismo 'widget' O 1 = 1; "como la consulta que vimos anteriormente. El resultado también es el mismo :
| productid | nombre de producto | precio | | - | 1 | Widget | 100.00 | | 2 | Thingy | 50.00 | | 3 | Boxy | 125.00 |
Si se pasan datos que no son de confianza, tenemos el mismo resultado que si la llamada se creara en nuestro código. Que la concatenación de cadenas tenga lugar dentro de un procedimiento almacenado en lugar de hacerlo en nuestro código no proporciona protección.
La siguiente pieza del rompecabezas para protegerse de los ataques de inyección viene usando la parametrización. La creación de consultas SQL mediante la concatenación de cadenas y luego el paso del código terminado no le da a la base de datos una idea de qué parte de la cadena es un parámetro y qué es parte del comando. Podemos ayudar a proteger contra ataques creando llamadas SQL de una manera que mantenga las declaraciones y los valores distintos..
Podemos reescribir el procedimiento almacenado que se muestra anteriormente para usar parámetros y generar una llamada más segura. En lugar de concatenar el %
Para representar caracteres comodín, crearemos una nueva cadena agregando estos caracteres y luego pasaremos esta nueva cadena como un parámetro a la declaración SQL. El nuevo procedimiento almacenado se ve así:
ALTER PROCEDURE [dbo]. [SearchProductsFixed] @searchterm NVARCHAR (50) = "COMO COMENZO A DECLARAR @query NVARCHAR (100) DECLARAR @msearch NVARCHAR (55) SET @msearch = '%' + @searchterm + '%' SET @query = 'SELECCIONAR * DE PRODUCTOS DONDE productname LIKE @search' EXEC sp_executesql @query, N '@ search VARCHAR (55)', @msearch END
La ejecución de este procedimiento almacenado con solo la palabra widget funciona como se espera.
| productid | nombre de producto | precio | ---- | 1 | Widget | 100.00
Y si pasamos con nuestro widget de parámetro "OR 1 = 1; - no se devuelve nada, lo que demuestra que ya no somos vulnerables a este ataque.
La parametrización no requiere procedimientos almacenados. También puede aprovecharla con consultas creadas dentro del código. Aquí hay un segmento corto en C # para conectar y ejecutar una consulta contra el servidor Microsoft SQL usando un parámetro.
const string sql = "SELECT * FROM Products WHERE productname LIKE @CategoryID"; var connString = WebConfigurationManager.ConnectionStrings ["ProductDatabase"]. ConnectionString; utilizando (var conn = new SqlConnection (connString)) var command = new SqlCommand (sql, conn); command.Parameters.Add ("@ searchterm", SqlDbType.NVarChar) .Value = string.Format ("% 0%", searchTerm); command.Connection.Open (); SqlDataReader reader = command.ExecuteReader (); while (reader.NextResult ()) // El código para ejecutar en cada línea va aquí
Hasta este punto, he mostrado cómo mitigar los ataques a la base de datos. Otra capa importante de defensa, minimiza los daños causados en caso de que el atacante supere las otras defensas. El concepto de privilegio mínimo otorga a un módulo de código que, en este caso, las llamadas a nuestra base de datos, solo deben tener acceso a la información y los recursos necesarios para sus fines..
Cuando se ejecuta un comando de base de datos, lo hace bajo los derechos de una cuenta de usuario. Obtenemos seguridad al darle a la cuenta las llamadas a la base de datos que se ejecutan solo bajo los derechos para hacer las cosas que normalmente necesita hacer. Si las llamadas desde una base de datos solo deben estar leyendo datos de una tabla, entonces otorgue a la cuenta derechos de selección sobre la tabla y no inserte ni elimine. Si hay tablas específicas que necesita actualizar, digamos una tabla de pedidos, luego insértela y actualícela en esa tabla, pero no en otra tabla como la que contiene información del usuario..
Las órdenes de cancelación se pueden manejar a través de una cuenta separada que se usa solo en la página que realiza esa tarea. Esto evitaría la eliminación de un pedido de la tabla en otro lugar más difícil. El uso de una cuenta de base de datos separada para las funciones administrativas del sitio, con los mayores derechos necesarios que los que el público usa, puede hacer mucho para evitar que un usuario encuentre un ataque de inyección abierta..
Esto no evitará todos los ataques. No haría nada para evitar la devolución de resultados adicionales, como el ejemplo anterior que muestra el contenido completo de una tabla. Evitaría que los ataques actualicen o borren datos..
La inyección de SQL es el ataque más peligroso, especialmente cuando se tiene en cuenta lo vulnerables que son los sitios web y el potencial que este tipo de ataque puede causar mucho daño. Aquí describí los ataques de inyección SQL y demostré el daño que uno puede hacer. Afortunadamente, no es tan difícil proteger sus proyectos web de esta vulnerabilidad al seguir algunas reglas simples.
Nunca confíe en ningún dato externo incorporado a su aplicación. Debe validarse contra una lista blanca de entradas válidas antes de seguir procesándose. Esto puede significar asegurarse de que un parámetro entero sea realmente un entero o una fecha sea un valor de fecha válido. También valide el texto para incluir solo los caracteres que necesitaría el parámetro. Para una búsqueda de texto a menudo se pueden permitir solo letras y números y filtrar la puntuación que podría ser problemática, como el signo igual o un punto y coma.
Utilice la parametrización y evite 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 utiliza una concatenación de cadena simple. La parametrización evita muchos de los problemas de concatenación de cadenas.
El código que accede a la base de datos debe ejecutarse con los privilegios mínimos necesarios para completar las tareas necesarias. En algunas circunstancias, las llamadas a la base de datos utilizadas por la aplicación web deben realizar cambios en la estructura de la base de datos, como eliminar o modificar tablas. Puede agregar una capa adicional de protección ejecutando partes separadas del sitio web en diferentes cuentas. Es probable que la cuenta de la base de datos utilizada para las acciones normales del usuario no tenga motivos para modificar la tabla que contiene los roles o derechos de los usuarios. La ejecución de las partes administrativas del sitio con una cuenta más privilegiada y la sección del usuario final con una menos privilegiada puede hacer mucho para mitigar las posibilidades de que el código que se desliza a través de causar otros problemas.