英雄背景无分隔线
Pautas

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:

技术 说明
检索隐藏数据 利用这种技术,攻击者可以修改任何 SQL 查询,从数据库中收集更多信息。
数据检查 攻击者可以提取有关数据库版本和结构的信息,这有助于他们利用更多信息。这种技术可能因数据库而异。
工会攻击 攻击者可以提取有关数据库版本和结构的信息,这有助于他们利用更多信息。这种技术可能因数据库而异。
盲 SQLi 利用 Blind SQLi,攻击者可以在数据库中实现查询。但问题是,攻击者控制着这个查询,而且它不会在应用程序的响应中返回任何结果。
颠覆应用逻辑 攻击者会干扰或操纵查询,从而破坏应用程序的逻辑。要篡改查询,攻击者可以将 SQL 注释序列"--"和 WHERE 子句结合起来。

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:

import mysql.connector
db = mysql.connector.connect(
)实践 #Bad. 避免这样做!这仅用于学习。
(host="localhost", user="newuser", passwd="pass", db="sample")
cur = db.cursor()
name = raw_input('输入姓名: ')
cur.execute("SELECT * FROM sample_data WHERE Name = '%s';" % name) for row in cur.fetchall(): print(row)
db.close()

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'

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.

espera a sequelize.query (
'SELECCIONAR* DE LA PUBLICACIÓN DONDE estado = $estado Y autor = $autor',
{
bind: {estado: estado, autor: autor},
tipo: QueryTypes.select
}
);
espera a sequelize.query (
«SELECCIONA * DE LA PUBLICACIÓN EN LA QUE el estado = 1$ Y el autor = 2$»,
{
encuadernar: [estado, autor],
tipo: QueryTypes.select
}
);