Source: Error-based SQLi (login)

apps/sqli/labs/error.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
57
58
59
60
61
62
63
64
"""SQLi lab: error — INTENTIONALLY VULNERABLE.

Login form whose handler concatenates both username and password into the
SQL and helpfully renders the raw exception when the query fails. The
exception text on MariaDB includes whatever value the attacker forced into
the error message, so EXTRACTVALUE/UPDATEXML-style extraction works.
"""
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_error", __name__, url_prefix="/error")

META = {
    "slug": "error",
    "title": "Error-based SQLi (login)",
    "summary": "Login query concatenates user input AND the app renders raw SQL errors.",
    "hint": (
        "Query: SELECT id, role FROM users WHERE username='<u>' AND "
        "password='<p>'. SQL errors are shown verbatim. Use EXTRACTVALUE: "
        "username=' AND EXTRACTVALUE(1, CONCAT(0x7e, (SELECT value FROM "
        "secrets WHERE name='sqli-error')))-- "
    ),
    "sink": "string-concatenated SELECT + raw exception render",
    "source_path": str(Path(__file__).resolve()),
    "vulnerable": True,
}


@bp.route("/", methods=["GET", "POST"])
def lab():
    username = request.values.get("username", "")
    password = request.values.get("password", "")
    result = error = query = None
    if request.method == "POST" and (username or password):
        # INTENTIONAL: both fields concatenated into SQL.
        query = (
            f"SELECT id, role FROM users "
            f"WHERE username='{username}' AND password='{password}'"
        )
        try:
            with get_conn().cursor() as cur:
                cur.execute(query)
                row = cur.fetchone()
                if row:
                    result = f"Welcome, user id={row[0]}, role={row[1]}."
                else:
                    result = "Invalid credentials."
        except pymysql.MySQLError as e:
            # INTENTIONAL: full exception in the response.
            error = f"{type(e).__name__}: {e}"
    return render_template(
        "lab_error.html",
        meta=META,
        username=username,
        result=result,
        error=error,
        query=query,
    )