VT-2026-126187

React Server Components / Next.js - Flight Typed-Array Deserialization RCE

CRITICAL 9.5CVErcedeserializationunauthenticatedreact

Run this lab

$vt start --id vt-2026-126187

Lab not starting or behaving wrong? Report a problem with this lab

Summary

React Server Components (the React Flight serializer in react-server-dom-webpack / -parcel / -turbopack) contain an insecure-deserialization flaw that can lead to unauthenticated remote code execution. When a response transports multiple typed arrays that are views over the same underlying ArrayBuffer and the buffer is detached between the serializer writing the length header and flushing the bytes, the client-side decoder reads a length header that no longer matches the emitted payload and over-reads into following (attacker-influenced) stream bytes. Those bytes can be parsed as forged Flight rows that reach a Server Action with attacker-chosen arguments. In Next.js this is reachable in App Router apps with Cache Components (use cache) whose cached function returns multiple typed arrays plus an attacker-controlled value. Confirmed by diffing react-server-dom-webpack 19.2.8 (vulnerable) against 19.2.9 (patched): the fix throws on a detached buffer and copies partial views before transport.

Technical detail

Root cause is in the React Flight typed-array transport (emitTypedArrayChunk / the write path in react-server-dom-webpack). The serializer computes the row length header from `typedArray.byteLength` at emit time and keeps a view over the live ArrayBuffer; the bytes are written later at flush. If two large (>2048 byte) typed arrays are views over one shared buffer and that buffer is detached in the window between emit and flush (the zero-copy transfer path), the first/second view flushes 0 bytes while its header still claims the original length. The client decoder trusts the header and over-reads the following stream bytes, which can be crafted into forged Flight rows ("6:[\"<cmd>\"]", "5:{\"id\":\"<action>\",\"bound\":\"$@6\"}", "4:{\"then\":\"$h5\"}") that invoke a Server Action with attacker-controlled bound arguments. The 19.2.8 -> 19.2.9 diff shows the fix: throw "Cannot transport a detached buffer" when `chunk.buffer.detached`, and `view.slice()` partial views (byteOffset!=0 or byteLength!=buffer.byteLength) before transport so a later detach cannot corrupt the stream. In this lab the vulnerable arrangement (two 8192-byte views over one 16384-byte ArrayBuffer returned from a `use cache` function, with the attacker value as the trailing bytes) makes the over-read fire deterministically server-side: a GET with any `annotation` value crashes the render on a vulnerable build ("Connection closed"), while a patched build renders the typed-array summary. The shipped Nuclei template detects this differential. Full command-execution weaponization (forging Flight rows through the over-read to invoke a Server Action) additionally requires matching the exact cache-stream chunk ids and resume offset of the target build; that step is not included here.

Impact

Unauthenticated remote code execution on the server in the rare configurations that meet the precondition (App Router + Cache Components returning multiple typed arrays sharing a buffer plus an attacker-controlled value). CVSS 4.0 base 9.5.

Exploitation & proof of concept

React Flight typed-array over-read via use cache (unauthenticated)

A single unauthenticated GET with an annotation query value drives the vulnerable use cache function to transport two large typed-array views that share one ArrayBuffer. On a vulnerable build the second view flushes 0 bytes while its length header still claims the full size, so the React Flight decoder over-reads and the server render fails ("Connection closed" / React error boundary) -> the typed-array summary never renders. A patched build copies the partial views (view.slice()) and renders the summary cleanly. The differential (summary absent on vulnerable, present on patched) is the detection signal.

GET/param=annotation

REQUEST
GET /?annotation=dGVzdGluZ3Rlc3Q= HTTP/1.1
Host: {{Hostname}}
RESPONSE
HTTP/1.1 200 OK
Content-Type: text/html

...React error boundary (digest); the typed-array summary (block0":8192) is absent...

Nuclei detection: vt-2026-126187.nuclei.yaml

Remediation

  • Upgrade Next.js to 16.4.1 (or later).
  • Upgrade react-server-dom-webpack/-parcel/-turbopack to 19.0.9 / 19.1.10 / 19.2.9 / 19.3.1 or later.
  • Audit `use cache` functions that return typed arrays; avoid transporting multiple views over one shared ArrayBuffer, or copy via view.slice() before returning.

Lab setup notes

  • Vulnerable lab: docker compose up -d then http://localhost:3100/ (Next.js 16.4.0 + React 19.3.0)
  • Detect: nuclei -t vt-2026-126187.nuclei.yaml -u http://localhost:3100/

References