HTB Write-Up: Nexus
| Platform | Hack The Box |
| Difficulty | Medium |
| OS | Linux |
| Author | shreekara |
| Date | July 15, 2026 |
| Source | github.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:
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.
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:
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:
Wildcard DNS, so basically everything comes back 302/154 bytes/4 words. Filtered that out:
billing [Status: 302, Size: 390]
Two real subdomains. Added them:
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:
| CVE | Type | Impact |
| CVE-2026-38526 | Unrestricted file upload → RCE | Full 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:
- Uploaded any image through the editor.
- Caught the request in Burp, sent to Repeater.
- Changed the filename to
RCE.php, swapped the image bytes for:
Forwarded it, got a 200 OK back with the storage path for the uploaded file straight in the JSON body:
Hit the path with ?c=id, command execution confirmed.
Reverse shell one-liner (URL-encoded, sent through the same ?c= param):
Listener:
Shell landed:
uid=33(www-data) gid=33(www-data) groups=33(www-data)
5. Shell as www-data to jones
Normal Laravel/Krayin layout.
Same password, still current. Tried it against SSH for jones since he's the only real user on the box.
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:
Made a new repo called RCE, marked it as a Template, cloned it:
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.
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:
Key got written to /root/.ssh/authorized_keys. Root:
root.txt
root@nexus:~# cat root.txt
7. Summary
| Step | What happened |
| Recon | nmap → 22, 80 |
| Web recon | Careers page leaks internal emails |
| Subdomains | ffuf + Host header → git., billing. |
| Leaked cred | Old commit on Gitea repo still has the DB password |
| Initial access | Krayin 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 root | template-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.