Exploiting a Tricky Blind SQL Injection inside LIMIT clause

Exploiting a Tricky Blind SQL Injection inside LIMIT clause

REDIRECTING TO THE NEW BLOG ...

Hey! Everyone I know its been a long time Since I last posted an article. so Today I am writing about a tricky SQL Injection that I was able to exploit in a private program. It was an in-scope subdomain which I got by using passive recon and the technology stack was basically PHP so I knew it had to be vulnerable at some point.
so the functionality was like, There was an ability to create Image albums and inside an album, you could have uploaded various Images but there was Pagination ie. Only a few numbers of albums were visible on a Page and You need to click Page numbers to view other albums. While clicking at the Page numbers I noticed the request was like:
/albums.php?page=2&num_max=20

It added num_max parameter which was nothing but told the limit of albums per page we could have increased or decreased it to increase the number of albums on a single Page. so I tried to check for SQL Injection by simply putting ’ " and \ etc. and It threw the SQL query in the response. Also, the error revealed that it was PostgreSQL DBMS.
Query Looked Something like this:
Select * from tbl_albums where page=2 order by album_date asc LIMIT 0,{{INPUT}}

Where in Limit 0,{{input}} 0 is the offset ie. from which row to return records and {{input}} is the number of rows that should be returned from the offset and can also be written as LIMIT {{INPUT}} OFFSET 0 The application was escaping quotes. However, since I also used \ to check and since the injection was in numeric context It caused an error.
Select * from tbl_albums where page=2 order by album_date asc LIMIT 20\ OFFSET 0

As far as I know, when Order by query is used along with LIMIT Clause we can’t use UNION Clause to inject our own row into the currently running query. It was possible to do error based Injection in case of MySQL but It was PostgreSQL DBMS this time. I tried to search a lot for workarounds this but found none. Another thing that was possible was, Stacked/Batched Queries but sadly even ; (semi-colon) was escaped and I couldn’t inject that as well.
Soon after this, I contacted my friend none other than @securityidiots and he came up with an amazing trick which I’m going to share right now. Although it was application dependent It could be possible at various other places.

Exploit

1. First of all, using Burp Intruder I created around 200 Albums (you will know below that only 127 was needed but why not!)
2. I decided to first extract the first character of the DBMS Version so I used inbuilt substr function and put my SQL Query inside it such that it became
 Select * from tbl_albums where page=2 order by album_date asc LIMIT 0,substr((select version()),1,1)

but obviously it threw an error because the DBMS expects the value of LIMIT and OFFSET to be numeric!
1. Next, we used "ascii" function to convert the result of the substr function to a number so the query looked something like
 Select * from tbl_albums where page=2 order by album_date asc LIMIT 0,ascii(substr((Select version()),1,1))

2. Now the query was perfectly fine and returned no errors, now what this query will do is, return that number of albums which is equivalent to the ascii value of first character of version() function’s output. That was the reason to create so many albums in the step #1 since the result would be in the range of 0-127.
Example: If the output of version() was PostgreSQL 9.6.2 then
substr((select version()),1,1) would be “P”
ascii(substr((select version()),1,1)) would be "80"
Therefore, only 80 albums would be returned to the page
substr((select version()),2,1) would be “o”
ascii(substr((select version()),2,1)) would be "111"
only 111 albums would be returned to the page and Similarly, we can move ahead.
so all we have to do now is count the number of albums returned in the DOM using some javascript and convert the count of albums back to the equivalent Character in ASCII Table and we got our first character of (select version()) and similarly we could go further to extract all DBMS by automating it.
I used document.querySelectorAll('.ALBUM_CLASS').length to find the number of albums returned and Converted them back to Character using
String.fromCharCode(80)
Though, this was enough for the POC. I hope you guys enjoyed the writeup as it was extremely fun exploiting it!