## https://sploitus.com/exploit?id=E85076D0-53F3-526A-AA46-1BE5D805E52D
# Flask Web Application Security Audit
A hands-on penetration test of a deliberately vulnerable Flask application:
find the flaws, prove them with real tooling, and ship the fix for each one.
Course: **OCY1102 β Cybersecurity in Development**, IngenierΓa en Conectividad
y Redes, Duoc UC. Author: **Hugo Herrera**.
> **Ethics.** The target is a lab application I wrote for this purpose and run on
> `127.0.0.1`. Everything here was executed against my own machine. Do not point
> these techniques at systems you don't own or aren't authorized to test.
---
## What this shows
Not a checklist of theory β a full loop on each finding:
1. **Locate** the flaw in the source (`app/vulnerable_app.py`, `app/create_db.py`).
2. **Prove** it: an automated scan with OWASP ZAP plus a manual exploit.
3. **Fix** it, with the corrected code in `fixes/` and the reasoning written down.
The scan under `evidence/owasp-zap-scan.csv` is the real export: **999 requests**,
of which **359** carry command-injection payloads (`;sleep 15.0;`, `||sleep 15.0`,
`&sleep$u+15.0&`). The `sleep` is the tell β a response that takes 15 seconds
longer proves the input reached a shell.
---
## Findings
| # | Vulnerability | Where | Severity |
|---|---|---|---|
| 1 | **OS Command Injection** | `/ping` builds `ping -c 1 {ip}` and runs it through the shell | Critical |
| 2 | **Reflected XSS** | `/saludo` interpolates `?nombre=` into HTML via `render_template_string` | High |
| 3 | **Missing CSRF protection** | `/submit_comment` accepts POSTs with no anti-CSRF token | High |
| 4 | **Insecure password storage** | `create_db.py` uses unsalted `sha256` | High |
| 5 | **Debug mode / RCE** | `app.run(debug=True)` exposes the Werkzeug console | Critical |
### 1 β OS Command Injection (`/ping`)
```python
comando = f"ping -c 1 {ip}" # ip comes straight from the form
resultado = os.popen(comando).read() # handed to the shell
```
Input `8.8.8.8; cat /etc/passwd` runs both commands. In the ZAP capture this
shows up as hundreds of `sleep`-based time payloads β the server stalls exactly
when the injected command executes.
**Fix** (`fixes/secure_app.py`): validate the input as a real IP with
`ipaddress.ip_address()`, then run `subprocess.run([...], shell=False)` as an
argument list so nothing is ever shell-interpreted. Defence in depth β either
layer alone stops it.
### 2 β Reflected XSS (`/saludo`)
```python
template = f"Bienvenido, {nombre}!"
return render_template_string(template) # nombre is attacker-controlled
```
`?nombre=alert(document.cookie)` executes in the victim's
browser. **Fix:** render through a real Jinja template, which auto-escapes β the
tags come back as visible text, not code.
### 3 β Missing CSRF token (`/submit_comment`)
`exploits/csrf_poc.html` is a standalone page that POSTs a comment to the app.
Loaded while the victim is logged in, it forges the request under their session.
**Fix:** `Flask-WTF`'s `CSRFProtect` requires a per-session token on every POST.
### 4 β Unsalted SHA-256 passwords (`create_db.py`)
```python
hashlib.sha256(password.encode()).hexdigest()
```
Fast to brute-force, and with no salt two identical passwords hash identically β
so rainbow tables apply and one crack breaks every reuse. **Fix**
(`fixes/create_db_secure.py`): `werkzeug.security.generate_password_hash`
(PBKDF2 + random per-hash salt). Same password, different hash every time.
### 5 β Debug mode enabled
`app.run(debug=True)` serves the interactive Werkzeug debugger. Anyone who can
reach it and trigger an exception gets a Python console on the server β remote
code execution. **Fix:** debug stays off, and the app binds to `127.0.0.1`
instead of `0.0.0.0`.
---
## Layout
```
app/ vulnerable_app.py, create_db.py β the target, as audited
exploits/ csrf_poc.html β manual proof-of-concept
evidence/ owasp-zap-scan.csv β 999-request ZAP export
fixes/ secure_app.py, create_db_secure.py β remediated code, per finding
```
## Tools
OWASP ZAP Β· Burp Suite Β· manual testing Β· Python Β· Flask Β· SQLite
## Reproduce
```bash
# Vulnerable target (lab only, localhost)
cd app && python create_db.py && python vulnerable_app.py
# Hardened version
cd fixes && pip install flask flask-wtf werkzeug && python secure_app.py
```