BB REPORT: Error-Based SQLi CRIT on a VDP Program

PUBLISHED ON 11/6/2023 / 5 MIN READ

Disclaimer

Some of the information in this post has been redacted and/or modified, as it pertains to a vulnerability in a program which does not allow public disclosure.

Note that it was a valid bug and was confirmed - however it was a VDP program, so there was no monerary reward. Having said that, it increased my reputation on the platform, allowing for private programs/invites, etc. :)

Overview

This post will discuss an SQL injection vulnerability, which was one of SEVERAL critical vulnerabilities, of which at least 5 were accepted, ultimately impacting every applicable domain in a VDP program.

This exact post is based on only one of such reports, as it was the only one which allowed me to achieve error-based SQLi - while the others were all blind/inferential.

Note that all of the (other) reports had similarities due to a similar code-base between the different targets, however, exploitation took different forms, such as via JSON or XML, and through different API endpoints - or even entirely different/third-party domains.

Since it is censored, I should mention that this was found on a store-locating functionality - the subdomains/endpoints would perhaps be named as such…

Steps to Reproduce Error-Based SQLi

The following steps will detail how to achieve initially blind/boolean-based SQLI, then later, error-based SQLi, on REDACTED.REDACTED.com/ajax?xml=foo, where the foo value of the xml parameter would contain a (manipulable) XML request:

Note that if you already understand the general idea of SQLi (both blind/boolean, and error-based), a working URL/payload with results can be found at the bottom of the “5.” section below.

  1. Launch a network proxy (such as Burp Suite or Zap) and use an appropriately configured browser to visit the following URL: https://REDACTED.REDACTED.com

  2. At this URL, in the search-box, insert any input (it doesn’t matter) and send the request. As an example, I will use “Columbus, OH” as the user input - although, again, it’s really not too important.

  3. Refer to your network proxy, and see whether any other requests have been sent after making the above request. There should be a GET request containing an XML request under the “xml” parameter, which looks something like this:

https://REDACTED.REDACTED.com/ajax?&xml=<request><appkey>REDACTED</appkey>(...REDACTED...)<where><redact_platform><eq>1</eq></redact_platform><key><eq>1</eq></key></where></formdata></request>

regarding above, the redacted info between the appkey and where tags was merely geolocation data - it’s irrelevant to the vulnerability.

…That’s quite a mouthful! Before going any further, it would be a good idea to cut this XML request down to size, both for clarity (i.e. not having to look through a messy/confusing request), and to prevent some of these parameters from getting in the way later in the exploitation process - that is to say, some of the tags in the above request can cause the conditional payloads given below to not be evaluated properly (or at all) - whether it would be because of a SQL syntax error, or an XML parsing error:

<request><appkey>REDACTED</appkey>(...)<where></where></formdata></request>

Much better!

  1. Looking at this XML request, some of it seems to greatly resemble the WHERE clause of an SQL query. the <where> and <eq> tags seem to correlate with the SQL syntax of ‘WHERE’ and ‘=’ respectively.

With this in mind, if you remove everything between the “where” tags, so that it looks like <where></where>, then, supposedly, you could have full (unauthenticated) control over the WHERE clause of the backend query.

This proves to be true. If you insert something that evaluates to be TRUE between the where tags, the query will execute successfully. Here are some examples:

<where>1=1</where>

<where>true</where>

(Note that for this particular query, the output can be quite large, as it seemingly returns all entries in the table with an ‘appkey’.)

If you were to change the value between the where tags to be ‘1=2’ or ‘false’, the result would be false, and the query would return nothing - or perhaps more aptly, something like “no locations were found in this area”.

  1. In other reports, this step would detail how to formulate a boolean condition to enumerate whether certain information pertaining to the contents of the database is true or false. In this case, however, it is possible to get large amounts of output from the database within an error message. Note that this is the only domain (so far) where error-based SQLi has been found from my testing.

    The basic idea behind triggering information disclosure within an error in this query, is to cause a miss-match of data types. This can be done using the CAST function, in addition to specifying that the resulting string should be interpreted as numeric - aptly named, “AS NUMERIC”. As in other reports, there are plenty of resources online which can specify in-depth how these techniques work - but for now, just know that this payload will disclose, via a SQL error, the full string of the database version:

<where>SELECT CAST(chr(32)||(SELECT query_to_xml('select version()',true,true,'')) AS NUMERIC) = 1</where>

As can be seen in the attached screenshot (albeit with encoded entities such as < and >), the resulting error will contain:

(...REDACTED...)

Using the same method, the current_user of the database is:

(...REDACTED...)

It would only take a very minimal change to this payload to be dumping entire tables of usernames, passwords/hashes, and any other sensitive data - indeed, the contents of the entire database could be exfiltrated without too much difficulty.

As such, this POC extracts authentic and arbitrarily defined information from the database, and I certainly suspect that the error-based nature of this vulnerability would allow an attacker who knows of this vulnerability to extract massive amounts of data from the database and/or drop tables, and/or escalate their privileges, etc. - The possibilities are quite vast, and none of them would seemingly be pleasant from the perspective of the company’s management.


Hopefully you learned something from this post! This was my first critical bug, and it opened the door(s) for me to hunt on private/exclusive programs.

I would also like to show my appreciation for this post from “Pulse Security”, which allowed me to understand the “AS NUMERIC” technique, thus enabling error-based SQLi.

Link: https://pulsesecurity.co.nz/articles/postgres-sqli


Until next time! :)