. [ Show ] in English
Optimization of blind sql injection vulnerabilities
Blind SQL Injections don’t always go down well with attackers, because it’s very boring to exploit.
But there are some techniques of optimization which make life easier.
A) Time
“Time is money”, this is valid for many categories and it’s
very important while exploiting Blind Injections and
dumping a large amount of data sets.
So it’s the goal to ..
1. Reduce the dumping time
and
2. Use as few requests as possible to get data.
Both goals are interlocked and there are criteria converning the
“time” problem.
Some of them are:
1. Reponsetime of the server
2. Amount of data
3. Range of ASCII Chars that are used for bruteforcing (=> Count of requests)
You can’t change the response time directly but you can
reduce the data volume.
A simple way is the unoptimised content-change method, which isn’t
not that bad, but better in it’s optimised way.
An usual example could be:
1
| index.php?id=1+and+if((select+ascii(substr(password,1,1))+from+users+limit+0,1)>100,null,1)=1/* |
Whereas it’s difficult to find a good search pattern, we substitute the TRUE condition with
a small subquery, which returns more than one row.
Because it’s not possible to return more than one row in a subquery, we’ll get this message:
“Subquery returns more than 1 row”, if display_errors is switched on.
Now we’ve got a good search pattern for automated scripts.
An advantage using this method is, that in the most cases the script will be aborted with die(), and no other
content will be shown than the error message.
If it works the loading time is lower than without optimization.
An example could look as follows:
1
| index.php?id=1+and+if((select+ascii(substr(password,1,1))+from+users+limit+0,1)>100,(select+1+union+select+2),1)=1/* |
Usually you should have a good search pattern now, and we reduced the amount of
content.
Now it’s time to reduce the requests:
An usual way to determine the chars is using operators like < and > to get closer
to the ascii value of the char.
It’s your decision how to use the determination exactly, but it’s not advisable to
test every single char seperate – would be absurd for sure, because you can halve the
intervals every time, while using < and >.
1
| If char greater than 85, do...: |
1
| If char greater than 103 do...: |
etc.
you could solve it recursive as well.
When you’ve got a large amount of chars to test (contents, where all chars are allowed)
theres another, maybe better way.
If we want to test the char range reaching from 32 to 126, and our char is a “B” we’ve to
send requests until we reach 66.
It depends on your code, how many requests you need for this.
If you cast 66 to binary you get a fix count of chars:
1000010
Here we’ve to start 7 requests, but it’s enough to test only one single state.
“If value is not 1 it must be 0.”
“If value is not 0 it must be 1.”
7 requests to test one char could be an advantage and a disadvantage too,
this depends on the style of programming.
We’ll test it using a fictive MD5 hash.
3295c76acbf4ca8ed33c36b1b5fc2cb1
We’ve to test the chars a-f0-9.
0 -> 110000
9 -> 111001
a -> 1100001
f -> 1100110
We notice, that alphabetic characters need one request more.
As it happens we’ve got 16 letters and 16 digits in our hash.
If we know that’s a MD5 Hash and it’s length is 32 byte for sure,
we need a determinable count of requests:
(16 * 6) + (16 * 7) = 208
If this method is profitable by determing MD5 Hashes is not sure, but it’s useful
when you’ve got a large range of chars.
B) Control Flow Functions
The best method of char determination is useless, when you’ve got no chance
to use it.
You aren’t successfull with the AND method everytime, and the and+1=1, and+1=0 check fails
although you’re sure that the injection is there.
Theres another possibility to be successfull, using the control flow function “case”.
It consists of a condition which can be true or false. It’s introduced with
“case” and “when”. It follows the result when the condition is true, and optional you
append an “else” part.
I’ll oppose both versions, with equal effect.
1
| index.php?id=1+and+if(1=1,(select+1+union+select+2),null)=1/* |
1
| index.php?id=1+and+case+when+(1=1)+then+(select+1+union+select+2)+else+null+end/* |
You can combine this way for sure with the already presented way of char determination.
——–
Have fun.
Lidloses_Auge
Blind Injections treffen nicht immer auf Anklang, weil man sich selbst einer umständlichen Feinarbeit ausgesetzt sieht, die lange dauert und nervenaufreibend ist.
Doch gibt es ein paar Kniffe, die die Arbeit erleichtern können.
A) Zeit
“Zeit ist Geld”, das gilt in fast allen Bereichen,
und bei Blind SQL Injections ist dies besonders wichtig,
wenn es darum geht, große Datenmengen zu extrahieren.
Es geht also zunächst darum, für einen Datensatz in der
Datenbank ..
1. Möglichst wenig Zeit aufzuwenden
und
2. Mit möglichst wenigen Requests an Infos gelangen.
Beide Ziele sind selbstverständlich eng miteinander verzahnt,
und so gibt es einige Kriterien, die ihren Teil zum Faktor
“Zeit” beitragen.
Diese sind beispielsweise:
1. Antwortzeit des Servers
2. Zu Übertragendes Datenvolumen
3. Wertebereich der zu testenden ASCII Zeichen (=> Anzahl Requests)
Ändern lässt sich die Antwortzeit des Servers nicht aktiv,
doch obliegt es unseren Möglichkeiten, die Datenvolumina zu
begrenzen.
Eine triviale Methode an Infos zu gelangen ist die unoptimierte
Content-Change Methode, die nicht unbedingt schlecht ist, doch
erst in optimierter Form zufriedenstellende Leistungen erbringt.
Ein Beispiel wie wir es kennen, wäre:
1
| index.php?id=1+and+if((select+ascii(substr(password,1,1))+from+users+limit+0,1)>100,null,1)=1/* |
Da es aber in diesem Falle schwierig sein kann, ein passendes Suchmuster zu finden, setzen wir für
die true Bedingung ein kleines Subquery ein, welches mehere Rows zurückgeben soll.
Da dies in einem Subquery nicht möglich ist, wird sich bei angeschaltetem display_errors
gleich die Fehlermeldung “Subquery returns more than 1 row” zeigen, und wir haben unser Suchmuster.
Dies hat oft den Vorteil, dass das Skript direkt mit die() abgebrochen wird, und der Fehler schwarz auf weiß
als einziger Content auf der Seite zu finden ist.
Dies ist aber nur manchmal der Fall, und dies vermindert die Ladezeit deutlich.
Ein Beispiel dafür könnte dann so aussehen:
1
| index.php?id=1+and+if((select+ascii(substr(password,1,1))+from+users+limit+0,1)>100,(select+1+union+select+2),1)=1/* |
Der Suchstring, sollte nun in den meisten Fällen vorhanden sein, und wenn wir Glück haben,
ist auch der Content der Seite bei “true” reduziert worden.
Jetzt gilt es selbstverständlich die Anzahl der Requests zu reduzieren.
Eine gängige Methode um die Zeichen zu bestimmen, ist selbstverständlich die,
mit den Operatoren < und > sich den den ASCII Wert des Zeichen hinanzupirschen.
Wie man diese Methode letztendlich umsetzt sei jedem selbst überlassen, doch wäre es nicht ratsam,
alle Zeichen von beispielsweise 48 bis 122 einzeln durchzutesten – wäre auch unsinnig, denn mit
Hilfe der < > Operatoren, ist es sinnvoll, die Abstände immer zu halbieren:
1
| Wenn Zeichen größer 85, teste: |
1
| Wenn Zeichen größer 103 teste: |
etc.
könnte man auch durchaus rekursiv lösen.
Bei sehr großen Wertebereichen, z.b. Inhalte, in denen alle Zeichen
vorkommen dürfen, gibt es aber noch eine weitere Möglichkeit, die nicht immer
zwingend weniger Requests mit sich bringt, auf große Distanzen aber eine große
Absicherung mit sich bringt.
Möchte man den Wertebereich von 32 – 126 prüfen, und unser Zeichen ist zufällig ein
“B” müssen wir so viele Requests starten, bis wir beim Zeichencode 66 angelangt sind.
Wieviele requests dies bedeutet, hängt von unserem Code ab.
Casten wir die 66 aber zu einer Binärzahl, haben wir eine fixe Anzahl an Zeichen:
1000010
Hier müssen wir zwar 7 Requests starten, doch reicht es hier aus, nur einen Zustand zu testen:
“Ist der Wert nicht 1, dann muss er 0 sein.”
“Ist der Wert nicht 0, dann muss er 1 sein.”
7 Requests bei einem Zeichen, kann eine Verbesserung sein, aber durchaus auch eine Verschlechterung,
dies hängt wie bereits genannt vom Programmierstil ab.
Testen wir es anhand eines fiktiven MD5 Hashes:
3295c76acbf4ca8ed33c36b1b5fc2cb1
Die Zeichen a-f0-9 sind zu testen.
0 -> 110000
9 -> 111001
a -> 1100001
f -> 1100110
Wir sehen, dass die Buchstaben ein Request mehr beanspruchen.
In unserem Hash finden wir zufällig, 16 Buchstaben und 16 Zahlen.
Sofern wir wissen, dass es sich um einen MD5 handelt, und demsptsprechend
die Länge 32 Bytes ist, benötigen wir eine bestimmbare Anzahl an Requests:
(16 * 6) + (16 * 7) = 208
Ob sich dieses Verfahren bei MD5 lohnt, sei dahingestellt, sicherlich ist es
immer sinnvoller, bei größeren Wertebereichen.
B) Ablaufsteuerungsfunktionen
Das schönste Verfahren zur Zeichenbestimmung nützt einem nichts, sofern man
es nicht anwenden kann. Nicht immer trifft man mit der AND Methode auf eine Goldader,
und der Check mit and+1=1 und and+1=0 versagt, obwohl man sich der Existenz der Injection
sicher ist.
Es gibt eine weitere Möglichkeit, auf den richtigen Pfad zu kommen, mit der Kontrollstruktur “case”.
Diese besteht aus dem Fallbeispiel, also die Bedingung, durch “case” und “when” gekennzeichnet,
der darauf bei wahrer Aussage folgenden Wirkung “then” und wahlweise einem “else” Teil.
Ich stelle nun beide Versionen gegenüber, die im Grunde das gleiche bewirken:
1
| index.php?id=1+and+if(1=1,(select+1+union+select+2),null)=1/* |
1
| index.php?id=1+and+case+when+(1=1)+then+(select+1+union+select+2)+else+null+end/* |
Dies kann man selbstverständlich kombinieren, mit bereits genannten Verfahren zur Zeichenbestimmung.
——–
Viel Spaß und neue Erkenntnisse wünscht
Lidloses_Auge
Lidloses_Auge SQL Injections, Text Tutorials