Source: In-band UNION-based SQLi

apps/sqli/labs/union.py · view on GitHub

← back to lab

 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
47
48
49
50
51
52
53
54
55
56
"""SQLi lab: union — INTENTIONALLY VULNERABLE.

Product browser. Category filter is concatenated straight into the SQL
without parameters. Three columns are returned (name, description, price),
making UNION-based extraction straightforward — any attacker who can
guess the column count can pull arbitrary data out via UNION SELECT.
"""
from __future__ import annotations

from pathlib import Path

import pymysql
from flask import Blueprint, render_template, request

from ..db import get_conn

bp = Blueprint("sqli_union", __name__, url_prefix="/union")

META = {
    "slug": "union",
    "title": "In-band UNION-based SQLi",
    "summary": "Product category filter concatenated into SQL; result rows are rendered.",
    "hint": (
        "Query shape: SELECT name, description, price FROM products WHERE "
        "category='<input>'. The three columns are returned to you. Try "
        "?category=widgets' UNION SELECT name, value, 0 FROM secrets-- "
    ),
    "sink": "string-concatenated SELECT",
    "source_path": str(Path(__file__).resolve()),
    "vulnerable": True,
}


@bp.route("/", methods=["GET"])
def lab():
    category = request.args.get("category", "")
    rows = []
    error = None
    query = None
    if category:
        # INTENTIONAL: f-string into SQL.
        query = f"SELECT name, description, price FROM products WHERE category='{category}'"
        try:
            with get_conn().cursor() as cur:
                cur.execute(query)
                rows = cur.fetchall()
        except pymysql.MySQLError as e:
            error = f"{type(e).__name__}: {e}"
    return render_template(
        "lab_union.html",
        meta=META,
        category=category,
        rows=rows,
        error=error,
        query=query,
    )