zlacker

[return to "RCE Vulnerability in React and Next.js"]
1. coffee+ny[view] [source] 2025-12-03 18:34:31
>>rayhaa+(OP)
This vulnerability is basically the worst-case version of what people have been warning about since RSC/server actions were introduced.

The server was deserializing untrusted input from the client directly into module+export name lookups, and then invoking whatever the client asked for (without verifying that metadata.name was an own property).

    return moduleExports[metadata.name]

We can patch hasOwnProperty and tighten the deserializer, but there is deeper issue. React never really acknowledged that it was building an RPC layer. If you look at actual RPC frameworks like gPRC or even old school SOAP, they all start with schemas, explicit service definitions and a bunch of tooling to prevent boundary confusion. React went the opposite way: the API surface is whatever your bundler can see, and the endpoint is whatever the client asks for.

My guess is this won't be the last time we see security fallout from that design choice. Not because React is sloppy, but because it’s trying to solve a problem category that traditionally requires explicitness, not magic.

◧◩
2. j45+oC[view] [source] 2025-12-03 18:53:13
>>coffee+ny
For the layperson, does this mean this approach and everything that doesn't use it is not secure?

Building a private, out of date repo doesn't seem great either.

◧◩◪
3. coffee+uD[view] [source] 2025-12-03 18:58:36
>>j45+oC
Not quite. This isn’t saying React or Next.js are fundamentally insecure in general.

The problem is this specific "call whatever server code the client asks" pattern. Traditional APIs with defined endpoints don’t have that issue.

◧◩◪◨
4. koakum+H41[view] [source] 2025-12-03 21:10:06
>>coffee+uD
You mean call whatever server action the client asks? I don't think having this vulnerability was intentional.
◧◩◪◨⬒
5. lionko+gQ2[view] [source] 2025-12-04 12:35:33
>>koakum+H41
This is only really fine as long as you have extremely clearly, well defined actions. You need to verify that the request is sane, well-formed, and makes sense for the current context, at the very least.
◧◩◪◨⬒⬓
6. koakum+pi3[view] [source] 2025-12-04 15:27:41
>>lionko+gQ2
You would probably need to do the same if you were writing back-end in Go or something. I don't see how that is conceptually different.
◧◩◪◨⬒⬓⬔
7. amluto+Ai6[view] [source] 2025-12-05 12:13:31
>>koakum+pi3
As I understand it, RSC is locating the code to run by name, where the name is supplied by the client.

JS/Node can do this via import() or require().

C, C++, Go, etc can dynamically load plugins, and I would hope that people are careful when doing this when client-supplied data. There is a long history of vulnerabilities when dlopen and dlfcn are used unwisely, and Windows’s LoadLibrary has historical design errors that made it almost impossible to use safely.

Java finds code by name when deserializing objects, and Android has been pwned over and over as a result. Apple did the same thing in ObjC with similar results.

The moral is simple: NEVER use a language’s native module loader to load a module or call a function when the module name or function name comes from an untrusted source, regardless of how well you think you’ve sanitized it. ALWAYS use an explicit configuration that maps client inputs to code that it is permissible to load and call. The actual thing that is dynamically loaded should be a string literal or similar.

I have a boring Python server I’ve maintained for years. It routes requests to modules, and the core is an extremely boring map from route name to the module that gets loaded and the function that gets called.

[go to top]