Difference between revisions of "Testing for MS Access"

From OWASP
Jump to: navigation, search
(39 intermediate revisions by 6 users not shown)
Line 1: Line 1:
 +
{{Template:OWASP Testing Guide v4}}
 +
 
== Short Description of the Issue ==  
 
== Short Description of the Issue ==  
In this paragraph we describe how to exploit sql injection vulnerabilties when the
+
This article describes how to exploit SQL Injection vulnerabilities when the
backend database is MS Access. In particular we focus on how to exploit blind sql injection
+
backend database is MS Access. In particular, the article focuses on how to exploit Blind SQL Injection.
as this kind of vulnerabilities are more frequent.
+
After an initial introduction on the typical functions that are useful to exploit a SQL Injection vulnerability,  
After an initial introduction on which are the typical functions  
+
a method to exploit Blind SQL Injection will be discussed.
that are useful to exploit a sql injection vulnerability,  
+
we will introduce a clever  methodology to exploit blind sql injection.
+
  
 
== Black Box testing and example ==
 
== Black Box testing and example ==
  
 
=== Standard Test ===
 
=== Standard Test ===
First of all, we start showing a typical example of SQL error that we can encounter when
+
First of all, let's show a typical example of SQL error that can encounter when
we execute our test:
+
a test is executed:
  
 
  Fatal error: Uncaught exception 'com_exception' with message '<b>Source:</b> Microsoft JET Database Engine<br/><b>Description:</b>
 
  Fatal error: Uncaught exception 'com_exception' with message '<b>Source:</b> Microsoft JET Database Engine<br/><b>Description:</b>
  
If we get this error then it's reasonable to think that we are testing an application with a MS Access Database as backend.
+
That means that maybe we are testing an application with an MS Access Database backend.
  
We have to say that, unfortunately, we start soon with a bad news since MS Access doesn't support any comment character in the sql query,
+
Unfortunately, MS Access doesn't support any comment character in the SQL query,
so we can't use the trick of inserting the chars /* or -- or # to truncate the query. On the other hand, we can fortunately  bypass this limit with the NULL character. If we insert the char %00 at some place in  
+
so it is not possible to use the trick of inserting the characters /* or -- or # to truncate the query. On the other hand, we can fortunately  bypass this limit with the NULL character. If we insert the char %00 at some place in  
the query, all the remaining characters after the NULL are not considerated. That happens
+
the query, all the remaining characters after the NULL are ignored. That happens
because at the low level the strings are considerated terminated with the NULL char.
+
because, internally, strings are NULL terminated.
However the NULL character can sometimes cause troubles. If we try every char in the ASCII
+
However, the NULL character can sometimes cause troubles. We can notice that there is another value that can be used in order to truncate the query.
charset we notice that there is another value that can be used in order to truncate the query.
+
The character is 0x16 (%16 in URL encoded format) or 22 in decimal. So if we have the following query:
The character is 0x16 (%16 in url encoded format) or 22 in decimal. So if we have the following query:
+
  
<nowiki>
+
SELECT [username],[password] FROM users WHERE [username]='$myUsername' AND [password]='$myPassword'
SELECT [username],[password] FROM users WHERE [username]='$myUsername' AND [password]='$myPassword'
+
</nowiki>
+
  
we can truncate the query with the following two urls:
+
we can truncate the query with the following two URLs:
  
<nowiki>
+
http://www.example.com/index.php?user=admin'%00&pass=foo
http://www.example.com/index.asp?user=admin'%00&pass=foo
+
http://www.example.com/index.php?user=admin'%16&pass=foo
http://www.example.com/index.asp?user=admin'%16&pass=foo
+
</nowiki>
+
  
 
==== Attributes enumeration ====
 
==== Attributes enumeration ====
In order to enumerate the attributes of a query we can use the same method used for the Database
+
In order to enumerate the attributes of a query, it is possible to use the same method used for the database
MS SQL Server. In short, we can obtain the name of the attributes by error messages. For example
+
MS SQL Server. In short, we can obtain the name of the attributes by error messages. For example,
 
if we know the existence of a parameter because we got it by an error message due to the
 
if we know the existence of a parameter because we got it by an error message due to the
 
' character, we can also know the name of the remaining attributes with the following query:
 
' character, we can also know the name of the remaining attributes with the following query:
  
<nowiki>
+
' GROUP BY Id%00
' GROUP BY Id%00
+
</nowiki>
+
  
in the error message we can notice that the name of the next attribute is shown. We iterate the
+
In the error message received we can see that the name of the next attribute is shown. We iterate the
 
method until we obtain the name of all the attributes. If we don't know the name of at least
 
method until we obtain the name of all the attributes. If we don't know the name of at least
one attribute, we can insert a fictitious column name, and just like by magic we obtain the name of
+
one attribute, we can insert a fictitious column name, and, just like by magic, we obtain the name of
 
the first attribute.
 
the first attribute.
  
==== Obtaining Database Scheme ====
+
==== Obtaining Database Schema ====
In MS Access exist various table that can be used to obtain the name of the table of a
+
Various tables exist in MS Access that can be used to obtain the name of a table in a
particular database. In the default configuration this table is not accessible, however it's
+
particular database. In the default configuration these tables are not accessible, however it's
worth a try. The names of the table are:
+
possible to try it. The names of these table are:
  
# MSysObjects
+
* MSysObjects
# MSysACEs
+
* MSysACEs
# MSysAccessXML
+
* MSysAccessXML
  
For example if a union sql injection vulnerability exists, you can use the following query:
+
For example, if a union SQL injection vulnerability exists, you can use the following query:
  
<nowiki>
+
' UNION SELECT Name FROM MSysObjects WHERE Type = 1%00
' UNION SELECT Name FROM MSysObjects WHERE Type = 1%00
+
</nowiki>
+
  
These are the main steps that you can use to exploit a sql injection vulnerability on  
+
These are the main steps that you can use to exploit a SQL injection vulnerability on  
 
MS Access. There are also some functions that can be useful to exploit custom  
 
MS Access. There are also some functions that can be useful to exploit custom  
 
queries. Some of these functions are:
 
queries. Some of these functions are:
  
# ASC: Obtain the ascii value of a character passed as input
+
* ASC: Obtain the ASCII value of a character passed as input
# CHR: Obtain the character of the ascii value passed as input
+
* CHR: Obtain the character of the ASCII value passed as input
# LEN: Return the length of the string passed as parameter
+
* LEN: Return the length of the string passed as parameter
# IIF: Is the IF construct, for example the following statement IIF(1=1, 'a', 'b') return 'a'
+
* IIF: Is the IF construct, for example the following statement IIF(1=1, 'a', 'b') return 'a'
# MID: This function permits to extract substring, for example the following statement mid('abc',1,1)
+
* MID: This function allows you to extract substring, for example the following statement mid('abc',1,1) return 'a'
return 'a'
+
* TOP: This function allows you to specify the maximum number of results that the query should return from the top. For example TOP 1 will return only 1 row.
# TOP: This function permits to specify the maximum number of results that the query should return from the top. For example
+
* LAST: This function is used to select only the last row of a set of rows. For example the following query SELECT last(*) FROM users will return only the last row of the result.
# TOP 1 will return only 1 row.
+
# LAST: This function is used to select only the last row of a set of rows. For example the following query
+
SELECT last(*) FROM users will return only the last row of the result.
+
  
Some of these functions will be used to exploit a blind sql injection as we see in the next
+
Some of these functions will be used to exploit a blind SQL injection as we see in the next
 
paragraph. For other functions please refer to References.
 
paragraph. For other functions please refer to References.
  
=== Blind sql injection testing ===
+
=== Blind SQL Injection testing ===
Blind sql injection vulnerabilities are by no mean the most frequent type of vulnerability
+
[[Blind SQL Injection]] vulnerabilities are by no means the most frequent type of vulnerability
that you will find. Generally you find a sql injection in a parameter where no union  
+
that you will find. Generally, you find an SQL injection in a parameter where no union  
query is possible. Also, usually there is no chance to execute shell command or to read/write
+
query is possible. Also, usually, there is no chance to execute shell commands or to read/write
 
a file. All you can do is infer the result of your query. For our test we take the
 
a file. All you can do is infer the result of your query. For our test we take the
 
following example:
 
following example:
  
<nowiki>
+
http://www.example.com/index.php?myId=[sql]
http://www.example.com/index.asp?myId=[sql]
+
</nowiki>
+
  
 
where the id parameter is used in the following query:
 
where the id parameter is used in the following query:
  
<nowiki>
+
SELECT * FROM orders WHERE [id]=$myId
SELECT * FROM orders WHERE [id]=$myId
+
</nowiki>
+
  
For our test we will consider the myId parameter vulnerable to blind sqli vulnerability.
+
For our test, we will consider the myId parameter vulnerable to blind SQL injection.
We want to extract the content of the table users, in particular of the column
+
We want to extract the content of the table users, in particular, of the column
 
username (we have already seen how to obtain the name of the attributes thanks
 
username (we have already seen how to obtain the name of the attributes thanks
to the error messages and other tecniques). It is supposed that the reader alreday knowns the theory behind
+
to the error messages and other techniques). It is supposed that the reader already knows the theory behind
the blind sql injection attack, so we go straight to show some example. A typical query that
+
the blind SQL injection attack, so we go straight to show some examples. A typical query that
 
can be used to infer the first character of the username of the 10th rows is:
 
can be used to infer the first character of the username of the 10th rows is:
  
<nowiki>
+
http://www.example.com/index.php?id=IIF((select%20mid(last(username),1,1)%20from%20(select%20top%2010%20username%20from%20users))='a',0,'ko')  
http://www.example.com/index.asp?id=IIF((select%20mid(last(username),1,1)%20
+
from%20(select%20top%2010%20username%20from%20users))='a',0,'ko')  
+
</nowiki>
+
  
 
If the first character is 'a', this query will return a 0 (a "true response"), otherwise a
 
If the first character is 'a', this query will return a 0 (a "true response"), otherwise a
 
'ko' string. Now we will explain why we have used this particular query.
 
'ko' string. Now we will explain why we have used this particular query.
The first thing to point out is that with the functions IFF, MID and LAST we extract the first
+
The first thing to point out is that with the functions IFF, MID and LAST, we extract the first
character of the username of the selected row. Unfortunately, the original query returns a set of records
+
character of the username of the selected row. Unfortunately, the original query returns a set of records and not only one record, so we can't use this methodology directly. We must first select only one row. We can use the TOP function, but it only works with the first row. To select the other
and not only one record, so we can't use this methodology directly. We must first select only one row.
+
We can use the TOP function, but it only works with the first row. To select the other
+
 
queries we must use a trick. We want to infer the username of the row number 10.
 
queries we must use a trick. We want to infer the username of the row number 10.
 
First we use the TOP function to select the first ten rows with the query:
 
First we use the TOP function to select the first ten rows with the query:
  
<nowiki>
+
SELECT TOP 10 username FROM users
SELECT TOP 10 username FROM users
+
</nowiki>
+
  
Then we extract from this set the last row with the function LAST. Once we have only one row and  
+
Then, we extract from this set the last row with the function LAST. Once we have only one row and  
exactly the row that we want, we can use the IFF, MID and LAST function to infer the value
+
exactly the row that we want, we can use the IFF, MID and LAST functions to infer the value
 
of the username.
 
of the username.
It may be interesting to notice the use of the IFF. In our example we use IFF to return a number or a string.
+
It may be interesting to note the use of the IFF function. In our example we use IFF to return a number or a string. With this trick we can distinguish when we have a true response or not. This is because id is of a numeric type, so if we compare it with a string we obtain a SQL error, otherwise with the 0 value we have no errors. Of course if the parameter was of type string we can use different values. For example, we can have the following query:
With  
+
this trick we can distinguish when we have a true response or not. This is because id is of numeric type,
+
so if we compare it with a string we obtain a sql error, otherwise with the 0 value we have
+
no errors. Of course if the parameter was of type string we can use different values. For example
+
we can have the following query:
+
  
<nowiki>
+
http://www.example.com/index.php?id='%20AND%201=0%20OR%20'a'=IIF((select%20mid(last(username),1,1)%20from%20(select%20top%2010%20username%20from%20users))='a','a','b')%00
http://www.example.com/index.asp?id=inexistenValueHere'%20or%20'a'=IIF((select%20mid(last(username),1,1)%20from%20(select%20top%2010%20username%20from%20users))='a','a','b')%00
+
</nowiki>
+
  
that returns a query that is always true if the first character is 'a' or a query that is always false in the other case (assuming that in the users table there is no id with value 'inexistenValueHere')
+
that returns a query that is always true if the first character is 'a' or a query that is always false in the other case.
  
Thanks to this metodology we can infer the value of the username. To understand when we have  
+
This method allows us to infer the value of the username. To understand when we have  
 
obtained the complete value we have two choices:
 
obtained the complete value we have two choices:
  
# We try all the printable values, when no one is valid then we have the complete value.
+
# We try all the printable values; when no one is valid then we have the complete value.
 
# We can infer the length of the value (if it's a string value we can use the LEN function) and stop when we have found all the characters.
 
# We can infer the length of the value (if it's a string value we can use the LEN function) and stop when we have found all the characters.
  
 
== Tricks ==
 
== Tricks ==
Sometimes we are blocked by some filtering function, here we see some tricks to bypass these filters.
+
Sometimes we are blocked by some filtering function. Here we see some tricks to bypass these filters.
  
 
=== Alternative Delimiter ===
 
=== Alternative Delimiter ===
Some filter strips away the space from the input string. We can bypass this filter using
+
Some filters strip away the space from the input string. We can bypass these filters using
 
the following values as delimiter instead of the white space:
 
the following values as delimiter instead of the white space:
  
<nowiki>
+
'''
 
9
 
9
 
a
 
a
Line 164: Line 134:
 
2d
 
2d
 
3d
 
3d
</nowiki>
+
'''
  
 
For example we can execute the following query:
 
For example we can execute the following query:
  
<nowiki>
+
http://www.example.com/index.php?username=foo%27%09or%09%271%27%09=%09%271
http://www.example.com/index.asp?username=foo%27%09or%09%271%27%09=%09%271
+
</nowiki>
+
  
 
to bypass a hypothetical login form.
 
to bypass a hypothetical login form.
Line 178: Line 146:
 
* http://www.techonthenet.com/access/functions/index_alpha.php
 
* http://www.techonthenet.com/access/functions/index_alpha.php
 
* http://www.webapptest.org/ms-access-sql-injection-cheat-sheet-IT.html
 
* http://www.webapptest.org/ms-access-sql-injection-cheat-sheet-IT.html
 +
 +
[[Category:FIXME| is the second link correct?]]

Revision as of 10:00, 9 October 2012

This article is part of the new OWASP Testing Guide v4. 
At the moment the project is in the REVIEW phase.

Back to the OWASP Testing Guide v4 ToC: https://www.owasp.org/index.php/OWASP_Testing_Guide_v4_Table_of_Contents Back to the OWASP Testing Guide Project: http://www.owasp.org/index.php/OWASP_Testing_Project

Contents


Short Description of the Issue

This article describes how to exploit SQL Injection vulnerabilities when the backend database is MS Access. In particular, the article focuses on how to exploit Blind SQL Injection. After an initial introduction on the typical functions that are useful to exploit a SQL Injection vulnerability, a method to exploit Blind SQL Injection will be discussed.

Black Box testing and example

Standard Test

First of all, let's show a typical example of SQL error that can encounter when a test is executed:

Fatal error: Uncaught exception 'com_exception' with message 'Source: Microsoft JET Database Engine
Description:

That means that maybe we are testing an application with an MS Access Database backend.

Unfortunately, MS Access doesn't support any comment character in the SQL query, so it is not possible to use the trick of inserting the characters /* or -- or # to truncate the query. On the other hand, we can fortunately bypass this limit with the NULL character. If we insert the char %00 at some place in the query, all the remaining characters after the NULL are ignored. That happens because, internally, strings are NULL terminated. However, the NULL character can sometimes cause troubles. We can notice that there is another value that can be used in order to truncate the query. The character is 0x16 (%16 in URL encoded format) or 22 in decimal. So if we have the following query:

SELECT [username],[password] FROM users WHERE [username]='$myUsername' AND [password]='$myPassword'

we can truncate the query with the following two URLs:

http://www.example.com/index.php?user=admin'%00&pass=foo
http://www.example.com/index.php?user=admin'%16&pass=foo

Attributes enumeration

In order to enumerate the attributes of a query, it is possible to use the same method used for the database MS SQL Server. In short, we can obtain the name of the attributes by error messages. For example, if we know the existence of a parameter because we got it by an error message due to the ' character, we can also know the name of the remaining attributes with the following query:

' GROUP BY Id%00

In the error message received we can see that the name of the next attribute is shown. We iterate the method until we obtain the name of all the attributes. If we don't know the name of at least one attribute, we can insert a fictitious column name, and, just like by magic, we obtain the name of the first attribute.

Obtaining Database Schema

Various tables exist in MS Access that can be used to obtain the name of a table in a particular database. In the default configuration these tables are not accessible, however it's possible to try it. The names of these table are:

  • MSysObjects
  • MSysACEs
  • MSysAccessXML

For example, if a union SQL injection vulnerability exists, you can use the following query:

' UNION SELECT Name FROM MSysObjects WHERE Type = 1%00

These are the main steps that you can use to exploit a SQL injection vulnerability on MS Access. There are also some functions that can be useful to exploit custom queries. Some of these functions are:

  • ASC: Obtain the ASCII value of a character passed as input
  • CHR: Obtain the character of the ASCII value passed as input
  • LEN: Return the length of the string passed as parameter
  • IIF: Is the IF construct, for example the following statement IIF(1=1, 'a', 'b') return 'a'
  • MID: This function allows you to extract substring, for example the following statement mid('abc',1,1) return 'a'
  • TOP: This function allows you to specify the maximum number of results that the query should return from the top. For example TOP 1 will return only 1 row.
  • LAST: This function is used to select only the last row of a set of rows. For example the following query SELECT last(*) FROM users will return only the last row of the result.

Some of these functions will be used to exploit a blind SQL injection as we see in the next paragraph. For other functions please refer to References.

Blind SQL Injection testing

Blind SQL Injection vulnerabilities are by no means the most frequent type of vulnerability that you will find. Generally, you find an SQL injection in a parameter where no union query is possible. Also, usually, there is no chance to execute shell commands or to read/write a file. All you can do is infer the result of your query. For our test we take the following example:

http://www.example.com/index.php?myId=[sql]

where the id parameter is used in the following query:

SELECT * FROM orders WHERE [id]=$myId

For our test, we will consider the myId parameter vulnerable to blind SQL injection. We want to extract the content of the table users, in particular, of the column username (we have already seen how to obtain the name of the attributes thanks to the error messages and other techniques). It is supposed that the reader already knows the theory behind the blind SQL injection attack, so we go straight to show some examples. A typical query that can be used to infer the first character of the username of the 10th rows is:

http://www.example.com/index.php?id=IIF((select%20mid(last(username),1,1)%20from%20(select%20top%2010%20username%20from%20users))='a',0,'ko') 

If the first character is 'a', this query will return a 0 (a "true response"), otherwise a 'ko' string. Now we will explain why we have used this particular query. The first thing to point out is that with the functions IFF, MID and LAST, we extract the first character of the username of the selected row. Unfortunately, the original query returns a set of records and not only one record, so we can't use this methodology directly. We must first select only one row. We can use the TOP function, but it only works with the first row. To select the other queries we must use a trick. We want to infer the username of the row number 10. First we use the TOP function to select the first ten rows with the query:

SELECT TOP 10 username FROM users

Then, we extract from this set the last row with the function LAST. Once we have only one row and exactly the row that we want, we can use the IFF, MID and LAST functions to infer the value of the username. It may be interesting to note the use of the IFF function. In our example we use IFF to return a number or a string. With this trick we can distinguish when we have a true response or not. This is because id is of a numeric type, so if we compare it with a string we obtain a SQL error, otherwise with the 0 value we have no errors. Of course if the parameter was of type string we can use different values. For example, we can have the following query:

http://www.example.com/index.php?id='%20AND%201=0%20OR%20'a'=IIF((select%20mid(last(username),1,1)%20from%20(select%20top%2010%20username%20from%20users))='a','a','b')%00

that returns a query that is always true if the first character is 'a' or a query that is always false in the other case.

This method allows us to infer the value of the username. To understand when we have obtained the complete value we have two choices:

  1. We try all the printable values; when no one is valid then we have the complete value.
  2. We can infer the length of the value (if it's a string value we can use the LEN function) and stop when we have found all the characters.

Tricks

Sometimes we are blocked by some filtering function. Here we see some tricks to bypass these filters.

Alternative Delimiter

Some filters strip away the space from the input string. We can bypass these filters using the following values as delimiter instead of the white space:

9 a c d 20 2b 2d 3d

For example we can execute the following query:

http://www.example.com/index.php?username=foo%27%09or%09%271%27%09=%09%271

to bypass a hypothetical login form.

References

Whitepapers