본문 바로가기

Security_Study

Hacking SQL Server Stored Procedures – Part 3: SQL Injection

728x90

If you read the first two blogs in this series then you already know that SQL Server roles and privileges can be misconfigured in ways that allow users to escalate their privileges to a sysadmin (database administrator). Even when those roles and privileges are configured correctly, sometimes stored procedures can still be a threat. In this blog I’ve covered how SQL injection can be identified and exploited to escalate privileges in SQL Server stored procedures when they are configured to execute with higher privileges using the WITH EXECUTE AS clause or certificate signing. I’ve also provided a lab setup guide for those of you who want to try this at home. To my knowledge this work with SQL Server 2005 to 2014. This should be interesting to penetration testers, application developers, and dev-ops. Feel free to jump down to the attack section if you’re not big on labs. :)

Below is a summary of the topics being covered:

SQL Injection Primer

If you’re not familiar with SQL injection I thought it would make sense to provide a little definition. OWASP defines SQL injection as an “…attack that consists of insertion or “injection” of a SQL query via the input data from the client to the application”. This holds true when attacking stored procedures in SQL Server as well, but with at least one noticeable difference. To my knowledge injection into stored procedures is only possible when dynamic SQL is being used in the procedure. Luckily (for attackers) it’s actually pretty common for developers to use dynamic SQL in procedures, because it allows them to create and execute flexible queries on the fly. It only becomes a problem when variables can be controlled by an attacker and they are not parameterized. That issue is amplified when procedures are configured to run as a sysadmin login, because they can be used by attackers to escalate their privilege to a sysadmin as well.

For more information on SQL injection take a look at https://www.owasp.org/index.php/SQL_Injection. Also, here are some of Microsoft’s recommendations for safe dynamic SQL http://msdn.microsoft.com/en-us/library/bb669091(v=vs.110).aspx.

Setting up the Lab Environment

Below I’ve provided some basic steps for setting up a SQL Server instance that can be used to replicate the scenarios covered in this blog/lab.

  1. Download the Microsoft SQL Server Express install that includes SQL Server Management Studio. It can be download at http://msdn.microsoft.com/en-us/evalcenter/dn434042.aspx
  2. Install SQL Server by following the wizard, but make sure to choose mixed-mode authentication and run the service as LocalSystem for the sake of the lab.
  3. Log into the SQL Server with the SA account setup during installation using the SQL Server Management Studio application.
  4. Press the “New Query” button and use the TSQL below to create a least privilege login.
  5. Set the “master” database as trustworthy. Configuring a database as trusted using the “is_trustworthy_on” flag allows certain objects within the database to access external resources like network shares, mail functions, and objects in other databases that are on the same SQL Server instance. This flag is set to disabled by default (except MSDB), but some DBAs still choose to enable it for a number of reasons. For the purpose of this lab, we will be enabling it so we can execute operating system commands via xp_cmdshell from within stored procedures setup using the WITH EXECUTE AS OWNER (sa in this case). However, it should be noted the setting also affects CLR-based stored procedures, UDFs, and Triggers. For more information on the “is_trustworthy_on” flag you can take a look at http://support.microsoft.com/kb/2183687.First, configure the “MASTER” database as trustworthy.

    Then verify that the configuration was set with the following query.

    Below is a screenshot of the expected result.

    1

  6. Use the TSQL below to enable xp_cmdshell. Enabling this now will simplify the labs later, but it could be enabled by an attacker even if we didn’t enable it.

Creating a Vulnerable Stored Procedure using WITH EXECUTE AS

In this section we’ll create the first vulnerable stored procedure. This one will use the WITH EXECUTE AS clause to run as a sysadmin. It will also be configured to use dynamic SQL that is vulnerable to SQL injection. Follow the instructions below to get it setup.

  1. Log into the SQL Server with the “sa” login and create the vulnerable stored procedure using the TSQL below. The stored procedure will return a list of database names that match the search string passed to it, as well as the “tempdb” database.
  2. Run the query below to test the sp_sqli procedure. It should return the “master” and “tempdb” databases.

    2

Finding Potentially Vulnerable Stores Procedures using WITH EXECUTE AS

In this section I’ve provided a basic process for finding custom stored procedures that use the WITH EXECUTE AS clause and may be vulnerable to SQL injection.  Please be aware that not all logins/database users will have the privileges required to view the source of all stored procedures. However, from a blue team perspective this is a nice way to quickly identify low hanging fruit. For the sake of simplicity just run these queries using the “sa” login.

  1. Finding Databases that are Trusted and Owned by a Sysadmin
    You should really review all of the databases, but databases owned by sysadmins are a good place to start if you’re tight on time, because any procedures that use the WITH EXECUTE AS OWNER clause will automatically be running as a sysadmin. In the example below you should only see the “MASTER” database returned by the query.

    3
  2. Finding Custom Stored Procedures
    The query below will return a list of functions and stored procedures for the target database. In this case, “MASTER” is being used, but in the real world you’ll want to swap it out for your target database name. In this lab you should see “sp_sqli” returned by the query.

    4
  3. Finding Custom Stored Procedures using WITH EXECUTE AS 
    By default stored procedures are configured to run as the caller. In other words, the login used to execute it. However, stored procedures can also be created to execute with another login’s privileges. Below are the five options, but we will be focusing on OWNER in our attack later. For more information visit http://msdn.microsoft.com/en-us/library/ms188354.aspx.

    • WITH EXECUTE AS OWNER: Meaning the owner of the procedure
    • WITH EXECUTE AS SELF: Meaning the creator/modifier of the procedure
    • WITH EXECUTE AS ‘USERNAME': Meaning a specific database user
    • WITH EXECUTE AS LOGIN: Meaning a specific login
    • WITH EXECUTE AS CALLER: Meaning the database user executing the procedure

    Below is a query that should only return stored procedures using the WITH EXECUTE AS clause. You should see sp_sqli in the list.

    5

  4. Finding Stored Procedures that use the WITH EXECUTE AS and Dynamic SQL
    The query below will go a little further than our last step. It will actually locate stored procedures in the “master” database using dynamic SQL that are configured to use the WITH EXECUTE AS clause. You should only see “sp_sqli” in the list again.

    6

It might be worth noting that some applications may have thousands of custom stored procedures. So if you don’t feel like taxing a production server you can simply export them and grep for the same keywords you see in the query. SQL Server Management Studio will allow you to save all results as an excel file, but if you’re looking for a more scriptable option you can use the little PowerShell script I wrote for exporting stored procedure code from all accessible databases. The script can be downloaded from:https://raw.githubusercontent.com/nullbind/Powershellery/master/Stable-ish/MSSQL/Get-SqlServer-Escalate-SpSource.psm1

Below is a basic command example.

Below is a sample screen shot of expected results. Note: The script doesn’t search the TempDB, MSDB, or Model database for custom stored procedures.

7

After the script is run it exports all of the stored procedures into a csv file and .sql files.

8

Creating a Vulnerable Stored Procedure Signed with a Certificate

Another way to provide stored procedures with privileges to access objects external to the current database is by signing them with a certificate. Some of the advantages include allowing a least privilege login to execute the stored procedure with elevated privileges WITHOUT having to:

  • Assign logins excessive privileges directly to logins/roles. For example, db_owner or sysadmin.
  • Assign logins excessive IMPERSONATE privileges used to impersonate users and logins on demand with the EXECUTE AS command.
  • Configure the stored procedure to run as another login using the WITH EXECUTE AS clause.
  • Flag the database as trustworthy, which could weaken other controls.

All those things are great and give me a warm fuzzy feeling. However, at the end of the day if a signed procedure uses variables that aren’t parametrized and the attacker has control over at least one of them then it’s still likely to be vulnerable to SQL injection.

Ok, enough of my yammering, let’s build a vulnerable procedure signed with a certificate using the instructions below.

    1. Create a new stored procedure in the current database named “sp_sqli2”.
    2. Create a master key for the database.
    3. Create certificate for the “sp_sqli2” procedure. This can be configured with a password, but for the sake of simplicity I left it out.
    4. Create a new login named “sp_sqli2_login” from the “sp_sqli2_cert” certificate. No password is defined for the login for the sake of simplicity, but in the real world one should be set.
    5. Sign the “sp_sqli2” stored procedure with the new “sp_sqli2_cert” certificate. This can use a password, but for the sake of simplicity I left it out.
    6. Add the “sp_sqli2_login” login to the sysadmins role.
    7. Allow members of the PUBLIC role to execute it.

For more information on signing procedures with certificates check out the Microsoft site athttp://msdn.microsoft.com/en-us/library/bb283630.aspx.

Finding Potentially Vulnerable Stored Procedures Signed with a Certificate

In this section I’ll provide a basic process for finding procedures signed with a certificate that may be vulnerable to SQL injection.   Please note that not all logins/database users will have the privileges required to view the source of all stored procedures. However, from a blue team perspective these are a nice way to quickly identify low hanging fruit. For the sake of simplicity just run these queries using the “sa” login.

  1. If you are interested in taking a quick look at which logins were created from a certificate then you can use the query below.

    It should return something like the results in the screenshot below.

    9

  2. Now let’s try finding procedures signed with a certificate for the current database that also have logins that were generated from them.

    The expected results should include the “sp_sqli2” stored procedure and look something like the screenshot below.

    10

  3. To take it a little further now we can expand the query and search for stored procedures that also appear to contain dynamic SQL.

    The expected result is shown in the screenshot below.

    11

Attacking the Stored Procedures

Below I’ve provided some basic exercises to get you familiar with the SQL injection into stored procedures.

Before we start

The goal of this lab is to escalate our privileges in SQL Server by exploiting stored procedures that use the WITH EXECUTE AS OWNER clause and certificate signatures. However, I also want show how the trustworthy flag affects our results. So for now let’s turn it off. Make sure to disable it with the “sa” login.

Note: Make sure to login with the “MyUser” login for all of the labs below.

  • Test the Basic Functionality
    Run the query below to get the expected output of the “sp_sqli” and “sp_sqli2” stored procedures. This is just to make sure everything is working.

    You should see the same results for both stored procedures. Below is a screenshot of the expected result.

    12

  • Injection 1: Commenting
    The injection below should comment out the “OR” and only return the “master” database. This is a basic example of SQL injection.

    You should see the same results for both of the stored procedures. Below is a screenshot of the expected result.

    13

  • Injection 2: Verifying execution as another user
    The injection below will return the user context running outside and inside of the stored procedures.

    Below is a screenshot of the expected result.

    14

    “MyUser” should be returned outside the stored procedure. You should also notice that inside the “sp_sqli” procedure (WITH EXECUTE AS OWNER) is running as the “sa” login.   However, the “sp_sqli2” procedure (signed) still appears to be running as “MyUser”. As we’ll see in a moment this is not always reflective of the privilege we actually have inside the stored procedures.

  • Injection 3: Verify sysadmin privileges 
    The injection below will return the sysadmin status outside and inside of the stored procedures. 1 means the current login has sysadmin privileges, and a 0 means it doesn’t.

    Below is a screenshot of the expected result.

    15

    You should notice that the “sp_sqli” procedure returns a 0 even though it’s running as the “sa” login. That’s because the “master” is not set as trustworthy. Conversely, we can see that the signed procedure “sp_sqli2” can execute with elevated privileges even though the trustworthy flag has not been set in the “master” database.

  • Injection 4: OS command execution
    First let’s verify that we can simply execute the “xp_cmdshell” procedure as the current user “MyUser”.

    Below is a screenshot of the expected result.

    16

    You should be see some type of access denied error. Now let’s try that inside the “sp_sqli” procedure.

    Below is a screen shot of the expected result.

    17

    Once again we are getting access denied, because the trustworthy flag has not been set on the “master” database. Finally, let’s try the same injection on the signed procedure “sp_sqli2”.

    Below is a screenshot of the expected output.

    18

    This time it works! I think the conclusion here is that although signing is the best option overall, it still comes with its own risks, because it doesn’t require the trustworthy flag to be set.

  • Injection 5: OS command execution in a trustworthy database
    Ok, let’s sign in as “sa” and set the “MASTER “database to trustworthy again.

    Now let’s try that command execution inside the “sp_sqli” procedure again. This time it should work!

    Below is a screen shot of the expected result.

    19

    Tada! As you can see when you’re trying to escalate privileges using a stored procedure that uses the “WITH EXECUTE AS” clause the trustworthy setting makes a big difference.

Fixing the Stored Procedures

Microsoft has some pretty good recommendations to help prevent these types of attacks so I recommend checking out their web site for more information. Naturally, the fixes will vary depending on the environment, application, and use cases, but below are a few options to get you started.

  1. Use parameterized queries in stored procedures to help prevent SQL injection. Below is an example of how to fix the first stored procedure from the lab. Removals in read, and additions are in black. Sign in as “sa” login to create it.

    Now when we attempt to inject SQL into the “sp_sqli_fix” procedure with the “MyUser” login the injection fails and only the tempdb is returned.

    Expect results shown in the screenshot below.

    20

  2. If it’s possible, set TRUSTWORTHY to off for the affected databases (excluding MSDB). This will help prevent the execution of xp_cmdshell and other bad things from within stored procedures that use WITH EXECUTE AS. It will also enforce a sandbox that only allows the stored procedure to access information associated with its own database.

    Note: Be careful, there are some legit use cases for this and you could end up breaking things! So make sure you know what you’re doing.

  3. Make sure custom stored procedures aren’t owned by sysadmins. For example, if the database owner of an application database is a sysadmin consider changing the owner to an account with less privilege so stored procedures using WITH EXECUTE AS OWNER will have less impact if other vulnerabilities exist.
  4. Don’t assign the PUBLIC role with execute privileges on custom stored procedures. Only assign it to users or roles that require it. This will help prevent low privileged users from accessing potentially dangerous custom stored procedures.

  5. Use stored procedures signed with certificates instead of the WITH EXECUTE AS clause when possible. It’s worth it for all the reasons I provided when we created the “sp_sqli2” stored procedure earlier in this blog.

Wrap Up

The issues covered in this blog/lab were intended to help pentesters, developers, and dev-ops understand how a few common misconfigurations and coding mistakes can lead to the compromise of an entire SQL Server instance via stored procedure SQL injection. It’s worth noting that the same techniques can be used via SQL injection through a web or thick application. I just thought it would be easier to understand if it was exploited via a direct database connection. Hopefully the information is useful. Have fun with it, and don’t forget to hack responsibly. :)

PS: For those of you looking to reset your lab when you’re all done use the TSQL below:

Other Blogs in this Series

References

728x90