深度解读React满分漏洞,一个前端框架怎么炸掉半个互联网【让编程再次伟大#50】
By Peter Pang
Summary
## Key takeaways - **Never Trust Frontend Data**: When developing a backend system, the most important thing is 'Don't trust anything from the frontend.' If you don't follow this basic principle, the result will be the same as React's, the most serious security vulnerability in the history of frontend frameworks. [00:15], [00:18] - **RSC Enables Arbitrary Code Execution**: Lachlan Davidson successfully made a server using RSC execute a command that wasn't actually in the code by constructing a carefully crafted JSON within an HTTP request. No matter what you write in the payload, as long as it's correct Node.js code, React will execute it, like calling child_process to run bash commands. [01:22], [01:38] - **Blob Parsing Bypasses All Checks**: React never verifies whether the AST data it uses is generated by the parser itself; you can disguise anything in the request content, and it will blindly accept it. The $B Blob case skips the constructor step and executes the initial data directly via _prefix and _formData keys. [05:50], [05:24] - **Next.js Duplicates Vulnerable React**: Next.js doesn't treat React as a dependency; it copies the entire React source code into its own codebase, so projects have two React codebases. CVE rejected Next.js's duplicate report, so automated patching might miss it—manually update Next.js. [07:59], [08:07] - **Cloudflare Crashes from Patch Haste**: To protect against the React vulnerability, Cloudflare raised proxy cache to 1MB for Next.js, but skipped WAF testing, triggering a null pointer in the FL1 Lua engine's rule summarizer. This chain reaction downed Cloudflare for half an hour, paralyzing half the internet. [10:03], [11:25]
Topics Covered
- Never Trust Frontend Data
- RSC Enables Arbitrary Code Execution
- RSC Parser Blindly Executes Payloads
- Next.js Duplicates Vulnerable React
- React Bug Crashes Cloudflare
Full Transcript
Dear viewers, please listen to the question : When developing a backend system, what is the most important thing?
A. Authentication and authorization B. Backward compatibility
B. Backward compatibility C. Caching mechanism
C. Caching mechanism D. Technical documentation
D. Technical documentation E. Error handling
E. Error handling The correct answer is F.
Don't trust anything from the frontend.
If you don't follow this basic principle , the result will be the same as React's . The most serious security vulnerability in the history of frontend frameworks
. The most serious security vulnerability in the history of frontend frameworks was released at the end of 2024.
The most important update was React Server Components (RSC), a brand-new backend rendering mechanism.
For this most popular frontend framework in the world, this was a significant milestone because it meant that, driven by Vercel, React was completely betting on the Server Side Rendering (SSR) technology roadmap.
Vercel used React to promote SSR to drive traffic to the backend , and then used the Next.js framework as a shell to drive traffic to its own cloud services.
The whole strategy was executed in one go, which can be described as a blatant conspiracy.
At the beginning of this year, Next.js had a serious vulnerability with a score of 9.1.
In my analysis of that vulnerability in "Make Programming Great Again #35", I mentioned in the final summary: "Behind these shady dealings, who knows how many more fatal vulnerabilities exist?"
We now know that on November 29, 2025 , Lachlan Davidson, a security expert from New Zealand, submitted a bug report to the React team. He
successfully made a server using RSC execute a command that wasn't actually in the code by constructing a carefully crafted JSON within an HTTP request.
If it were just using server resources to run `console.log`,
it wouldn't be a particularly serious issue, at most a 6 out of 10.
The reason this vulnerability is worth 10 is that no matter what you write in the payload (the highlighted part), as long as it's correct Node.js code, React will execute it.
For example, you could call Node.js's `child_process`. The module
executes bash commands directly on the server.
If your backend is deployed using a high-privilege account like root, it's even more exciting; you can perform a one-click sweep.
If you don't want to cause damage but just want to steal some confidential information, it's also easy.
For example, you can use the fs module to read any system file and then send the file content to your own server via the HTTPS module.
If you're too lazy to set up your own server or are afraid of being caught by the police following the URL, you can put the stolen content into an error and throw it.
The server will then send the error back with a 500 error.
If you want to be even more covert and avoid a sudden surge of errors... If an anomaly is detected in the log, you can allow the HTTP request to return normally and then insert the confidential information into the returned header.
This vulnerability is not only serious but also easily reproduced , putting the React team in a dilemma.
If they secretly release a patch, it's difficult to guarantee the patch's installation rate because people won't know there's a major issue. If they announce the vulnerability to the whole world, hackers will definitely react faster than anyone else.
Therefore, to slow down the hackers and give everyone enough time to see the news and apply the patch, on December 3, 2025, the React team only released an announcement and a patch, without explaining any technical details.
They also deliberately mixed nearly 1,000 lines of other code into the patch to make it harder for people to find the source of the vulnerability and reproduce it.
But after all, React is an open-source project, and everything is transparent.
This little trick won't last long.
Even a programmer like me with only a smattering of front-end knowledge only spent a few hours looking through the files involved in the patch and their upstream components, and I was able to basically understand the entire logical chain.
Simply put, when the React RSC module receives a front-end request, it enters a recursive... The parsing (recursive parsing) logic involves the parser generating an AST-like data structure.
A function called `parseModelString` is responsible for identifying this data structure.
As you can see, it's essentially a large switch case, determining the data type based on the first and second characters.
For example, `$Q` indicates a Map, `$W` an A Set, and `$K` a FormData.
A special case is `$@` , which refers to a Promise, an asynchronous JavaScript command.
This special data is packaged into an independent Chunk with a PENDING state, awaiting the subsequent data stream.
Once the data stream is fully transmitted, the Chunk's state is updated to RESOLVED_MODEL, indicating it's ready for execution.
Before execution , the `parseModelString` function performs another parsing , converting the binary data into the correct JavaScript structure.
For example, `$A` is an ArrayBuffer, and `$S` is an Int16Array.
Another special case is `$B`, which refers to a Blob.
Since a Blob is simply a Blob, no conversion is needed . The initial data skips the constructor step and executes directly.
. The initial data skips the constructor step and executes directly.
OK, that's the whole logic chain.
Now let's look back at the sample JSON submitted by Lachlan Davidson.
You'll understand it then. First, the payload is converted into a string, then into a buffer.
then. First, the payload is converted into a string, then into a buffer.
This way, the React parser reads something that looks exactly like the real AST data.
Here, `$@` tells you it's a Promise to be executed, and `status` equals `resolved_model` , allowing it to bypass the non-existent pending state and directly enter the execution flow.
Here, `$B` tells you it's a Blob, so you bypass the constructor step and execute directly.
Finally, through the processing code that precisely corresponds to the `$B` data, the parser uses the provided `_prefix` and `_formData` keys to treat the text in `_prefix` as the content of the Blob and executes it directly—that is, this line of `console.log()` code.
Why can such a simple JSON be successfully faked?
Because React never verifies whether the AST data it uses is generated by the parser itself, theoretically, you can disguise anything in the request content, and it will blindly accept it all.
This is what makes this vulnerability a perfect 10/10, the finishing touch to make it a flawless vulnerability.
There is no scapegoat in this incident ; the entire React core development team and the mastermind behind it, Vercel, are both culprits.
Vercel has been blurring the lines between React's front-end and back-end , not accidentally, but intentionally, because they need to make React users, those front-end development teams, more accepting of this extreme SSR approach.
But essentially, this cross-network communication is a form of Remote Procedure Call (RPC).
RPC frameworks are nothing new, and their security mechanisms have long had mature design principles.
Whether it's the early SOAP or the currently popular gRPC, they all adhere to these basic principles, such as schema design, explicit definition, and measures to prevent boundary obfuscation.
However, the people at Vercel seem to lack any professional ethics in backend development.
Looking at the entire design of RSC , you could even say they brought the "what you see is what you get" approach from the frontend to the backend.
Just like at the end of "Make Programming Great Again #35," I boldly predicted that this wouldn't be the last serious vulnerability in Next.js. Here, I dare to boldly predict again
in Next.js. Here, I dare to boldly predict again that this won't be the last serious vulnerability in React.
If you use Next.js, please listen carefully to this section.
On the same day that React reported a vulnerability (CVE-2025-55182) , Next.js also submitted a vulnerability report to CVE (CVE-2025-66478).
The report submitted by Next.js is completely identical to the one submitted by React; the entire report only describes the React vulnerability and nothing else.
Therefore, CVE rejected this vulnerability report and marked it as a duplicate.
This leads to a misunderstanding : many people mistakenly believe that "Next.js itself is not the problem in this vulnerability incident" or "If my project has both React and Next.js installed..."
"I just need to update the React patch."
Notice! This is wrong! Wrong! Wrong!
Because Next.js doesn't treat React as a dependency like other frameworks, it copies the entire React source code into its own codebase.
If your project has both React and Next.js installed, you effectively have two React codebases : one independent React and the other hidden in the Next.js bundle.
Therefore, if your team uses advanced automated vulnerability patching processes that automatically monitor and synchronize CVEs, download patches, and release updates, you might miss patching Next.js because it's rejected by CVEs.
So, remember to manually update Next.js.
Although I haven't seen any difference between the React code in Next.js and the React you're using yet , it's obvious that this is paving the way for future custom React versions.
My intuition in this area is usually quite accurate; just wait and see. Finally,
our old friend Cloudflare makes its appearance, and this time they've brought us another amazing vulnerability! 😄
Normally, as an infrastructure service provider , a vulnerability in a front-end framework like React shouldn't be their concern, but based on my experience within Cloudflare... Informants revealed that they used React intensively, but their skill level was terrible . Sure enough,
terrible . Sure enough, two days after the React vulnerability was announced, at 8 AM on December 5th, Cloudflare was down for nearly half an hour.
However, the cause of the outage wasn't an attack triggered by the discovery of the (React) vulnerability, but rather a chain reaction of incidents very characteristic of Cloudflare.
Going back to the day the React vulnerability was announced, Cloudflare immediately and proudly announced that its firewall service, WAF, had added protection against this vulnerability . All free and paid users could automatically enjoy
. All free and paid users could automatically enjoy WAF's HTTP request detection, primarily from... Their proxy cache currently has a 128KB limit.
Since most people using RSC are also Next.js users , and Next.js's default request body limit is 1MB , Cloudflare decided to change its proxy cache limit to 1MB as well.
This would ensure that all Next.js traffic can be protected by the WAF.
However, when releasing this change, they discovered a small problem: the tool used for testing the WAF does not support 1MB of data volume, and the deployment process was blocked by this testing tool.
But since it's just for testing, turning it off doesn't matter, right?
And as it turns out...
it really didn't matter. However,
turning it off caused another classic mistake.
First, it's important to know that all Cloudflare modules are guided by a unified rules engine.
Whether a request passes through a module, including the sub-modules responsible for testing within that module , is determined by independent rules.
Therefore, turning off WAF testing essentially means skipping the sub-rule in WAF that points to the test module.
If you've read "Make Programming Great Again #49," you'll know that Cloudflare is currently migrating its rules engine, with both the old and new versions running simultaneously.
Last month's problem was with the FL2 engine, rewritten in Rust , while this time the problem is with the FL1 engine, based on NGINX and written in Lua.
The Lua code shown in the image is mainly responsible for summarizing the execution results of sub-rules.
But because the WAF's test sub-rules were skipped , `rule_result.execute` is null when summarizing its results.
, `rule_result.execute` is null when summarizing its results.
Therefore, it naturally doesn't have a parameter called `results_index`, which is a classic null pointer error. Such a basic bug as the exception occurred because this code was never used or tested throughout the entire lifecycle of the FL1 engine.
If it weren't for this React vulnerability, it's highly likely that no one would have discovered this bug until the rule engine migration was complete and the entire FL1 engine officially retired.
Because if the React vulnerability hadn't been exposed, Cloudflare wouldn't have had the opportunity to capitalize on the hype , wouldn't have released WAF support for Next.js , wouldn't have wanted to modify the proxy cache size , wouldn't have discovered that the WAF testing tools didn't support modification , wouldn't have wanted to skip the test , wouldn't have turned off this sub-rule in the rule engine , wouldn't have triggered this bug , and Cloudflare wouldn't have crashed.
So, the React vulnerability inadvertently paralyzed half the internet.
Brilliant!
Whether it's React's underwhelming ambition , the stupid and malicious Next.js , or the formulaic, makeshift Cloudflare, I have nothing more to summarize.
Having done four fault interpretations in the past six months, I'm completely numb.
Normally, I would delve into the root cause of the problem , find the essential flaws in the industry, and offer my own improvement suggestions.
Now, I just want to say: When will the next idiot appear?
I've already created the folder.
Loading video analysis...