Tag Archives: SQL

Chained Exploits

Sometimes it is one vulnerability that gets exploited that leads to the newsworthy stories of businesses getting compromised, but usually it is the chaining of 2, 3, 4, or more vulnerabilities together that leads to the compromise of a business.

That’s where WhiteHat comes in. WhiteHat will report not just the XSS, SQL injection, and CSRF, but the Information Leakage, Fingerprinting, and other vulnerabilities. Those vulnerabilities may not be much by themselves, but if I see your dev and QA environments in commented-out HTML source code in your production environment, I just found myself new targets. That QA environment may or may not have the same code that is running on the production environment, but does it have the same configurations? The same firewall settings? Are those log files monitored like the production environment? I now have a leg up in to your network all because of an Information Leakage vulnerability that I was able to leverage and chain together with other vulnerabilities. How about a Fingerprinting vulnerability that tells me you are running an out-of-date version of nginx or PHP or Ruby on Rails. A version I happen to know is vulnerable to Remote Code Execution, Buffer Overflow, Denial of Service, or something else. You just made my job much easier. Doesn’t seem so benign now, does it?

But let’s assume for a moment that you take care of those problems. You turn off stack traces, you get rid of private IP addresses in the response headers. What next? Let’s build another scenario, one that I encountered recently.

There is a financial institution that provides online statements to users for their accounts. To encourage users to use the online statements instead of paper statements, you charge users a nominal fee to get paper versions of their imaged checks. As part of the log in process, in addition to the username and password, a user needs to answer one or more security questions before gaining access to the account. This helps prove that it is the user and not someone who was able to obtain or guess the username and password set.

Have that vision in your head? Now, what if I told you that I could CSRF transferring funds, but only between the accounts you have, perhaps a checking account and a credit card account. That surely can’t be bad, can it? Well, it turns out that the user gets charged whenever a cash advance is made from their credit card to their checking account. Okay, so I can rack up some charges. But what if I want to do something else? Say, CSRF changing their username? If you’ll recall, I need the password too, along with security questions. No go on the password and security questions. But I can CSRF changing the user from online statements to paper statements and for added fun, make them get charges for the imaged checks. My fun doesn’t stop there. The creme de la creme. The piece de resistance. The go big or go home. CSRF on the mailing address.

Why is that such a big deal, you ask? Now I know the username of the account. I have active account statements sent to a mailing address I control, along with imaged checks. And then, all I need to do is call the bank’s customer support, ask about “my” account, using “my” username, “my” account number, and the details of the imaged checks just in case the bank asks for further confirmation to prove that I am who I say I am.

And then I say: “Oh, and I’m calling because I forgot both my password and the answer to my security questions.”

Is it common for users to forget their security questions? Yes. I used to be in the habit of providing fake information to the security questions because I didn’t want an attacker who may know or guess the answer, what would otherwise be a correct answer, to my security questions. But me being me, I forgot them and I lost access to the account. Others may be in the same habit.

So you gotta ask yourself, what’s one vulnerability?

In Your Oracle: Part Two

In Micheal‘s previous post, ‘In Your Oracle: The Beginning‘, he introduced a blind SQL Injection vulnerability that a client was asking us to dig deeper into. The client wanted us to do this, because while they recognized that the vulnerability was real, actionable, and a threat – especially to their users – they weren’t convinced of its severity. Instead, the client claimed that the vulnerability could only be leveraged to read data already intended to be accessible by the logged-in user. In other words, the SQL query was executing within the context of a low-privileged database user.

A quick aside: I had a different client who recently downplayed the severity of an SQL Injection vulnerability because the result set was being parsed and formatted before being incorporated into the response. Because of this behavior, they didn’t think any data could be accessed other than what was intended to be parsed and formatted into the page. Aside from being able to UNION other data into the result set, or simply to brute force dropping tables, I introduced the client to something Micheal touched on in his post: The true/false/error condition. More on this in a minute.


The Vulnerability

The vulnerability that Micheal and I were digging into did not involve the entire result set being output to the page, so we couldn’t simply UNION other data and get it all dumped back to us. That’s because the application would execute an SQL query – using an ID that was being supplied in the query string – and the first row of data returned by the query would be used to determine the response. Therefore, we could only return a limited amount of data at a time, and that data would have to conform to certain data types – otherwise, the page would error out.

Here’s an example of what the back-end SQL query may have looked like (though, in reality, it was much more complex than this):

SELECT * FROM listingsData WHERE id='504'

And an example of the URL we were injecting on:

http://example.com/somepage?id=504

And last, but not least, an extraordinarily simplified example of the output from the page:

Listing ID: 504
Listing Entity: Acme, Inc.
Listing Data: <a table of data>


The True/False/Error Condition

When an SQL Injection vulnerability can’t be leveraged to just dump rows upon rows of data, a true/false condition can allow an attacker to fuzz the database, and determine the table and column names, the cell values, and more. Basically, with a reliable true/false condition, an attacker can brute force the entire database in a practical amount of time.

However, due to strange behavior from the application (plus what we suspected was a complex query syntax that we couldn’t discern), we were not able to simply append our own WHERE clause condition, like this:

http://example.com/somepage?id=504'%20AND%201=1%20AND%20''='

We were, however, able to perform string concatenation. Injecting id=5'||'0'||'4 would give us the same response as id=504. We also discovered that id=54 would result in the following response:

Listing ID:
Listing Entity:
Listing Data:

Furthermore, we found that a syntax error, such as what id=' would cause, produced the following response:

An error has occurred. Please try again.

In Oracle, there exists a dummy table called ‘dual‘. We were able to use this table, in combination with string concatenation and a sub-query, to establish a boolean condition:

http://example.com/somepage?id=5'||(SELECT%200%20FROM%20dual%20WHERE%201=1)||'4

The URL encoding makes it look messy. Here’s the URL-decoded version of our injection:

5'||(SELECT 0 FROM dual WHERE 1=1)||'4

Because the WHERE clause of our sub-query evaluated to TRUE, the SELECT would return a zero, which got concatenated with the 5 and 4, and became 504. This injection resulted in Acme, Inc.’s information being returned in the page, and became our TRUE condition.

Now consider this injection (URL decoded for readability):

5'||(SELECT 0 FROM dual WHERE 1=0)||'4

Because the WHERE clause evaluated to FALSE, the SELECT returned nothing, which got concatenated with 5 and 4, and became 54. This injection resulted in blank listing data in the response, and was considered to be our FALSE condition.

The TRUE condition let us know when the WHERE clause of our sub-query evaluated to TRUE, and the FALSE condition told us the inverse. The error condition (described a few paragraphs above) let us know if there was a syntax error in our query (possibly due to characters being unexpectedly encoded).

Now that we had established a reliable true/false/error condition, we could start having some fun.


The Good Stuff

We were able to use the true/false condition to brute force some pieces of information that we figured the client did not intend logged-in users to obtain, such as the database name (from this point forward, all injections will remain URL-decoded for the sake of readability):

5'||(SELECT 0 FROM dual WHERE SUBSTR(SYS.DATABASE_NAME, 1, 1) BETWEEN 'a' AND 'z')||'4

The above injection took the first character of the database and determined if it was a lowercase letter. If true, we’d get Acme, Inc.’s data in the response; if false, we’d get blank listing data.

We could quickly brute force each character by cutting our BETWEEN range in half for each test. For example, if the above injection resulted in a TRUE condition, then we could figure out which lowercase letter the database name started with by using the following process:

Cut the range of characters in half:

5'||(SELECT 0 FROM dual WHERE SUBSTR(SYS.DATABASE_NAME, 1, 1) BETWEEN 'a' AND 'm')||'4

If the above resulted in a TRUE condition, then we knew the letter was between ‘a’ and ‘m’, and could then test for it being between ‘a’ and ‘g’. If the above resulted in a FALSE condition, then we’d drill down into the ‘m’ through ‘z’ range. In either case, we kept narrowing the search range until we were able to pinpoint the correct character.

We could then brute force the rest of the database name characters by incrementing the second parameter of the SUBSTR function (2 for the second character, 3 for the third, etc). If we incremented the parameter and got an error condition as a result, we knew we had surpassed the length of the database name.

We could also pre-determine the length of the database name with a similar “test and drill down” technique with this injection:

5'||(SELECT 0 FROM dual WHERE LENGTH(SYS.DATABASE_NAME)>10)||'4

Once we had brute forced the entire value, we verified our finding with this injection:

5'||(SELECT 0 FROM dual WHERE SYS.DATABASE_NAME='dbmaster01')||'4

We were able to extract information from other tables, too, such as:

5'||(SELECT 0 FROM SYS.USER_USERS WHERE SUBSTR(username, 1, 1) BETWEEN 'a' AND 'z')||'4

Using this method, we were able to extract various pieces of information, such as the database name, database user (which the query was being executed with), database user ID, and default table space. However, Micheal and I were not happy stopping there. With the help of pentestmonkey.net, we discovered some interesting Oracle features that gave us some juicy insights into our client’s network.


The Juicy Stuff

Oracle has a couple of variables that allowed us to extract the server’s internal hostname and IP address: UTL_INADDR.get_host_name and UTL_INADDR.get_host_address, respectively. Using the same brute force technique described above, we were able to successfully extract the hostname/IP and verify our findings with the following injections:

5'||(SELECT 0 FROM dual WHERE UTL_INADDR.get_host_name='srvdb01')||'4
5'||(SELECT 0 FROM dual WHERE UTL_INADDR.get_host_address='10.1.20.5')||'4

What we found even more interesting was the fact that UTL_INADDR.get_host_name would accept a parameter – an IP – and would allow us to basically perform DNS queries through the SQL Injection vulnerability:

5'||(SELECT 0 FROM dual WHERE LENGTH(UTL_INADDR.get_host_name('10.1.20.6'))>0)||'4

If the above resulted in a TRUE condition, we knew the IP resolved successfully, and we could proceed with brute forcing the hostname. If the result was a FALSE condition, we’d presume the IP to be invalid.

Micheal and I, being the PHP fans that we are, collaborated with each other to automate the entire process. Several hundred lines of code later, we were able to quickly harvest dozens of internal IPs and corresponding hostnames – information that would come in quite handy for a network-level attack, or even for a social engineering approach (“Hi, I’m Matt from IT. I’m having trouble logging in to srvdb01 at IP 10.1.20.5. Is the root password still “qSSQ[W2&(8#-/IQ4b{W;%us”?).


Conclusion & Take-Aways

Never assume that you know the full extent of the threat that a vulnerability represents to your organization. While you can reach a particular level of confidence in your knowledge and understanding of the situation, you never know what new and imaginative avenues of attack or combinations of techniques an attacker may discover or create – like mapping out your internal network through an SQL Injection vulnerability.

Imagination is more important than knowledge. For knowledge is limited to all we now know and understand, while imagination embraces the entire world, and all there ever will be to know and understand.” -Albert Einstein