Exploit database error to leak users data ( Error based sql injection )
Contents
Hi everybody I don’t post much write-ups online because most of the work done privately and under NDA.
But this time i decided to publish this (anonymously after website owner agreed ) because too many developers insists that you can’t exploit complicated SQL , or non result SQL (example: count(*) sql).
Note: You can automate everything in this article using SQLMap, but i choose to exploit it manually to explain under the hood proccess.
First I got a call from one of my friends told me that a hacker contacted them and told them that he found a sql-injection in their website and express his intentions to blackmail them.
I opened the website and tried to play with the parameter just to check , in a couple of minutes i found a bug in their search page . search page make ajax request when you filter the result to the page , the term parameter is the one is not filtered.
/?FilterThemes?tags[]=free&term='
it generates a database error page with full sql dump ( debug mode is ON :) ).
| |
Awesome , but oh wait …
the parameter pass first to count query before it pass to aggregate query.. so must our payload pass the two queries and execute it with union..
OR …
I remembered uncommon way we used it to exploit blind SQL injection which don’t generate any result to the browser — this way can done only if errors is displayed, great that what we have here , right!
The POC
lets change the payload for a poc
| |
BINGO :)
| |
Did you notice :) , the expression
CONCAT('hello',FLOOR(RAND(0)*2),'world') has been evaluated and displayed :)
let me explain the sql query and how it works before we finish our exploit.
There is some information you needs to know about MySQL.
When mysql try to run a sql with multiple sub queries in it , it evaluate the sub queries first then the parent.
When using group by required a unique key.
To understand more, lets try some queries on our terminal.
| |
Normal count sql to count the number of records inside Information Scheme database (default database in mysql). now lets play with this sql a little.
| |
again nothing special , we added version() (function retrieve the mysql version) and named it x and then we grouped by it.
| |
I use maria db in my local machine , its a fork from mysql but with much optimization
Lets be aggressive
| |
We added a little change ( floor(rand(0) * 2) ) and if you run this sql you will get
| |
Do you remember the information i told you before about Mysql requires group key to be unique :).
the ( floor(rand(0) * 2) ) generates a sequence of 0, 1, 1, 0, 1, 1
and the version() will be always the same.
so the sequence of calling count(*) will be
CONCAT(version(),floor(rand(0) *2))= 10.2.3-MariaDB-10.2.3+maria~xenial-log0CONCAT(version(),floor(rand(0) *2))= 10.2.3-MariaDB-10.2.3+maria~xenial-log1CONCAT(version(),floor(rand(0) *2))= 10.2.3-MariaDB-10.2.3+maria~xenial-log1 //Duplication
So we got the error duplicate in the third attempt
| |
do you got it :)
Extract some data
We got the error , we got the leak .. now we need some valuable informations.
Starting with INFORMATION_SCHEME.tables , we can grab tables names.
With simple script we can grab all tables names by extracting the result from the db error , just feed the script with payload.
| |
Now we know the users table. modifying the script with the new payload to grab users data.
| |
Thats it.
Lesson learned
- Always turn errors off in production.
- Don’t underestimate any bug , 99% of bugs can be exploited somehow.
Stay safe.
Author Eslam Salem
LastMod 2020-08-13