
Inyección SQL
Es hora de analizar la inyección de SQL. Durante mucho tiempo, fue el rey indiscutible del Top 10 de OWASP, hablamos de años seguidos. A pesar de su antigüedad (más de 20 años), y aunque ha bajado ligeramente del primer puesto de la lista, sigue siendo una vulnerabilidad increíblemente popular y peligrosa.
Al tratarse de una vulnerabilidad de seguridad web, la inyección de SQL (SQLi) sigue siendo una de las técnicas de «hackeo» más utilizadas por los atacantes, ya que les permite manipular una base de datos y extraer información crucial de ella. Un dato aún más alarmante es que un atacante puede convertirse en administrador del servidor de la base de datos y hacer cosas realmente devastadoras, como destruir bases de datos, manipular transacciones, revelar datos y hacerlo vulnerable a más problemas.
Echemos un vistazo rápido a cómo sucede
SQL (o lenguaje de consulta estructurado) es el lenguaje que se utiliza para comunicarse con las bases de datos relacionales; es el lenguaje de consulta que utilizan los desarrolladores, los administradores de bases de datos y las aplicaciones para gestionar las enormes cantidades de datos que se generan todos los días.
Dentro de una aplicación, existen dos contextos: uno para los datos y otro para el código. El contexto del código indica a las computadoras qué ejecutar y lo separa de los datos que se procesarán. La inyección de SQL se produce cuando un atacante introduce datos que el intérprete de SQL trata erróneamente como código, lo que le permite recopilar información valiosa de la aplicación.
Efectos de un ataque de inyección SQL
Una inyección de SQL puede ser extremadamente dañina para cualquier aplicación web y ha sido la técnica preferida detrás de tantas infracciones de alto perfil porque proporciona a los atacantes acceso no autorizado a datos críticos. Pueden ver muchísima información, desde nombres de usuario y contraseñas hasta detalles de tarjetas de crédito y números de identificación personal.
Tras obtener acceso a estos datos, los atacantes pueden apoderarse de las cuentas, restablecer las contraseñas, realizar compras en línea prolongadas o cometer otros tipos de fraude (mucho peores).
Pero quizás lo más alarmante de SQLi es que un atacante puede, si no es detectado, mantener una puerta trasera en el sistema durante largos períodos de tiempo. Como puede imaginarse, eso provocaría que se repitieran las filtraciones de datos durante el tiempo que se mantenga abierta la puerta trasera. Cosas que dan miedo.
Veamos algunos ejemplos para entender mejor cómo se ve esto en acción.
Ejemplos de SQLi
SQLi incluye varias técnicas de vulnerabilidad que pueden abordar diferentes situaciones. Los siguientes son solo algunos de los ejemplos más comunes de SQLi:
Tipos de SQLi
Bien, ahora veamos los tres tipos diferentes de SQLi.
SQLi en banda
Este es uno de los tipos de inyección SQL más comunes, simples y eficientes. En este tipo de ataque, se usa el mismo canal de comunicación para atacar y recuperar el resultado o los resultados.
Los siguientes son los dos tipos de ataques SQLi en banda:
- SQLi basado en la unión - El ataque basado en la unión utiliza el operador de unión para combinar dos o más consultas SQL, como las sentencias SELECT, para obtener la información deseada y dar como resultado una respuesta HTTP GET.
- SQLi basado en errores - El atacante utiliza los mensajes de error de la base de datos para entender su estructura. En este ataque, el atacante puede enviar solicitudes falsas o realizar acciones para que el servidor muestre mensajes de error y pueda recibir información de la base de datos. Por eso es importante que los desarrolladores eviten enviar errores o mensajes de registro en el entorno en vivo; en su lugar, deben almacenarse con acceso restringido.
SQLi inferencial
Los ataques SQLi inferenciales o ciegos son más complicados y su explotación puede tardar más tiempo. Además de eso, el atacante no obtiene los resultados del ataque de inmediato, lo que lo convierte en un ataque a ciegas.
El atacante envía las cargas útiles mediante solicitudes HTTP al servidor de la base de datos para reestructurar la base de datos del usuario y, a continuación, observa la respuesta y el comportamiento de la aplicación para ver si el ataque tuvo éxito o no.
Estos son dos tipos de ataque inferencial de SQLi:
- Blind SQLi basado en booleanos - En este ataque, se envía una consulta a la base de datos para obtener el resultado booleano (verdadero o falso) y el atacante observa la respuesta HTTP para predecir el resultado booleano.
- SQLi ciego basado en el tiempo - En este ataque, el atacante envía una consulta a la base de datos para que espere unos segundos antes de enviar la respuesta, y juzga los resultados de la consulta en función del tiempo de respuesta de la solicitud HTTP.
SQLi fuera de banda
Este es un tipo de ataque SQLi más raro que depende de las funciones habilitadas del servidor de base de datos. Ocurre en casos en los que el atacante no puede utilizar realmente los otros tipos de ataque.
Por ejemplo, si no pueden usar el mismo canal de comunicación para el ataque dentro de banda o si la respuesta HTTP no es lo suficientemente clara como para poder calcular los resultados de la consulta.
Además, no es tan común debido a que depende en gran medida de la capacidad del servidor de la base de datos para realizar solicitudes HTTP o DNS para enviar los datos necesarios al atacante.
Cómo defenderse de SQLi
Afortunadamente, el lado positivo de que la inyección de SQL sea tan antigua y tan común es que hay formas de evitar que suceda. El uso de este tipo de técnicas de prevención no solo es un buen hábito de programación, sino que también reforzará la seguridad de una organización frente a SQLi.
Hay varias formas de proteger los servidores de bases de datos de este tipo de ataques, como la validación de entradas, el uso de un firewall de aplicaciones web (WAF), la protección de las bases de datos, el empleo de equipos o sistemas de seguridad de terceros y la escritura de consultas SQL infalibles.
Veamos un ejemplo de cómo prevenir las inyecciones de SQL en Python mediante el empleo de una de las medidas de seguridad mencionadas anteriormente.
Ejemplo de Python
En este ejemplo, el atacante utilizará una inyección SQL ciega basada en booleanos para obtener información importante del sistema.
Python: vulnerable
Supongamos que hay una tabla llamada «sample_data» en la base de datos. Esta tabla almacena los nombres de usuario y las contraseñas de los usuarios de la aplicación.
Ahora permita al usuario buscar un valor en esta tabla de base de datos mediante los siguientes comandos:
importar mysql.connector
db = mysql.connector.connect
Práctica #Bad. ¡Evita esto! Esto es solo para aprender.
(host="localhost», user="newuser», passwd="pass», db="sample»)
cur = db.cursor ()
name = raw_input ('Ingresar nombre: ')
cur.execute («SELECT * FROM sample_data WHERE Name = '%s';»% name) para la fila de cur.fetchall (): print (row)
db.cerrar ()
Inyección SQL
Aquí, si el usuario introduce un nombre en la búsqueda, por ejemplo, Alicia, no habrá ningún problema con el resultado.
Sin embargo, si el usuario introduce algo como 'Alicia'; DROP TABLE sample_data; afectará significativamente a la base de datos.
Python: Remediación
La sentencia SQL debe cambiarse por la siguiente para evitar que se produzca el ataque:
cur.execute («SELECCIONE * DE sample_data DONDE Nombre = %s;», (nombre,))
Ahora, el sistema tratará la entrada del usuario como una cadena, incluso si el usuario intenta insertar consultas SQL en ella, y tratará la entrada del usuario únicamente como el valor del nombre.
Este sencillo cambio puede evitar la actividad malintencionada en futuras consultas y proteger el sistema de los ataques de entrada de los usuarios.
Ejemplo de Java
Para este ejemplo, también usaremos una tabla de base de datos denominada «sample_data» que almacena los datos de usuario de la aplicación.
Una página de inicio de sesión básica toma un nombre de usuario y una contraseña y el archivo java, que es un servlet (LogInServlet), los valida en la base de datos para permitir la operación de inicio de sesión.
Java: ejemplo vulnerable
Al utilizar la tabla «sample_data» de la base de datos, el sistema permite a los usuarios realizar operaciones de inicio de sesión tomando sus credenciales como entrada.
Hay una consulta en el archivo LogInServlet para dar cabida a la operación de inicio de sesión, que es:
//Mal ejemplo. No utilice la concatenación de cadenas.
String query = «seleccione * de sample_data donde username='» + username + «'y password ='» + password + «'»;
Conexión conn = nula;
Sentencia stmt = nulo;
prueba {
conn = DriverManager.getConnection («jdbc:mysql: //127.0.0. 1:3306 /user», «raíz», «raíz»);
stmt = conn.createStatement ();
ResultSet rs = stmt.ExecuteQuery (consulta);
si (rs.next ()) {
//Inicio de sesión exitoso si se encuentra una coincidencia
éxito = verdadero;
}
} catch (Excepción e) {
por ejemplo, printStackTrace ();
} finalmente {
prueba {
stmt.close ();
conn.close ();
} catch (Excepción e) {}
}
si (éxito) {
response.sendRedirect (» home.html «);
} otra cosa {
response.sendRedirect (» login.html? error = 1 pulgada);
}
}
A continuación se presenta la consulta para el inicio de sesión del usuario:
seleccione * de sample_data donde username='username' y password ='password'
Inyección SQL
El sistema funcionará perfectamente si la entrada es válida. Por ejemplo, diremos que el nombre de usuario es Alicia nuevamente y que la contraseña es secreta.
El sistema devolverá los datos del usuario con estas credenciales. Sin embargo, un atacante puede manipular la solicitud del usuario utilizando Postman y cURL para la inyección de SQL.
Por ejemplo, el hacker puede enviar un nombre de usuario ficticio (Alicia) y la contraseña «or» = «1».
En este caso, el nombre de usuario y la contraseña no coincidirán, pero la condición '1'=' 1' siempre será verdadera, por lo que la operación de inicio de sesión se realizará correctamente.
Java: Prevención
Para la prevención, necesitamos modificar el código LogInvalidation y usar PreparedStatement en lugar de Statement para la ejecución de consultas. Este cambio evitará la concatenación del nombre de usuario y la contraseña en la consulta y los tratará como datos de configuración para evitar la inyección de SQL.
A continuación se muestra el código modificado para LogInvalidation:
Consulta de cadena = «seleccione * de sample_data donde username=? y contraseña =?» ;
Conexión conn = nula;
PreparedStatement stmt = nulo;
prueba {
conn = DriverManager.getConnection («jdbc:mysql: //127.0.0. 1:3306 /user», «raíz», «raíz»);
stmt = conn.prepareStatement (consulta);
stmt.setString (1, nombre de usuario);
stmt.setString (2, contraseña);
ResultSet rs = stmt.executeQuery ();
si (rs.next ()) {
éxito = verdadero;
}
rs.close ();
} catch (Excepción e) {
por ejemplo, printStackTrace ();
} finalmente {
prueba {
stmt.close ();
conn.close ();
} catch (Excepción e) {
}
}
En este caso, PreparedStatement, los setters y la API JDBC subyacente se encargarán de la entrada del usuario y evitarán la inyección de SQL.

Ejemplos
Ahora veremos algunos ejemplos más en varios idiomas para entender mejor cómo se ve esto en acción.
C# - Inseguro
Este ejemplo es inseguro debido al uso de `FromRawSql`. Este método no enlaza los parámetros ni intenta escapar de ellos. Por lo tanto, este método debe evitarse a toda costa.
var blogs = contexto.Publicaciones
.fromRawSQL («SELECCIONA * DE LAS PUBLICACIONES EN LAS QUE ESTADO = {0} Y autor = {1}», estado, autor)
.toList ();
C# - Seguro
Este ejemplo es seguro gracias al `FromSqlInterpolated`, que toma los valores interpolados y los parametriza.
Si bien esto es generalmente seguro, corre el riesgo de ser muy similar a `FromRawSql`, que no es seguro.
var blogs = contexto.Publicaciones
.fromSqlInterpolated ($"SELECT * FROM POSTS WHERE state = {state} AND author = {author}»)
.toList ();
Java - Secure: Hibernate - Consulta con nombre + Consulta nativa
Hibernate ofrece dos métodos para construir consultas de forma segura a través de su `Consulta nativa` y su `Consulta con nombre`. Ambos permiten especificar ubicaciones para los parámetros.
@NamedNativeQuery (
nombre = «find_post_by_state_and_author»,
consulta =
«SELECCIONAR *" +
«DE Post» +
«WHERE state =:state» +
«Y autor =:autor»,
Clase de resultado = Post.class)
java
Listar <Post>publicaciones = session.createNativeQuery (
«SELECCIONAR *" +
«DE Post» +
«WHERE state =:state» +
«Y autor =:autor»)
.addEntity (Post.class)
.setParameter («estado», estado)
.setParameter («autor», autor)
.lista ();
Java: seguro: jplq
Al anotar un atributo `Query` en una interfaz de repositorio jplq, pueden adoptar múltiples formas y se parametrizan.
@Query («SELECCIONA p DE LA PUBLICACIÓN p DONDE u.state =? 1 y u.author =? 2 pulgadas)
Publicar Buscar publicación por estado y autor (estado de cadena, autor int);
@Query («SELECCIONA p DE LA PUBLICACIÓN P DONDE u.state =:state y u.author =:author»)
User findPostByStateAndAuthor (@Param («state») String state, @Param («author») int author);
Javascript - Seguro: pg
Cuando se usa la biblioteca `pg`, el método `query` permite la parametrización al proporcionar valores de parámetros a través de su segundo parámetro.
const {posts} = await db.query ('SELECCIONA * DE LA PUBLICACIÓN DONDE estado = $1 Y autor = $2', [estado, autor])
Javascript - Seguro: Sequelize
La biblioteca `sequelize` proporciona una forma de parametrizar una consulta mediante su segundo argumento, que toma la configuración de la consulta. Esto incluye una lista de valores que se pueden vincular a la consulta como parámetro, ya sea por nombre o índice.