Testing for MS Access
This article is part of the new OWASP Testing Guide v4.
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: https://www.owasp.org/index.php/OWASP_Testing_Project
Short Description of the Issue
As explained in the generic [SQL injection] section, SQL injection vulnerabilities occur whenever user-supplied input is used during the construction of a SQL query without being adequately constrained or sanitized. This class of vulnerabilities allows an attacker to access the SQL servers and execute SQL code under the privileges of the user used to connect to the database.
In this section some SQL Injection techniques that utilize specific features of Microsoft Access will be discussed.
Black Box testing and example
Fingerprinting the specific database technology while testing SQL-powered application is the first step to properly asses potential vulnerabilities. A common approach involves injecting standard SQL injection attack patterns (e.g. single quote, double quote, ...) in order to trigger database exceptions.
Assuming that the application does not handle exceptions with custom pages, it is possible to fingerprint the underline DBMS by observing typical error messages. Depending on the specific web technology used, MS Access driven applications will result in the following errors:
Fatal error: Uncaught exception 'com_exception' with message 'Source: Microsoft JET Database Engine
Microsoft JET Database Engine error '80040e14'
In both cases, we have a confirmation that we're testing an application using MS Access database.
Unfortunately, MS Access doesn't support typical operators that are traditionally used during SQL injection testing, including:
- No comments characters
- No stacked queries
- No LIMIT operator
- No SLEEP or BENCHMARK alike operators
- and many others
Nevertheless, it is possible to emulate those functions by combining multiple operators or by using alternative techniques.
As mentioned, it is not possible to use the trick of inserting the characters
# in order to truncate the query. However, we can fortunately bypass this limitation by injecting the 'null' character. Using a null byte
a SQL query will result in MS Access ignoring all remaining characters. This can be explained by considering that all strings are NULL terminated in the internal representation used by the database. It is worth mentioning that the 'null' character can sometimes cause troubles too as it may truncate strings at the web server level. In those situations, we can however employ another character: 0x16 (%16 in URL encoded format).
Considering the following query:
SELECT [username],[password] FROM users WHERE [username]='$myUsername' AND [password]='$myPassword'
We can truncate the query with the following two URLs:
LIMIT operator is not implemented in MS Access, however it is possible to limit the number of results by using the
LAST operators instead.
By combining both operators, it is possible to select specific results.
String concatenation is possible by using
& (%26) and
+ (%2b) characters.
There are also many other functions that can be used while testing SQL injection, including but not limited to:
- 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 are essential to exploit blind SQL injections. For other advanced operators, please refer to the references.
In order to enumerate the attributes of a query, it is possible to use a common error-based technique. In short, we can obtain the attributes name by analyzing error messages and repeating the query with different selectors. For example, assuming that we know the existence of a parameter, we can also obtain 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. At this point, we iterate the method until we obtain the name of all attributes. If we don't know the name of at least one attribute, we can insert a fictitious column name and obtain the name of the first attribute within the error message.
Obtaining Database Schema
Various system tables exist by default in MS Access that can be potentially used to obtain table names. Unfortunately, in the default configuration of recent MS Access database releases, these tables are not accessible. Nevertheless, it is always worth trying.
For example, if a union SQL injection vulnerability exists, you can use the following query:
' UNION SELECT Name FROM MSysObjects WHERE Type = 1%00
Alternatively, it is possible to infere the database [HERE]
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:
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:
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:
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:
- 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.
Sometimes we are blocked by some filtering function. Here we see some tricks to bypass these filters.
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:
to bypass a hypothetical login form.