Loading video player...
The worst case scenario for React server
components just happened. There is an
unauthenticated remote code execution
vulnerability in React server
components. We recommend upgrading
immediately. Yeah, this is really bad.
Like CVSS level of 10 bad. Now, luckily,
it has been caught and fixed. Just make
sure you upgrade React immediately. And
hosting providers like FEL and
Cloudflare claim they've patched it on
their end with the WAF rule, but have
seen some people say there are ways
around that. So, please just upgrade. I
mean AWS have even reported that they've
seen cyber threat groups rapidly trying
to exploit this. And that's no surprise.
This is remote code execution on
basically any site that uses the Next.js
server or the React server package. And
you don't even need to be using server
actions to be vulnerable to this
exploit. So let me show you the
vulnerability in action. First, I'm
going to create a next app using version
16.06 since that's the latest version
that is vulnerable. And I'm simply going
to go through all of the normal
defaults. Then all I've done with the
boiler plate it's given us is simply
build it for production and start the
server. And we can see it's up and
running here. Next, I'm going to store a
secret text file on my Mac's desktop
since my Mac is acting as the server
here. And finally, I'll send a post
request to my Next.js application. The
problem though is that post request was
no ordinary post request. As you can see
here, it's been able to execute some
code on the Next.js server. And in my
case, I simply made it echo you have
been pawned and then cap the contents of
that secret file. In my case, we were
simply seeing this in the terminal
output, but since you have remote code
execution, you can do anything the
server could. So, you could extract that
data via a curl request or just
basically run anything that you want.
This is remote code execution and you
can see how this is really bad. Now,
there's actually two CVEs related to
this issue. The first one is for the
React server package and you can see
this has that score of 10 and the second
one is simply for the NexJS server since
that uses React server as a dependency
and they decided to have another CV for
that one as well. And credit where
credit's due, this vulnerability called
React to Shell was discovered by Lachlan
Davidson, and he has a great write up
here. He's also published the original
proof of concepts for this vulnerability
on his GitHub. And there's also a quick
TLDDR of how this works. For a more
detailed explanation, I found this proof
of concept on GitHub. So, let me take
you through this and go over the steps
to see how a post request sent to the
NextJS server can then run code. The
root cause of the issue comes from how
the server and the client talk to each
other. Instead of just using JSON to
handle complex structures like
components themselves, server components
use a custom serializer called the React
Flight Protocol. This lets the server
and the client send each other chunks of
data via form data. To show this in
action, I've set up a server action here
called process payload, and it simply
just logs out the data that it receives.
Now, if I send that server action, those
three chunks that we were just looking
at, when it actually reaches the server
action, you can see it's been
deserialized into a very simple object.
So what's happening with the react
flight protocol des serializer before it
gets to that server action is it
receives this chunk data and this dollar
notation is simply a reference to
another chunk. So chunk zero here is
simply an array with dollar one. So
that's saying just go to chunk one and
use that value. And then in chunk one
for the value of name here we then have
dollar two which says go to chunk two
but we also have path traversal. So it
says go to path two but then this colon
notation here says get us the value of
fruit name. So this is why we get the
value of cherry. And the final object
down here doesn't have this fruit name
property since we simply wanted chunk
one and chunk one just extracted that
information directly using path
traversal. The logic of this path
traversal functionality is where the
exploit takes place. Essentially what
researchers noticed if they sent a
request where the path traversal wanted
to access a property that didn't exist.
So in this case, the object is simply
empty, but we're saying go to chunk one
and access that property of A. It would
return a 500 server error saying it
can't read the properties of undefined
when it was trying to read A. This means
that there was no check for whether that
requested key is actually set on the
object before it tries to access it.
Without that check in place, they
realized that what they could do is send
a path traversal where it tries to
access the object's prototype. The
reason accessing the object's prototype
worked is because the path traversal
code in React Server looked something
like this. Up here we have our path and
we're splitting based on the colon. Then
when we actually start to traverse that
path, you can see down here where my
path is set to underscore proto, all
we're doing is passing it into the value
which in this case is the object which
will be chunk one. But then we're just
simply providing it with the raw path.
There is no check for whether this path
actually existed on that object. This
means that if we're simply doing it like
this, we can start to access the
JavaScript internals like that prototype
which every object in JavaScript has.
for our path. Then imagine this dollar
one up here was simply this object that
we have. We are then accessing its
prototype. We see in the console log
here that that returns the actual
prototype of the object. And we'll come
back to why that's an issue in a bit.
But first I want to show you what they
did to fix this as it was very simple.
Essentially all they did is before you
access the value at path, they add in an
if statement which uses the has own
property function. And if it turns out
that the object doesn't have that path
as an own property, it will simply throw
an error. We can see that down here when
it tries to traverse that malicious path
that we had earlier. This time it's just
saying invalid reference as proto is not
an own property. This function is only
looking for properties on the object
itself and not the internals of
JavaScript. If we go back to the
vulnerable code though, what is the
issue with having the objects prototype?
Well, the problem is if we traverse up
from our object to the constructor. This
is the object constructor. We can then
traverse up to the function constructor.
The reason the function constructor is
bad is because it lets you create
functions from a string. So we can see
down here I set test equal to the
function constructor. Then where we have
evil here this is set to new test and in
here I simply pass in the code that I
want to run. So now if we simply run
evil you can see using that constructor
we've been able to access using that
path traversal logic I can now run this
code. So essentially in the full chunk
vulnerability that we'll see later this
is the piece where I want to add in the
remote code I want to execute on the
server. But we're not completely done
yet. The problem is we do have this
function constructor, but we actually
have no way of calling it. So if I
remove this call here in my example, you
can see this function simply isn't going
to run. So they needed to find a way to
get the function that they're creating
to actually run on the React server. The
simple explanation of how they did this
is they took advantage of how JavaScript
handles promises. When you await
something in JavaScript, it simply
checks whether it has a then method and
if it does, it will simply run that
automatically. So you can see my demo
object has then and that is the run this
function. And if we await demo, we then
actually run the function which is hello
world. Now there's a few more technical
pieces, but to summarize, using the
tricks that we just looked at, you can
craft a fake chunk object that looks
like a real one. But crucially, we have
this then value here, which points to
our prototype traversal. The final piece
is then this dollar B reference in the
value here. This dollar B is actually
the reference for blobs. And resolving
it simply calls form data.get. But what
we've done is we've set up our fake
chunk. So down here, form data.get get
actually points to our function
constructor using that prototype
traversal and underscore prefix here
contains any malicious code that we want
to run on the server. In my case, this
is the chunk that I used earlier. So
this one's simply console logs you have
been pawned and then since we're on the
server, we can say process.odu.require.
We can get the child process do exec
sync. Then we can run whatever terminal
command we want. So in this case, it was
simply catting the contents of that
file. There we go. That is remote code
execution. There we go. Hopefully this
has given you a good insight into how
the vulnerability worked. It was quite a
technical heavy one, but it's also quite
simplistic in how you run it. Has this
made you think twice about server
components? And maybe you think they're
a mistake now? Let me know in the
comments down below. While you're there,
subscribe. And as always, see you in the
next one.
A massive React + Next.js security flaw was just exposed and it’s as bad as it gets. An unauthenticated remote code execution (RCE) vulnerability hit React Server Components, letting attackers run anything on your server with a single request. 🔗 Relevant Links CVE: https://github.com/advisories/GHSA-fv66-9v8q-g76r React2Shell: https://react2shell.com/ Original POC: https://github.com/lachlan2k/React2Shell-CVE-2025-55182-original-poc/tree/main Detailed POC: https://github.com/msanft/CVE-2025-55182 ❤️ More about us Radically better observability stack: https://betterstack.com/ Written tutorials: https://betterstack.com/community/ Example projects: https://github.com/BetterStackHQ 📱 Socials Twitter: https://twitter.com/betterstackhq Instagram: https://www.instagram.com/betterstackhq/ TikTok: https://www.tiktok.com/@betterstack LinkedIn: https://www.linkedin.com/company/betterstack