SQL Server Security team is starting a new blog

  First of all, I am really sorry for not writing anything for quite a long time.

 

  The SQL Server Security team is starting a new blog: SqlSecruity. Starting today I will be writing all the new SQL Server articles in the new SQL Server Security blog, and I am keeping this blog for other security related topics that may be interesting outside SQL Server.

 

  Thanks a lot,

 

 -Raul Garcia

  SDE/T

  SQL Server Engine

Link to MSDN forum discussion: "Yet another question on Application security.... "

   I am adding a link to one of the MSDN SQL Server Security forum discussion regarding application security (i.e. restricting access to database resources based on the application): Yet another question on Application security....

  

   Please feel free to post any additional questions or feedback either on the forum or on this space.

 

  Thanks a lot,

-Raul

Posted 13 July 07 10:09 by raulga | 2 Comments   
Disaster Recovery: What to do when the SA account password is lost in SQL Server 2005

You may have faced the issue of losing the SQL Server SA password. Perhaps you followed the security best-practice of removing the  builtin\Administrators from the sysadmin server role,  and no one  you can find is in the sysadmin role.   At this point you may think that your only options are to reinstall SQL Server and attach the databases, or to  directly access the master database files, which may potentially damage the data.

 

SQL Server 2005 provides a better disaster recovery option for this scenario that is non-intrusive for master DB and that will help you preserve any objects and data stored in master DB (such as logins, certificates, Service Master Key, etc.) intact. Members of the Windows Administrators group now have access to SQL Server when SQL Server is in started in single-user mode, also known as “maintenance mode “.

 

Using the single-user mode, SQL Server 2005 prevents a  Windows Administrator to abuse this privilege to act on behalf of the sysadmin without being noticed. This allows Windows Administrator accounts to perform certain maintenance tasks, such as installing patches.

 

In order to start SQL Server in single-user mode, you can add the parameter “-m” at the command line. You can also use the SQL Server Configuration Manager tool, which provides proper controls for the file access and other privileges. To use the Configuration Manager tool to recover your system, use the following steps:

 

1. Open the Configuration Manager tool from the "SQL Server 2005| Configuration" menu

2. Stop the SQL Server Instance you need to recover

3. Navigate to the “Advanced” tab, and in the Properties text box add “;–m” to the end of the list in the “Startup parameters” option

4. Click the “OK” button and restart the SQL Server Instance

 

NOTE: make sure there is no space between “;” and “-m”, the registry parameter parser is sensitive to such typos. You should see an entry in the SQL Server ERRORLOG file that says “SQL Server started in single-user mode.”

 

5. After the SQL Server Instance starts in single-user mode, the Windows Administrator account is able to connect to SQL Server using the sqlcmd utility using Windows authentication. You can use Transact-SQL commands such as "sp_addsrvrolemember" to add an existing login (or a newly created one) to the sysadmin server role.

The following example adds the account "Buck" in the "CONTOSO" domain to the SQL Server "sysadmin" role:

 

EXEC sp_addsrvrolemember 'CONTOSO\Buck', 'sysadmin';

GO

 

6. Once the sysadmin access has been recovered, remove the “;-m” from the startup parameters using the Configuration Manager and restart the SQL Server Instance

 

 

Important Security Notes:

This process should only be used for disaster recovery when no other method to access the system with a privileged (i.e. sysadmin or equivalent) is available.

 

This process allows a Windows Administrator account to override their privileges within SQL Server. It requires explicit and intrusive actions that can be monitored and detected, including:

·         Stop SQL Server and restart it in single use mode

·         Connecting to SQL Server using Windows credentials

 

 

  Special thanks to Buck Woody (http://blogs.msdn.com/buckwoody/) for his help in writing this article.

Posted 12 July 07 10:19 by raulga | 3 Comments   
Filed under
Dynamic SQL and digital signatures in SQL Server 2005

   As I already mentioned, dynamic SQL is a quite powerful, but also quite dangerous. In SQL Server 2005 we introduced a new feature that is also quite powerful and when used properly can be quite useful; but it is important to learn and understand any such feature in order to use it properly.  In this small article I will describe a little bit more about the interaction between these two features.

 

  First, I will describe briefly digitally signing modules in SLQ Server 2005, but this explanation will not be thorough, so I strongly recommend reading the references I include at the end of the article to learn more about this subject. After that I will explain how digital signatures affect dynamic SQL, what are the pitfalls you should try to avoid.

 

  Digitally signing modules

 

Hopefully you are familiar with the concept of digital signatures. In SQL server 2005 we introduced digital signatures for modules, the concept per se is not far from the one you may be familiar with: have a piece of data (in this case I will talk about a T-SQL module) and use a digital certificate and its private key to create a signature; this signature and its relationship with the certificate can be used to a) verify that the original piece of data has not been modified since it was signed and b) that the data was signed by the owner of the give certificate.

 

Before SQL server 2005 there were very few reliable mechanisms to verify that the code issued by any ISV was not tampered. While typically not a problem for the consumer of the module as sysadmins and DBOs typically have good control (based on permissions and roles) on who can write executable modules (SPs, UDFs, etc.), ISVs have sometimes difficulties validating any unsupported modifications to their applications.

 

In SQL Server 2005, it is possible for ISVs to deliver digitally signed modules to their customers without granting any additional permissions or privileges based on the signature itself. This may allow them to help in support scenarios where customers (either by mistake or as an explicit act from a rogue employee) modified a module and their application is in an unsupported state. For example:

 

CREATE CERTIFICATE [cert_demo01]

  WITH SUBJECT = 'Cert demo - simple siganture'

go

 

CREATE PROC [sp_demo01]

AS

  PRINT 'hello world'

go

 

ADD SIGNATURE TO [sp_demo01] BY CERTIFICATE [cert_demo01]

go

 

-- Let's see the signature

declare @thumb varbinary(32)

select @thumb = thumbprint from sys.certificates where name = 'cert_demo01'

select object_name(major_id) as 'object_name',

  crypt_property as signature

  from sys.crypt_properties where thumbprint = @thumb

go

 

-- Now alter the module to verify that the signature is gone

ALTER PROC [sp_demo01]

AS

  PRINT 'hello world again'

go

-- Let's see the signatures again... should be empty

declare @thumb varbinary(32)

select @thumb = thumbprint from sys.certificates where name = 'cert_demo01'

select object_name(major_id) as 'object_name',

  crypt_property as signature

  from sys.crypt_properties where thumbprint = @thumb

go

  

 Another common issue DB administrators may face is the requirement to allow users o access some of the resources (such as tables) only via limited modules (i.e. users who should be able to execute an application, should not be able to access the tables directly). In SQL Server 2000, the common mechanism to achieve this was ownership chaining (OC), but OC has a lot of limitations because of its own nature (limited to DML, permissions are completely bypassed, security considerations for allowing cross-DB OC, etc.).

 

  In SQL Server 2005, using digital signatures can be used to modify the execution context and add a user (mapped from the certificate) as a secondary identity that will affect the permission checks for the duration of the module (without bleeding to a subsequent module). Another way to explain this usage of signatures is to “extend permissions via signature” or “granting permission to the module”. In the following example I have some additional explanations in the comments:

 

CREATE CERTIFICATE [cert_demo02] WITH SUBJECT = 'Cert demo - signature as secondary identity'

go

 

-- Create a schema to store all resources, and a loginless user to be the schema owner

CREATE USER [usr_resources_owner] WITHOUT LOGIN

go

CREATE SCHEMA [sch_resources] AUTHORIZATION [usr_resources_owner]

go

 

-- Create a schema to store all modules, and a loginless user to be the schema owner

-- this will break Ownership chaining

CREATE USER [usr_module_owner] WITHOUT LOGIN

go

CREATE SCHEMA [sch_modules] AUTHORIZATION [usr_module_owner]

go

 

-- mCreate a simple table

CREATE TABLE [sch_resources].[t_Demo02]( data nvarchar(100) )

go

 

-- and a module to access it

CREATE PROC [sch_modules].[sp_demo02]

AS

  SELECT * FROM sys.user_token ORDER BY usage, type, name

  SELECT * FROM [sch_resources].[t_Demo02]

go

 

-- Add a siganture to the newly created module

ADD SIGNATURE TO [sch_modules].[sp_demo02] BY CERTIFICATE [cert_demo02]

go

 

-- Create a user for our signing cert, but no permissions granted yet

CREATE USER [usr_cert_demo02] FOR CERTIFICATE [cert_demo02]

go

 

-- Now let's create a low-privielged user to test our module

CREATE USER [usr_lowpriv] WITHOUT LOGIN

go

GRANT EXECUTE ON [sch_modules].[sp_demo02] TO [usr_lowpriv]

go

 

-- Let's see what happens when the low priv user executes the module:

EXECUTE AS USER = 'usr_lowpriv'

go

EXEC [sch_modules].[sp_demo02]

go

-- What happened?

-- We can see that the user token during the module execution is different than

-- the token outside the call (below).

-- The signature is affecting the execution context based on the module siganture

-- and any permissions granted to the signing certificate will be added to the token.

--

SELECT * FROM sys.user_token ORDER BY usage, type, name

go

REVERT

go

 

-- Grant permission to access the table to the certificate

GRANT SELECT ON [sch_resources].[t_Demo02] TO [usr_cert_demo02]

go

 

-- and run the script from above again

-- Let's see what happens when the low priv user executes the module:

EXECUTE AS USER = 'usr_lowpriv'

go

EXEC [sch_modules].[sp_demo02]

go

REVERT

go

 

-- Now, it is impornat to notice that the siganture is added to the current token

-- not completetly replaced, and it is also importnat to notice that the permission checks

-- will still be evaluated based on this token (i.e. OC will bypass permission checks).

--

 

CREATE USER [usr_DeniedPrivs] WITHOUT LOGIN

go

-- Permission to execute the module, but not to access the table

--

DENY SELECT ON [sch_resources].[t_Demo02] TO [usr_DeniedPrivs]

GRANT EXECUTE ON [sch_modules].[sp_demo02] TO [usr_DeniedPrivs]

go

 

-- Should fail to select from table thanks to the DENY permission

EXECUTE AS USER = 'usr_DeniedPrivs'

go

EXEC [sch_modules].[sp_demo02]

go

REVERT

go

 

Digital signatures and dynamic SQL

 

  Using the digital signature as a mechanism to extend permissions affects any operation on the body of the signed module, including dynamic SQL executed in it. What does it mean? It means that the signer should understand that the module to be signed will execute dynamic code that is also going to be signed. For example:

 

CREATE PROC [sch_modules].[sp_demo03] ( @Id int )

AS

  DECLARE @cmd nvarchar(max)

  DECLARE @params nvarchar(max)

  -- the follwoing code will be also afected by the siganture

  SET @cmd = 'SELECT * FROM sys.user_token ORDER BY usage, type, name; SELECT * FROM [sch_resources].[t_Demo02] WHERE Id = @Id;'

  SET @params = '@Id int'

  EXEC sp_executesql @cmd, @params, @Id = @Id

go

 

-- Add a siganture to the newly created module

ADD SIGNATURE TO [sch_modules].[sp_demo03] BY CERTIFICATE [cert_demo02]

go

GRANT EXECUTE ON [sch_modules].[sp_demo03] TO [usr_lowpriv]

go

 

-- Let's see what happens when the low priv user executes the module:

EXECUTE AS USER = 'usr_lowpriv'

go

-- Will succeed

EXEC [sch_modules].[sp_demo03] 2

go

REVERT

go

 

This characteristic is a useful one, but it can also be dangerous in case of an arbitrary code execution or SQL injection.  In the following example I will try to demonstrate these dangers in case of an injection.

 

 

-- The following module is subject to SQL injection

--

CREATE PROC [sch_modules].[sp_demo04] ( @Table_name sysname )

AS

  ------------------------------------------------------------

  -- WARNING: The following code is subject to SQL injection!!!

  ------------------------------------------------------------

  DECLARE @cmd nvarchar(max)

  -- the follwoing code will be also afected by the siganture

  SET @cmd = 'SELECT * FROM sys.user_token ORDER BY usage, type, name; SELECT * FROM '

  + @table_name

  EXEC ( @cmd )

go

 

-- Add a siganture to the newly created module

ADD SIGNATURE TO [sch_modules].[sp_demo04] BY CERTIFICATE [cert_demo02]

go

GRANT EXECUTE ON [sch_modules].[sp_demo04] TO [usr_lowpriv]

go

 

EXECUTE AS USER = 'usr_lowpriv'

go

-- Using the module as originally intented

EXEC [sch_modules].[sp_demo04] '[sch_resources].[t_Demo02]'

go

-- .. but now abusing the signature...

EXEC [sch_modules].[sp_demo04] '[sch_resources].[t_Demo02]; EXEC sp_addrolemember ''db_owner'', ''usr_lowpriv'';'

go

-- Notice that the attack failed thanks to the

-- limited permissions granted to the certificate.

-- This is one of the reasons why I always recommend

-- following the least privilege principle

REVERT

go

 

-- Now, what would happen if we would have granted

-- a much higher permission?

--

GRANT CONTROL TO [usr_cert_demo02]

go

 

EXECUTE AS USER = 'usr_lowpriv'

go

-- Your DB would be compromised!!!

EXEC [sch_modules].[sp_demo04] '[sch_resources].[t_Demo02]; SELECT * FROM fn_my_permissions( NULL, ''DATABASE''); print ''Insert your favorite attack here'''

go

REVERT

go

 

-- remove the extremely-high privilege from the cert

REVOKE CONTROL TO [usr_cert_demo02]

go

 

  As you can see from the example, it is a good idea to follow the least-privilege principle when using signatures as a mechanism to extend the execution context.

 

 Now, the natural question to follow: Why would SQL Server allow carrying the signature to dynamic SQL? The answer is not as simple, and I am sure there may be some people who won’t like it, but the truth is that SQL Server is a platform and digital signatures is a feature that, when used properly and responsibly, can be extremely useful and safe, and in case the application developer really don’t want to execute dynamic SQL with a signature, there is an alternative: Move the dynamic SQL to a non-signed module (the signature will not be carried to a different module). For example:

 

-- Based on demo04 and still carrying injectable dynamic SQL

--

CREATE PROC [sch_modules].[sp_demo05_dyn] ( @Table_name sysname )

AS

  ------------------------------------------------------------

  -- WARNING: The following code is subject to SQL injection!!!

  ------------------------------------------------------------

  DECLARE @cmd nvarchar(max)

  -- the follwoing code will be also afected by the siganture

  SET @cmd = 'SELECT * FROM sys.user_token ORDER BY usage, type, name; SELECT * FROM ' + @table_name

  EXEC ( @cmd )

go

 

CREATE PROC [sch_modules].[sp_demo05] ( @Table_name sysname )

AS

  SELECT * FROM sys.user_token ORDER BY usage, type, name;

  SELECT * FROM [sch_resources].[t_Demo02];

  -- Call the module that will do dynamic SQL

  -- Noticed that the signature will be lost

  EXEC [sch_modules].[sp_demo05_dyn] @table_name

go

 

ADD SIGNATURE TO [sch_modules].[sp_demo05] BY CERTIFICATE [cert_demo02]

go

GRANT EXECUTE ON [sch_modules].[sp_demo05] TO [usr_lowpriv]

go

 

-- Let's see what happens when the low priv user executes the module:

EXECUTE AS USER = 'usr_lowpriv'

go

-- The signature is not carried to the 2nd module as we intended

EXEC [sch_modules].[sp_demo05] '[sch_resources].[t_Demo02];'

go

REVERT

go

 

Conclusions

 

 Digital signatures in SQL Server 2005 are a quite powerful tool, but as any such tool, it has to be used with care to avoid unnecessary risks and potential damage. When using digital signatures remember to be careful on what you are signing, and be extra careful when signing a module that includes dynamic SQL as it will be affected by the signature. Don’t sign unnecessary code, and keep the escalated (signed) code to a minimum.

 

  I also strongly suggest following the least-privilege principle when using signatures. Grant the minimum permission necessary to the certificate, and if necessary, split the code and sign different pieces of the code by different certificates.

 

Additional references

 

From BOL:

·         Module Signing (http://msdn2.microsoft.com/en-us/library/ms345102.aspx)

·         Understanding Execution Context (http://msdn2.microsoft.com/en-us/library/ms187096.aspx)

Laurentiu Cristofor’s blog:

·         SQL Server 2005: procedure signing demo (http://blogs.msdn.com/lcris/archive/2005/06/15/429631.aspx)

·         SQL Server 2005: An example for how to use counter signatures  (http://blogs.msdn.com/lcris/archive/2006/10/19/sql-server-2005-an-example-for-how-to-use-counter-signatures.aspx)

 

I hope this article will be useful. Please let me know either here in the blog comments or in the SQL Server Security forum () if you have any feedback or comments on this topic.

After a long delay, I am ready to start posting again

  I know it has been quite some time since I added any new content, I sincerely apologize for that, but I have the next article ready and I will be posting it quite soon.

 

  Please let me know if there is any topic you will like to discuss in more detail for future articles.

 

  Thanks a lot for your support and feedback,

-Raul

Posted 09 May 07 11:38 by raulga | 0 Comments   
Dynamic SQL & SQL injection

  I know there are a lot of papers that talk about dynamic SQL in more depth than what I am going to cover, but as SQL injection is still one of the biggest security problems in the relation databases world, that I decided to include this part as a quick (and hopefully helpful) reminder.

 

   First, allow me to define dynamic SQL as any mechanism used to programmatically generate and execute T-SQL statements, including statements generated in some application (using C#, C++ or any other programming language) and strings executed using the SQL Server sp_executesql stored procedure or the EXECUTE statement.

 

  Being able to interpret any string (or variable) as a SQL statement is a really useful and powerful feature; unfortunately, as happens with most powerful tools, it is really easy to misuse it and leave the door open for somebody to abuse it. In this case, the abuse typically takes form as SQL injection attacks.

 

   SQL injection occurs when the attacker provides malicious data that will change the semantics of the intended SQL you are generating, affecting the way it will be interpreted in the system. For example:

 

-- An innocent looking SP

CREATE PROC [sp_demo_injection01]( @name sysname )

AS

  -- ...with an obvious SQL injection-vulnerable sample

  EXEC( 'SELECT * FROM sys.database_principals WHERE name = ''' + @name + '''' )

go

 

-- This is how it was intended to be used

declare @var sysname

SET @var = 'Some Name'

EXEC [sp_demo_injection01] @var

go

 

-- As you can see, I can easily abuse this module in the following manner

declare @var sysname

SET @var = 'Some Name''; GRANT CONTROL TO [Malicious User]; PRINT ''Game over! This system is no longer yours!''-- Malicious User now can control the database!!!'

EXEC [sp_demo_injection01] @var

go

 

  When the attacker runs this query the system will concatenate the input to the command we defined in the SP:

 

  EXEC ( 'SELECT * FROM sys.database_principals WHERE name = ''' + 'Some Name''; GRANT CONTROL TO [Malicious User]; PRINT ''Game over! This system is no longer yours!''-- Malicious User now can control the database!!!' + '''' )

 

  The attacker is able to close the quote in the user name (notice the trailing quote in Some Name’) and converted the rest of what should have been a user name into a different SQL statement, causing the following command to be executed:

 

SELECT * FROM sys.database_principals WHERE name = 'Some Name'; GRANT CONTROL TO [Malicious User]; PRINT 'Game over! This system is no longer yours!'-- Malicious User now can control the database!!!'

 

  As you can see the attacker was able to add extra SQL statements that were not intended by the author of the stored procedure, in this case granting CONTROL on the database to herself and printing a note.

 

Parameterization

 

  In most of these scenarios there is an alternative to the example used above using parameterization.  Using parameterization gives you the advantage that you can clearly specify the data type and avoid pitfalls as well as the final T-SQL statement generated will reference the parameters as variables and not directly use the user defined input to generate the statement.

 

  If you are using T-SQL directly to generate dynamic SQL, you can take advantage of sp_ExecuteSql to execute parameterized queries, for example:

 

-- An improved version of [sp_demo_injection01]

CREATE PROC [sp_demo_injection02]( @name sysname )

AS

  declare @cmd nvarchar(max)

  declare @parameters nvarchar(max)

  set @cmd = N'SELECT * FROM sys.database_principals WHERE name = @name'

  set @parameters = '@name sysname'

  EXEC sp_executesql @cmd, @parameters, @name = @name

go

 

-- This is how it was intended to be used

declare @var sysname

SET @var = 'Some Name'

EXEC [sp_demo_injection02] @var

go

 

-- The previous attack no longer has any effect!

declare @var sysname

SET @var = 'Some Name''; GRANT CONTROL TO [Malicious User]; PRINT ''Game over! This system is no longer yours!''-- Malicious User now can control the database!!!'

EXEC [sp_demo_injection02] @var

go

 

 

CREATE PROC [sp_demo_injection03]( @Value nvarchar(100) )

AS

  declare @cmd nvarchar(max)

  declare @parameters nvarchar(max)

  set @cmd = N'SELECT * FROM sys.database_principals WHERE principal_id = @Value'

  set @parameters = '@Value int'

  EXEC sp_executesql @cmd, @parameters, @value = @value

go

 

-- Should work

declare @var sysname

SET @var = '1'

EXEC [sp_demo_injection03] @var

go

 

-- Expect error 8114

-- Error converting data type nvarchar to int.

declare @var sysname

SET @var = '1; select * from sys.objects'

EXEC [sp_demo_injection03] @var

go

 

But be careful, using sp_executesql is not a guarantee that the SQL statement to be executed is not susceptible to SQL injection; the parameters should be used properly in order to really take advantage of this feature. The following example is a demonstration of a common mistake I have seen a few times: constructing the @cmd parameter using user-defined data instead of using it as a parameter.

 

 

-------------------------------------------------------------

-- Incorrect usage of sp_executeSql

CREATE PROC [sp_demo_injection04]( @name sysname )

AS

  declare @cmd nvarchar(max)

  declare @parameters nvarchar(max)

  -- Looks famliar? yep, same injection as [sp_demo_injection01]

  set @cmd = N'SELECT * FROM sys.database_principals WHERE name = ''' + @name + N''''

  -- No parameters!!! This is typically a sign of misusing sp_execsql.

  set @parameters = null

  EXEC sp_executesql @cmd, @parameters

go

 

-- and now run the same attack we tried before...

declare @var sysname

SET @var = 'Some Name''; GRANT CONTROL TO [Malicious User]; PRINT ''Game over! This system is no longer yours!''-- Malicious User now can control the database!!!'

EXEC [sp_demo_injection04] @var

-- ... and it is game over!

go

 

  Be aware that sp_ExecuteSql doesn’t automatically protect against every SQL injection. It helps you to create the parameterized query, but it has to be used properly in order to work. I have seen a common misuse of this stored procedure: using the user-defined input (untrusted data) to generate the @statement parameter.

 

---------------------------------------------------------------------

-- Incorrect usage of sp_executeSql

CREATE PROC [sp_demo_injection04]( @name sysname )

AS

  declare @cmd nvarchar(max)

  declare @parameters nvarchar(max)

  -- Looks famliar? yep, same injection as [sp_demo_injection01]

  set @cmd = N'SELECT * FROM sys.database_principals WHERE name = ''' + @name + N''''

  -- No parameters!!! This is typically a sign of misusing sp_execsql.

  set @parameters = null

  EXEC sp_executesql @cmd, @parameters

go

 

-- and now run the same attack we tried before...

declare @var sysname

SET @var = 'Some Name''; GRANT CONTROL TO [Malicious User]; PRINT ''Game over! This system is no longer yours!''-- Malicious User now can control the database!!!'

EXEC [sp_demo_injection04] @var

-- ... and it is game over!

go

 

If you are using the .Net framework, you can use the SqlParameter class to create parameterized queries in a similar way, and the same warning still applies: Do not use user-defined input directly when constructing the parameterized statement. For further reference on this class, please refer to http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfsystemdatasqlclientsqlparameterclasstopic.asp

 

When parameterization is not an option

 

  Unfortunately in SQL Server 2005 (and previous versions) some statements (i.e. DDL) don’t allow specifying parts of the statement using variables, for example:

 

-------------

-- Will fail:

-- Msg 102 - Incorrect syntax near '@name'.

--

CREATE PROC [sp_demo]( @name sysname )

AS

  -- ...

  CREATE USER @name WITHOUT LOGIN

  -- ...

go

 

  If your business logic require such operations inside T-SQL modules, and you require using data that cannot be known before hand, you have little options other than creating the whole statement dynamically without parameterization.

 

  While not the best of the situations, it is not a SQL-injection-for-sure situation either. In this case you have two different mechanisms to protect yourself: validate user input and escape the input.

 

Data validation

 

   I will start with data validation. The rules on how (and where) to validate the data is completely up to you and your application/business logic with just some general recommendations:

 

Validate the data in such a way that an attacker cannot bypass or alter the results. This may sound obvious, but sometimes we forget that the data crosses trust boundaries and that we may not control the untrusted application/client at all. For example, if your client application is the one validating the input (i.e. a length restriction in a web form), the attacker can always bypass your client app validation routines and send invalid data directly to the server.

 

Always look for valid input, not for invalid one. The idea is simple: You know what good data looks like, but you cannot always predict how bad data looks like. New ways to attack data may catch you by surprise if you assume that you know all the possible attacks.

 

 For more information on this topic, I strongly recommend Writing Secure Code, Second Edition by Michael Howard and David C. LeBlanc. (ISBN: 0735617228).

 

Escaping input

 

  In SQL Server, some characters have special meaning, such as the single quote ( ‘ ) and the braces ( [, ] ), but these TSQL specific constrains don’t always reflect the nature of the business needs, and sometimes it is necessary to accept such characters. After validating the data, it may still be necessary to properly escape (or quote) the data in order to be used in the proper context. You can use SQL Server builtins in order to help you in this task, the most important ones you can use are QUOTENAME and REPLACE.

 

QUOTENAME is designed for system names (sysnames or its equivalent, nvarchar(128)); it will properly add the proper delimiters ( “[“ and “]” by default) to the input and escaping any occurrence of the closing delimiter by duplicating it. For example:

 

declare @data sysname

set @data = 'data'

-- Will print [data]

print quotename( @data )

set @data = 'this data needs to be escaped: ] '

-- Will print [this data needs to be escaped: ]] ]

print quotename( @data )

 

For strings I would recommend using REPLACE instead of QUOTENAME, the reason is that QUOTENAME was designed for sysname data types, and it is limited to 128 characters, REPLACE on the other hand doesn’t have this limitation, but you will have to add the delimiter yourself.

 

  One important thing to consider whenever you are escaping or manipulating the user input in any way is to carefully consider the length of the transformed data and allocate enough space on the variables that will be used. Here is a link to a really well written and useful article that describes this problem is detail: New SQL Truncation Attacks And How To Avoid Them (http://msdn.microsoft.com/msdnmag/issues/06/11/SQLSecurity/ )

 

  When using other languages

 

  In general everything I talked about here is focused on TSQL, but it is pretty much the same when you are using any other language like C++ or C#. If you are constructing the statement directly without parameterizing, validating the user input and/or without properly escaping (and making sure you have enough buffer for the escaped string) you are most likely susceptible to SQL injection.

 

 References

 

 These references are quite interesting and helpful, but they are only some of my favorites ones. I hope you will find them useful as well.

 

 

Let's talk about Dynamic SQL (preamble)

  I want to talk about how dynamic SQL is affected by the execution context, but as this is a huge and broad topic I am going to divide this topic into multiple parts and write different posts for each one of them, focusing in one aspect of dynamic SQL at a time.

  • Dynamic SQL & SQL injection.
  • Dynamic SQL & EXECUTE AS
  • Dynamic SQL & module signatures

   I hope you will find the subsequent posts useful.

Posted 04 January 07 03:46 by raulga | 1 Comments   
Filed under
Using a digital signature as a secondary identity to replace Cross database ownership chaining

  In SQL Server 2000, Cross database ownership chaining (CDOC) was a mechanism used to allow access (DML access) to resources on different DBs without explicitly granting access to the resources (such as tables) directly.

 

  Unfortunately CDOC is a feature that Microsoft does not recommend as it has some serious security risks inherent to the feature (for details on this topic, you can consult BOL, http://msdn.microsoft.com/library/default.asp?url=/library/en-us/adminsql/ad_security_4iyb.asp).

 

   Fortunately in SQL Server 2005 we count with 2 alternative mechanisms that will enable cross-database resource usage without explicitly granting access to either the database or to the resources directly to each principal. These mechanisms are EXECUTE AS feature and using digital signatures.

 

  Some of the best references on how to use these mechanisms can be found in BOL (Extending Database Impersonation by Using Execute As) and in

Laurentiu Cristofor’s blog (http://blogs.msdn.com/lcris/).

 

 On this demo, I want to extend these materials with a demo that relies only on digital signatures as secondary identity (i.e. no authenticator involved).

 

  While this approach has some advantages, including:

·         No need to create/re-use a login for the application

·         Works as a CDOC replacement

o   In addition, works with dynamic SQL

·         Denied permissions on the caller will be honored

·         Easier to script for ISV applications (i.e. the signature can be precalculated)

·         Can be easily adapted for either DB or server scoped permissions

 

  As any other security feature, this approach also has some limitations you should consider before deploying:

·         It doesn’t work if the calling context is a DB-scoped context (i.e. approles, EXECUTE AS USER).

·         Doesn’t work if the operation requires creating an object or use the calling’s primary identity.

o   Do not rely on implicit user creation!

·         Be extra careful when using dynamic SQL as SQL injection attacks can abuse the escalated privileges.

·         Need to write the certificate to disk (at least temporarily) in order to create the certificate in 2 or more databases.

·         If you require calling nested modules, each one of the nested modules should be signed or counter signed as well.

 

Demo

/*******************************************************************

*

*   This posting is provided "AS IS" with no warranties, and

* confers no rights.

*

* Author:   Raulga

* Date:     10/30/2006

*

*     (c) 2005 Microsoft Corporation. All rights reserved.

*

*******************************************************************/

 

CREATE DATABASE [db_Source]

go

 

CREATE DATABASE [db_Target]

go

 

CREATE LOGIN [dbo_db_Source] WITH PASSWORD = 'My S0uRc3 D8 p@55W0rD!'

CREATE LOGIN [dbo_db_Target] WITH PASSWORD = 'My +@r637 D8 p@55W0rD!'

go

 

-- Change the ownership for the source and the target databases

ALTER AUTHORIZATION ON DATABASE::[db_Source] to [dbo_db_Source]

ALTER AUTHORIZATION ON DATABASE::[db_Target] to [dbo_db_Target]

go

 

-- This principal will be the data owner, he can access the data on

-- the target database, and he controls the stored procedures on the

-- source database

CREATE LOGIN [data_owner] WITH PASSWORD = 'd@+4 0wn3R'

 

-- This principal should only have access to the data via the stored

-- procedures

CREATE LOGIN [AppUser] WITH PASSWORD = 's0m3 p@55w0Rd'

go

 

-----------------------------------------

-- Setting up the target DB

--

use [db_Target]

go

 

CREATE USER [data_owner] WITH DEFAULT_SCHEMA = [data_owner]

go

 

CREATE SCHEMA [data_owner] AUTHORIZATION [data_owner]

go

 

CREATE TABLE [data_owner].[MyTable]( data nvarchar(100) )

go

 

INSERT INTO [data_owner].[MyTable] values ( N'My data' )

go

 

-----------------------------------------

-- Setting up the source DB

--

use [db_Source]

go

 

-- The low privielged user is only required here

CREATE USER [AppUser]

go

 

-- Create an application that uses the table stored in db_Target

-- I will use a specific schema for all the application modules

--

CREATE SCHEMA [schema_MyApp]

go

 

GRANT EXECUTE ON SCHEMA::[schema_MyApp] TO [AppUser]

go

 

-- Remember that sigantures are sensitive to comments and white spaces

--

go

CREATE PROC [schema_MyApp].[sp_MyApp01] ( @new_data nvarchar(100) )

AS

  -- Print the user token on this DB

  SELECT * FROM sys.user_token

  -- Print the user token on the target DB

  SELECT * FROM [db_Target].sys.user_token

  -- Insert data on the Cross-DB table

  INSERT INTO [db_Target].[data_owner].[MyTable] VALUES (@new_data)

go

 

-----------------------------------------------------

-- Test the application as the low privielged user,

EXECUTE AS LOGIN = 'AppUser'

go

-- The call should fail teh moment it tries to access db_Target

EXEC [schema_MyApp].[sp_MyApp01] N'Test data'

go

-- revert to original context

REVERT

go

 

-----------------------------------------------------

-- Now, let's play the role of db_target DBO

--

USE [db_Target]

go

 

EXECUTE AS LOGIN = 'dbo_db_Target'

go

 

-- Create our signing certificate

CREATE CERTIFICATE [cert_MyAppSecIdentity]

    ENCRYPTION BY PASSWORD = 'S16n1n6 c3r+1f1C@+3'

      WITH SUBJECT = 'myApp siging certificate'

go

 

-- Re-create the Proc exactly as it was created in the source DB

-- including comments and blank characters

CREATE SCHEMA [schema_MyApp]

go

CREATE PROC [schema_MyApp].[sp_MyApp01] ( @new_data nvarchar(100) )

AS

  -- Print the user token on this DB

  SELECT * FROM sys.user_token

  -- Print the user token on the target DB

  SELECT * FROM [db_Target].sys.user_token

  -- Insert data on the Cross-DB table

  INSERT INTO [db_Target].[data_owner].[MyTable] VALUES (@new_data)

go

 

-- And add the siganture

ADD SIGNATURE TO [schema_MyApp].[sp_MyApp01] BY CERTIFICATE [cert_MyAppSecIdentity]

       WITH PASSWORD = 'S16n1n6 c3r+1f1C@+3'

go

 

BACKUP CERTIFICATE [cert_MyAppSecIdentity] TO FILE = 'cert_MyAppSecIdentity.cer'

go

 

-- obtain the pre-calculated signature that can be applied to the module in db_Source

DECLARE @signature varbinary(max)

SELECT @signature = crypt_property FROM sys.crypt_properties WHERE major_id = object_id('[schema_MyApp].[sp_MyApp01]')

PRINT @signature

go

-- In my case the siganture value was:

-- 0x5EF9C30476A8E3E248E9E11B7563528EB02DA1D8F440CAA9141841B9F3101F1988760D2775000CD0D70F44A8672984E327FBF2676E7FAC9AAED8E6F383A98B2A569A407577917E671F3D632EF7326AD3770A32E05CF43A613D310D64B6D52FD978E57A73912BF3587C475E48F4AA58561A7E0DB5D9DB53D35E03EC281BEC7772

 

-- Let's create a user for teh certifcate so we can use it as a secondary identity

CREATE USER [cert_MyAppSecIdentity] FOR CERTIFICATE [cert_MyAppSecIdentity]

go

 

-- And grant the right permission to it, in thsi case INSERT on teh table would be sufficient

GRANT INSERT ON [data_owner].[MyTable] TO [cert_MyAppSecIdentity]

go

 

-- Let's look at the permissions for the certificate-mapped user:

SELECT * FROM sys.database_permissions WHERE [grantee_principal_id] = user_id( 'cert_MyAppSecIdentity' )

--... notice that in addition to INSERT on our table, this user also has CONNECT permission on the database

go

 

-- revert to original context

REVERT

go

 

-----------------------------------------------------

-- Now, let's play the role of db_Source DBO

--

USE [db_Source]

go

 

EXECUTE AS LOGIN = 'dbo_db_Source'

go

 

-- Let's create a copy of teh certifcate on this DB

CREATE CERTIFICATE [cert_MyAppSecIdentity] FROM FILE = 'cert_MyAppSecIdentity.cer'

go

 

-- Now use teh pre-calculated siganture to sign the app

-- Notice that the Source DB dbo doesn't have any access to trhe private key

-- therefore, she cannot modify the SP body

--

ADD SIGNATURE TO [schema_MyApp].[sp_MyApp01] BY CERTIFICATE [cert_MyAppSecIdentity]

  WITH SIGNATURE = 0x5EF9C30476A8E3E248E9E11B7563528EB02DA1D8F440CAA9141841B9F3101F1988760D2775000CD0D70F44A8672984E327FBF2676E7FAC9AAED8E6F383A98B2A569A407577917E671F3D632EF7326AD3770A32E05CF43A613D310D64B6D52FD978E57A73912BF3587C475E48F4AA58561A7E0DB5D9DB53D35E03EC281BEC7772

go

 

-- revert to original context

REVERT

go

 

-----------------------------------------------------

-- Let's test the application as the low privielged user once more

EXECUTE AS LOGIN = 'AppUser'

go

-- The call should succeed!!!

EXEC [schema_MyApp].[sp_MyApp01] N'Test data'

go

-- revert to original context

REVERT

go

 

--0   NULL  public      ROLE  GRANT OR DENY

--6   0x0106000000000009010000002A1A61C7FF8883632259BFA45D0493B234FDD3C1      cert_MyAppSecIdentity   USER MAPPED TO CERTIFICATE    GRANT OR DENY

 

-- Verify that the insert succeeded

SELECT * FROM [db_Target].[data_owner].[MyTable]

go

 

-----------------------------------------------------

-- 2nd part

-- Using dynamic SQL with access via siganture

-----------------------------------------------------

--

USE db_Source

go

 

EXECUTE AS LOGIN = 'dbo_db_Source'

go

 

-- Let's create a simple module that will execute a select & a simpel dynamic SQL code

CREATE PROC [schema_MyApp].[sp_MyApp02] ( @new_data nvarchar(100) )

AS

  -- SELECT from teh table

  SELECT * FROM [db_Target].[data_owner].[MyTable]

  -- Using dynamic SQL for demonstration purposes only

  EXEC( 'use db_target; SELECT * FROM sys.user_token; SELECT user_name();' )

go

 

REVERT

go

 

--------------------------------------------

-- Now let's create the siganture for the previous module

USE [db_Target]

go

 

EXECUTE AS LOGIN = 'dbo_db_Target'

go

CREATE PROC [schema_MyApp].[sp_MyApp02] ( @new_data nvarchar(100) )

AS

  -- SELECT from teh table

  SELECT * FROM [db_Target].[data_owner].[MyTable]

  -- Using dynamic SQL for demonstration purposes only

  EXEC( 'use db_target; SELECT * FROM sys.user_token; SELECT user_name();' )

go

 

ADD SIGNATURE TO [schema_MyApp].[sp_MyApp02] BY CERTIFICATE [cert_MyAppSecIdentity]

  WITH PASSWORD = 'S16n1n6 c3r+1f1C@+3'

go

 

-- We need SELECT permission to succeed on the SP

GRANT SELECT ON SCHEMA::[data_owner] TO [cert_MyAppSecIdentity]

go

 

-- same step as before

DECLARE @signature varbinary(max)

SELECT @signature = crypt_property FROM sys.crypt_properties WHERE major_id = object_id('[schema_MyApp].[sp_MyApp02]')

PRINT @signature

go

-- 0x16A91194689EB9D07FB1DEB5526B1216126D79DF00B4C74CDC5D86CA94DF81732DB001C504DC7C361A3F4FC45214DA9A6484A085CDC1679E7C5D23EB0C2ADD9F118C26B20B3853CB8D329591E100BA742EFA5E47985623C8D0CF9BAE80AC488B09B42386010F079319FA241012A73BFD2E3BC214D527398B12EAB22316FC4A59

 

REVERT

go

 

------------------------------

USE db_Source

go

 

ADD SIGNATURE TO [schema_MyApp].[sp_MyApp02] BY CERTIFICATE [cert_MyAppSecIdentity]

   WITH SIGNATURE = 0x16A91194689EB9D07FB1DEB5526B1216126D79DF00B4C74CDC5D86CA94DF81732DB001C504DC7C361A3F4FC45214DA9A6484A085CDC1679E7C5D23EB0C2ADD9F118C26B20B3853CB8D329591E100BA742EFA5E47985623C8D0CF9BAE80AC488B09B42386010F079319FA241012A73BFD2E3BC214D527398B12EAB22316FC4A59

go

 

-- Let's test the application as the low privielged user

EXECUTE AS LOGIN = 'AppUser'

go

-- The SELECT call should succeed!!!

EXEC [schema_MyApp].[sp_MyApp02] N'Test data'

go

-- Notice that the token inside the dynamic SQL also contains the certificate user as a secondary identity

-- Additionally, look at the result from user_name()!

-- The reason why it shows AppUser is because the access to teh DB is via a secondary identity, similar to the

-- case when access to a database is granted via a Windows group.

 

-- revert to original context

REVERT

go

 

-----------------------------------------------------

-- 3rd part

-- Honoring denied permissions

-----------------------------------------------------

--

CREATE LOGIN [dbTarget_DenyReader] WITH PASSWORD = '53cr3+ p@55WoRd!'

go

 

USE [db_Target]

go

 

CREATE USER [dbTarget_DenyReader]

go

 

-- This user cannot SELECT from [data_owner] schema

DENY SELECT ON SCHEMA::[data_owner] TO [dbTarget_DenyReader]

go

 

USE [db_Source]

go

 

-- But it is a valid, maybe even privielged user on db_Source

CREATE USER [dbTarget_DenyReader]

go

EXEC sp_addrolemember 'db_owner', 'dbTarget_DenyReader'

go

 

-- Can dbTarget_DenyReader use the application we created?

EXECUTE AS LOGIN = 'dbTarget_DenyReader'

go

 

-- This call will succeed, after all INSERT permission via teh certificate is still valid

-- and no explicit denied permission for INSERT

EXEC [schema_MyApp].[sp_MyApp01] N'Test data as deny reader'

go

-- Notice that on the user token for db_Target this time we can see "dbTarget_DenyReader"

-- The reason is that this time we are not accessing teh db_Target based on teh secondary identity

--  as dbTarget_DenyReader is a valid user on it, we are just extending the existing permissions.

--

-- Let's try the 2nd SP now...

EXEC [schema_MyApp].[sp_MyApp02] N'Test data as deny reader'

go

-- SELECT on [db_Target].[data_owner].[MyTable] failed,

-- but the rest of the module executed as we expected, you can see

-- that the certificate is still aprt of the token on the dynamic SQL call.

 

-- revert to original context

REVERT

go

Quick guide to DB users without logins in SQL Server 2005

  SQL Server 2005 introduced a new SQL DB principal subtype that can be quite useful: a SQL user that is not mapped to any login. You may be asking yourself “Why is this feature interesting? after all SQL Server already had the ability to create SQL users”, well, to answer this question I would like to describe what this SQL principal subtype really is and what interesting properties it has.

 

  For most operations, these subtype of users behave the same way as regular SQL users. They can own objects and schemas, can be granted/denied permissions, can be impersonated, etc.; The difference as the DDL describes is that these are DB-scoped principals not mapped to any login.

 

  Because there is no mapping on these principals there is no need to prerequisite to generate a login, therefore a DBO can generate such principals at will, even without having access to create/guess logins.

  CREATE DATABASE db_Demo

go

-- Create a login whose only purpose is to manage the db_Demo DB

-- no other permissions at server or crossDB scope are granted

CREATE LOGIN db_Demo_dbo WITH PASSWORD = 'My dem0 p@ssw0Rd'

go

 

ALTER AUTHORIZATION ON DATABASE::db_Demo TO db_Demo_dbo

go

 

USE db_Demo

go

 

-- Switch to db_Demo_dbo

EXECUTE AS LOGIN = 'db_Demo_dbo'

go

 

-- Let's create a user for my application, but do we have a

-- login named my_app_login?

CREATE USER my_app_login

go

 

-- As you can see, the access to sys.server_principals

-- is limited. Forcing this DBO to "guess" the name of

-- a login in order to create a user ...

SELECT name FROM sys.server_principals

go

-- ... and of course, creating a new arbitrary login is out of question

CREATE LOGIN my_app_login WITH PASSWORD = 'My dem0 p@ssw0Rd'

go

 

-- USER WITHOUT LOGIN on the other hand does not require

-- any additional permission

CREATE USER my_app_user WITHOUT LOGIN

go

 

-- Succeeded!, Let's take a quick look to the MD

-- As youcan see, except for the SID, this looks like an ordinary SQL user

SELECT * FROM sys.database_principals WHERE name = 'my_app_user'

go

 

REVERT

go

 

  In many situations, especially for ISVs, it may be interesting to create a DB user to own a schema and objects used in any given application, or to mark modules with execute as and granting only permissions to these principals. For this scenario, creating a regular SQL user will require to create a login with a password, this may affect the ability to script the application as well as potentially polluting the server principals information; using a user without login for this purpose may be a very good alternative that requires no password (making it easier to script).

 

-- Create a schema for the application objects

-- making my_app_user the owner of all of them

CREATE SCHEMA my_app_schema AUTHORIZATION my_app_user

go

 

-- my_app_user is the owner (via schema ownership) of this table

CREATE TABLE my_app_schema.table1( data int )

go

 

-- create a demo table that is required by the app,

-- but my_app_user is not the owner

CREATE TABLE dbo.table2( data int )

go

 

-- GRANT access to the demo table to my_app_user

GRANT SELECT ON dbo.table2 TO my_app_user

go

 

-- Create a module that will always run as my_app_user

CREATE PROC my_app_schema.sp_demo

WITH EXECUTE AS 'my_app_user'

AS

  SELECT user_name()

  SELECT * FROM my_app_schema.table1

  SELECT * FROM dbo.table2

go

 

  One of my favorite ways to use users without login is to test if my application works with minimum permissions. As there is no need to create logins, I don’t expose or pollute my server in any way, and it is usually easy to clean up these users on a dev environment.

 

-- Let's test the app

-- I will create a user w/out login to test

CREATE USER my_app_tester WITHOUT LOGIN

go

 

-- This is the minumum permission needed to run my app

GRANT EXECUTE ON my_app_schema.sp_demo TO my_app_tester

go

 

EXECUTE AS USER = 'my_app_tester'

go

 

-- Should succeed and run as my_app_user

EXEC my_app_schema.sp_demo

go

 

-- direct access should fail

SELECT * FROM my_app_schema.table1

SELECT * FROM dbo.table2

go

 

REVERT -- my_app_tester

go

 

Now here is a quite interesting question: If there is no login for this user, how is it possible to impersonate the user and what is the behavior outside the current database?

  Impersonation is possible as these type of users are mapped to special type of SID. This SID belong to a special family that indicates that the information used to create the login token is not available in metadata, instead, the login information must be generated on the fly (pretty much a “public access only” generic token). Let’s take a look in detail to the tokens:

 

EXECUTE AS USER = 'my_app_tester'

go

 

-- Let's look at the token

SELECT principal_id, sid, name, type FROM sys.login_token

SELECT principal_id, sid, name, type FROM sys.user_token

go

 

REVERT -- my_app_tester

Go

 

Login token:

principal_id

sid

name

type

0

0x010500000000000903000000…

S-1-9-3-…

Sql login

2

0x02

public

Server role

 

Notice that the principal_id is 0. This is a special id and refers to a principal that is not in metadata, also take a look to the SID and name, the name is really a string representation of the SID.

 

User token:

principal_id

Sid

name

type

<#>

0x010500000000000903000000…

My_app_tester

SQL USER

0

Null

public

ROLE

 

The user token on the other hand looks exactly the same as anty other database principal token would look like.

 

Now the next question: Can I access other databases or server resources while impersonating these subtype of users?

  Unlike approles, that are truly DB scoped (the login token for approles is a special case, and it is never trusted on the server) the user without login tokens are bound to the same trust relationship as any other user impersonation. You can use digital signatures (recommended) or trustworthy bit (personally, I don’t recommend using this option) to establish a trust relationship to access server resources.

 

-- Modify the app to access a server resource

-- in this case we will use VIEW ANY DEFINITION

ALTER PROC my_app_schema.sp_demo

WITH EXECUTE AS 'my_app_user'

AS

  SELECT user_name()

  SELECT count(*) FROM sys.server_principals

  SELECT principal_id, sid, name, type, usage FROM sys.login_token

  SELECT principal_id, sid, name, type, usage FROM sys.user_token

go

 

-- Let's run the app as our test user

EXECUTE ('EXEC my_app_schema.sp_demo' ) AS USER = 'my_app_tester'

go

 

-- No surprises so far, now let's grant VIEW ANY DEFINITION to public

-- we have to revert to sysadmin for this one!

REVERT -- db_Demo_dbo

go

 

use master

go

 

GRANT VIEW ANY DEFINITION TO public

go

 

-- Let's see how many logins we have, in my case it's 35

SELECT count(*) FROM sys.server_principals

go

 

-- Let's go back to the demo DB and test the app again

-- Let's run the app as our test user

USE db_Demo

go

EXECUTE ('EXEC my_app_schema.sp_demo' ) AS USER = 'my_app_tester'

go

 

-- What happened?! This time we only got 10 logins back

-- As you can see in teh login token info, the login token is not trusted (deny only)!

--  Let's sign the module and establish the proper trust relationship

CREATE CERTIFICATE my_app_cert ENCRYPTION BY PASSWORD = 'My c3r+ p@zzw0Rd' WITH SUBJECT = 'My app signing cert'

go

ADD SIGNATURE TO my_app_schema.sp_demo BY CERTIFICATE my_app_cert WITH PASSWORD = 'My c3r+ p@zzw0Rd'

go

-- Backup the cert and it's PVK and remove it from teh DB

BACKUP CERTIFICATE my_app_cert TO FILE = 'my_app_cert.cer'

  WITH PRIVATE KEY( FILE = 'my_app_cert.pvk', ENCRYPTION BY PASSWORD = 'My c3r+ p@zzw0Rd', DECRYPTION BY PASSWORD = 'My c3r+ p@zzw0Rd'  )

go

ALTER CERTIFICATE my_app_cert REMOVE PRIVATE KEY

go

 

-- Now go to master and create the cert, a login map to it and grant the appropiate permission

USE master

go

 

CREATE CERTIFICATE my_app_cert FROM FILE = 'my_app_cert.cer'

go

 

CREATE LOGIN my_app_cert FROM CERTIFICATE my_app_cert

go

 

GRANT AUTHENTICATE SERVER TO my_app_cert

go

 

-- Now that the cert is vouching for the context, let's try again

USE db_Demo

go

EXECUTE ('EXEC my_app_schema.sp_demo' ) AS USER = 'my_app_tester'

go

-- Success!!!

-- Notice that in the login token the certificate will work as both a

-- secondary identity and as authenticator

-- For this demo, we are only using it as authenticator.

 

  These are just a few examples on how this new type of principal can be used along with other features in SQL Server 2005 based on the ways I typically use them, but I am sure you will find other new interesting way to take advantage of this feature.

 

 I hope this article has been helpful.

Link to Laurentiu's blog

I am including a link to Laurentiu Cristofor's blog: http://blogs.msdn.com/lcris. Laurentiu is one of the most valuable contributors in the SQL Security forums, and his articles and demos are great resources for anyone interested in SQL Server security

 

I would also like to add a link for a blog that unfortunately has been discontinued, but has interesting articles: http://blogs.msdn.com/yukondoit/.

Posted 03 May 06 09:25 by raulga | 0 Comments   
Filed under
Indexing encrypted data

Encrypted data and indexes

 

One thing I have been asked many times is how to create an index on top of encrypted data in SQL Server 2005.  In SQL Server 2005 the encryption functions are nondeterministic, which means that every time a function is called, the output will be different, even if exactly the same input was used; for example:

 

CREATE SYMMETRIC KEY key1 WITH ALGORITHM = AES_256 -- TRIPLE_DES

   ENCRYPTION BY PASSWORD = 'My Password!'

go

DECLARE @x varbinary(8000)

DECLARE @y varbinary(8000)

OPEN SYMMETRIC KEY key1

   DECRYPTION BY PASSWORD = 'My Password!'

SET @x = EncryptByKey( key_guid( 'key1'), 'Test' )

SET @y = EncryptByKey( key_guid( 'key1'), 'Test' )

IF ( @x = @y )

    PRINT 'ERROR: EncryptByKey returned the same output twice!!!!'

ELSE

    PRINT 'EncryptByKey returns different results every time it is called'

CLOSE SYMMETRIC KEY key1

go 

DROP SYMMETRIC KEY key1

go

 

 As there is no way to predict the outcome of an encryption builtin call, there is no way to establish any relationship between two different values being encrypted. Therefore, creating an index on top of encrypted data will be of no use at all. It is possible to decrypt the data for the lookup operation, but it would be too expensive as it will result in a linear search. For example:

 

-- ...

-- The following select will always return 0 rows,

-- because EncryptByKey will never return the same value twice

SELECT CONVERT( nvarchar(20), DecryptByKey( SSN )) as SSN_decrypted,

        * FROM t_Customer

        WHERE SSN = EncryptByKey( key_guid( 'key_Encryption' ), N'111-11-1111' )

go

 

-- The following SELECT statement will work, but it

-- will be very expensive, because it will result in a linear search

SELECT CONVERT( nvarchar(20), DecryptByKey( SSN )) as SSN_decrypted,

        * FROM t_customer

        WHERE DecryptByKey( SSN ) = N'111-11-1111'

go

 

  This raises a question: why did we choose to have a nondeterministic encryption function? In order to answer it, I want to start with a simplified explanation on how block ciphers work (i.e. DES or AES), which I hope will help explain this property better.

 

Block ciphers

 

  As the name implies, block ciphers work on blocks of plaintext/ciphertext, and every time the same plaintext is encrypted by the same key, the resultant ciphertext will always be the same. This raises some security considerations, such as how can we conceal patterns in the plaintext or how can we reuse a key to encrypt more than one plaintext.

 

  One mechanism that is used to address these issues is the use of the block cipher in different “cryptographic modes”, which usually consist on some feedback mechanism and simple operations such as XOR.

 

  The most simple of the cryptographic modes is called Electronic Codebook mode (ECB) and it consists of dividing the plaintext in blocks and then encrypting them independently. Sounds simple and it is deterministic, but unfortunately, this cryptographic mode allows an attacker to create a code book without any need to access the key. Imagine the following scenario: You are trying to encrypt a social security number of the form “xxx-xx-xxxx”, if we were using TRIPLE_DES, the block length would be 8 bytes, therefore the ciphertext would really be divided in two blocks:  Encrypt( key, “xxx-xx-x” ) + Encrypt( key, “xxx” ). An attacker can easily find out that any SSN that starts with the same numbers will always result in the ciphertext for the first block of ciphertext. It is not recommended to use this mode, especially if there are potential patterns to conceal or if the key is going to be used to encrypt more than one plaintext.

 

  The cryptographic mode we decided to use in SQL Server 2005 is Cipher Block Chaining mode (CBC). This mode uses the previous ciphertext block to feed the next one, by XORing the previous ciphertext block with the current plaintext block before encrypting it, and it repeats this for every block. But what should we do with the first block? As there is no previous ciphertext block, we need to initialize the system with something else; in this case, we will use an Initialization vector (IV). How do we choose an IV? If we select a fixed IV, then the first block will always encrypt to the same ciphertext and all subsequent blocks will be the same until we hit the first difference between 2 plaintexts. To avoid this, a randomly generated IV is used every time and it is included as the first block of the ciphertext in order to be able to decrypt it back.

 

How to perform lookups on encrypted data

 

     At this point, you are probably asking: “Good, thanks for the information, but I still need to create an index on encrypted data! Any suggestions?”. Well, actually, yes we have some ideas, and I really hope they can be of some help.

 

   First of all, we need to emphasize that in order to be able to index something we need to give away some information about it. How much information? The answer may be different for every situation, but here are some ideas as well as some pros and cons for each one of them. The basic idea behind the following suggestions is to create an additional column on which we will create the index.

 

   For the reminder of this article, we will assume the problem of indexing data that is used to uniquely identify entities in a table, for example, credit card numbers identifying customers or Social Security numbers identifying patients. For these scenarios, the data that we want to index is a primary key and is used as the identification method. Below we describe several suggestions for how to address this problem.

 

  Create a new identifier value

 

  It may be possible in some cases to use a column with an identity value completely unrelated to the data we are trying to protect, instead of using the protected data; for example, we could create a unique customer ID instead of using a social security number for identification.

 

  Unfortunately, because of the business logic and existing applications, this approach is not always possible.

 

   Index a hash of your data

 

   The idea is simple: create a new column to store the hash (for example, SHA1) of the plaintext data and then index this column.

 

   As the hash functions are deterministic, this sounds like a good approach, but its simplicity is also its worst enemy. An attacker can potentially create a dictionary with all possible values of plaintext offline and then she can just do a simple lookup of the indexing column and correlate that with her dictionary.

 

   Index a MAC of your data

 

   We can use a MAC (Message Authentication Code) of the plaintext to create a new indexing column. This approach is similar to using a hash, but it requires a secret key to calculate the MAC. This prevents an unauthorized user to use a general purpose dictionary of hashed values and it will also prevent her from creating a targeted dictionary without having access to the MAC key. Please note that an attacker who has access to the MAC key can generate a targeted dictionary.

 

  SQL Server 2005 doesn’t provide a function for computing a MAC, but it is possible to write a user-defined function that calculates a MAC using either SQL Server 2005 CLR or by reusing the existing builtin functions.

 

  Sample code for indexing a MAC

 

  I would like to include a short demo that hopefully will help to understand this better. I strongly recommend reading more about hashes and MACs - one good source you can use is Bruce Schneier’s Applied Cryptography (http://www.schneier.com/book-applied.html).

 

  First, I create a certificate to protect my symmetric key and I protect the certificate itself using the DB master key (DBMK). This will allow me to control who can access the protected data by giving the proper permissions on the certificate and symmetric key without the need to memorize or hardcode any password.

 

/*********************************************************

*      This posting is provided "AS IS" with no warranties, and confers no rights.

*

* Authors:             Raul Garcia

*                      Laurentiu Cristofor

* Date:         11/02/2005

*                      09/01/2006

* Description:

*

*  Create an index based on a MAC (message authentication code)

* to be able to do SEEK operations on a table that contains

* encrypted data that is typically used as an index, such as SSN

* or credit card numbers.

*   It also demonstrates a few other topics such as EXECUTE AS.

*

* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *    * Security note:  

* This demo is intended to demonstrate how to protect data

* at rest, while trying to keep it easily accessible to users while

* the SQL Server instance is running; the root for the key hierarchy

* in this demo is the service master key (SMK).

*

*     (c) 2005 Microsoft Corporation. All rights reserved.

*

*********************************************************/

 

-- This certificate will be used to protect the data-encryption key

CREATE CERTIFICATE cert_ProtectEncryptionKeys WITH SUBJECT = 'Data encryption key protection'

go

 

-- This key will be used to protect our plaintext data

CREATE SYMMETRIC KEY key_Encryption WITH ALGORITHM = AES_128 ENCRYPTION BY CERTIFICATE cert_ProtectEncryptionKeys

go

 

  I will generate a random value as a MAC key. Also, because we may want to use this mechanism for more than one table, we would want to be able to store a MAC for each column that we index this way. So I will create a table to store all the MAC keys, as well as a certificate and a symmetric key to protect them.  I will be using the table id as a primary key for the MAC keys. This will allow me to keep track of one MAC key for each table. Of course, if we would need to have several MAC keys per table, because we might want to index several encrypted columns, then we would need to use a column id in addition to the table id, but for the purpose of this demo, I am keeping it simple.

 

-- This is the certificate that will protect our MAC key-encryption key

CREATE CERTIFICATE cert_ProtectIndexingKeys WITH SUBJECT = 'Data indexing key protection'

go

 

-- This key will be used to protect the MAC keys

CREATE SYMMETRIC KEY key_Indexing WITH ALGORITHM = AES_128 ENCRYPTION BY CERTIFICATE cert_ProtectIndexingKeys

go

 

-- This table will store the encrypted MAC keys for all tables

CREATE TABLE t_MacIndexKeys( table_id int PRIMARY KEY, Mac_key varbinary(100) not null )

go

 

I will also define the MAC user-defined function. The function itself is quite simple, and I would strongly suggest you to review this code and my notes in it and modify it as needed. The MAC function that I am using is defined as SHA1( plaintext + key ). Also note that this function may return null in case of an error.

 

-- This function is used to generate a MAC based on the plaintext.

-- Parameters:

--      @Message       Message we want to calculate the MAC for.

--      @Table_id      The table id (object_id),

--                     this value will be used to find the corresponding

--                     MAC key from the t_MacIndexKeys table

--      return value: a varbinary(24) MAC if succeeded, null otherwise

--

-- NOTES:  This function will run under the DBO context to be able

-- to access the required keys and calculate the MAC

--  Only grant execute permission to authorized principals

-- who need to access the protected data and/or use

-- the module signature feature to grant access via an application.

--  This function relies on the DBMK being available;

-- if the DBMK is not protected by the SMK, the caller must

-- open the DBMK explicitly before calling this function.

--

-- SECURITY NOTE:   A potential attacker with access to the database

-- and with execute permissions on this function while the system

-- is online and the DBMK is opened can generate a dictionary with

-- all the possible values for the protected data

-- and its corresponding MAC based on the table id.

--  By using a different key for every table, the attacker

-- may be slowed down a little bit, depending on the protected data

-- domain space. A good advice is to monitor irregular activity

-- on this function.

--

CREATE FUNCTION MAC( @Message nvarchar(4000), @Table_id int )

RETURNS varbinary(24)

WITH EXECUTE AS 'dbo'

AS

BEGIN

        declare @RetVal varbinary(24)

        declare @Key   varbinary(100)

        SET @RetVal = null

        SET @key    = null

        SELECT @Key = DecryptByKeyAutoCert( cert_id('cert_ProtectIndexingKeys'), null, Mac_key) FROM t_MacIndexKeys WHERE table_id = @Table_id

        if( @Key is not null )

               SELECT @RetVal = HashBytes( N'SHA1', convert(varbinary(8000), @Message) + @Key )

        RETURN @RetVal

END

go

 

The following stored procedure will be used to generate a new random varbinary value that we can use as a MAC key. As we don’t have any cryptographic random byte generator builtin available out of the box, we will use the Initialization Vector (IV) of the EncryptByKey builtin to generate entropy on our new varbinary MAC key. I also strongly suggest to review this code and to modify it as needed.

 

--  This SP creates a new randomly generated MAC key

-- for the table specified by table_id

-- Parameters:

--      @Table_id      The table id (object_id) related to the MAC key.

--

--  NOTE:  This SP will run under the DBO's context

-- to be able to access the required keys and tables.

--  It will not return any new or existing key blob back,

-- but it is recommended to only grant execute permissions

-- to trusted principals who need to be able to create new MAC keys.

--

CREATE PROC AddMacForTable @Table_id int

WITH EXECUTE AS 'dbo'

AS

        declare @Key    varbinary(100)

        declare @KeyGuid uniqueidentifier

        SET @KeyGuid = key_guid('key_Indexing')

        -- Open the encryption key

        -- Make sure the key is closed before doing any operation

-- that may end the module, otherwise the key will

-- remain opened after the store-procedure execution ends

        OPEN SYMMETRIC KEY key_Indexing DECRYPTION BY CERTIFICATE cert_ProtectIndexingKeys

 

        -- The new MAC key is derived from an encryption

-- of a newly created GUID. As the encryption function

-- is not deterministic, the output is random

        -- After getting this cipher, we calculate a SHA1 Hash for it.

        SELECT @Key = HashBytes( N'SHA1', ENCRYPTBYKEY( @KeyGuid, convert(varbinary(100), newid())) )

 

-- Protect the new MAC key

        SET @KEY = ENCRYPTBYKEY( @KeyGuid, @Key )

 

        -- Closing the encryption key

        CLOSE SYMMETRIC KEY key_Indexing

        -- As we have closed the key we opened,

-- it is safe to return from the SP at any time

 

        if @Key is null

        BEGIN

               RAISERROR( 'Failed to create new key.', 16, 1)

        END

        INSERT INTO t_MacIndexKeys VALUES( @Table_id, @Key )

go

 

And finally, we create a sample table with a Social Security Number column (SSN) that we want to use as the primary key. The table has been modified to have 2 columns for storing the SSN:

* SSN_cipher is used to store the SSN in an encrypted way, such that someone with the right key can recover all the SSNs from the table.

* SSN_index is used to store MACs based on the SSN and on this table’s MAC-key. This column will be used for identity lookups.

 

-- Table encryption example:

CREATE TABLE t_RawCustomer(

        -- MAC-based index (SSN)

        SSN_index      varbinary(20) PRIMARY KEY,   

        -- ciphertext (SSN)

SSN_cipher     nvarchar(60),                        

        -- Two extra columns with information.

name    nvarchar(max),

        ExtraData      nvarchar(100)

     )

go

 

We also have to call the stored procedure that we created, to generate and store the new table MAC key.

 

--- Create a new MAC key for this table

declare @objid int

SET @objid = object_id('t_RawCustomer')

EXEC AddMacForTable @objid

go

 

Now I will create a few objects that will make it easier to work with the data table. These objects are not intended to increase the security of the system, only its usability. Let’s start with a view that will display the SSN in plaintext form when the viewer has proper access.

 

-- Now, let's create an easy to use view for the table

--

-- NOTE: as we are using DecryptbyKeyAutoCert to decrypt the data,

-- it is not necessary to explicitly open the key

--      Also note that this means that in order to see

-- the plaintext value for SSN, the caller requires permission

-- to access both the certificate's private key and the

-- data symmetric key.

CREATE VIEW v_Customer

WITH SCHEMABINDING

AS

SELECT  

        -- We want to leave the index in the view as

-- it may be useful for SEEK operations

        SSN_index as SSN_Id,

        -- Use the certificate to automatically open the encryption key.

        -- Additionally use the index (MAC(k1, PT)) to verify the

-- decryption and prevent data tampering such as

-- copying encrypted values from one row to another

convert( nvarchar(15), DecryptbyKeyAutoCert(

cert_id('cert_ProtectEncryptionKeys'), null,

SSN_cipher, 1, SSN_index)) as SSN,

        -- the rest of the data will remain the same

        Name, ExtraData FROM dbo.t_RawCustomer

go

 

Next, we create a trigger that will fire during an insert in the base table. This trigger expects that the SSN_cipher value will be a SSN plaintext and will perform the MAC calculation and encryption on it. The trigger will validate that the SSN is not null, as well as verify that the caller can generate both MAC and ciphertext values based on the keys being used.

Note that, for inserting data using our trigger, it is a prerequisite to open the key that is used to encrypt the data.

 

-- Intercept the inserts and make sure the inserted data is properly generated

CREATE TRIGGER trig_ProtectSSN on t_RawCustomer

INSTEAD OF INSERT

AS

        declare @Index varbinary(24)

        declare @KeyGuid uniqueidentifier

        declare @Cipher nvarchar(60)

 

        if( select count(*) from inserted where SSN_cipher is null ) > 0

               RAISERROR( 'Cannot store null as protected data. ', 16, 1)

        ELSE

               BEGIN

               SET @KeyGuid = key_guid('key_Encryption')

               SELECT @Index = dbo.MAC( SSN_cipher,

object_id('t_RawCustomer') ) from inserted

 

               if( @Index is null

 OR @KeyGuid is null

 OR encryptbykey( key_guid('key_Encryption'), 0x00)

     is null )

                       BEGIN

                       RAISERROR( 'Cannot Insert protected data. Either the encryption or indexing keys are not available or the indexing key is not valid for MAC generation.', 16, 1)

                       END

               ELSE

                       INSERT INTO dbo.t_RawCustomer select

                         @Index,

                         encryptbykey( key_guid('key_Encryption'),                                              SSN_cipher, 1, @Index ),

                         Name, Extradata

                       from inserted

        END

go

 

In a similar way, we will create a trigger that will fire during updates and that will prevent arbitrary changes on the SSN fields (cipher or MAC).

 

-- Intercept any attempt to modify the RawData table

-- and prevent anyone from modifying the cipher values

CREATE TRIGGER trig_ProtectSSNUpdate on t_RawCustomer

INSTEAD OF UPDATE

AS

        if( COLUMNS_UPDATED() & 3 ) > 0

               raiserror( 'Cannot update protected columns. Drop the row and create a new one with the updated information.', 16, 1 )

        ELSE

               BEGIN

               UPDATE t_RawCustomer

SET Name = ins.Name, ExtraData = ins.ExtraData

FROM inserted ins, t_RawCustomer orig

WHERE ins.SSN_index = orig.SSN_index

               END

go

 

Here is a usage example :

 

-- test: Try to insert without opening the key; this should fail

insert into t_RawCustomer values ( null, N'111-11-1110', N'Customer 0', N'New data' )

go

 

-- test: Open the symmetric key before we can use it

OPEN SYMMETRIC KEY key_Encryption DECRYPTION BY CERTIFICATE cert_ProtectEncryptionKeys

go

 

insert into t_RawCustomer values ( null, N'111-11-1111', N'customer 1', N'New data 1' )

insert into t_RawCustomer values ( null, N'111-11-1112', N'customer 2', N'New data 2' )

go

 

CLOSE SYMMETRIC KEY key_Encryption

go

 

-- Compare the raw table with the view

select * from t_RawCustomer

select * from v_Customer

go

 

-- Select using the SSN in plaintext.

-- Note that this will require a full table scan and is inefficient.

-- We cannot make this query take advantage of an index.

SELECT * FROM v_Customer

        where SSN = N'111-11-1112'

go

 

-- Now select using the SSN_id column, which will use

-- the index that we have set up for this column.

SELECT * FROM v_Customer

        where SSN_Id = dbo.MAC( N'111-11-1112', object_id('t_RawCustomer') )

go

 

And finally, here is a Least Privileged User example. These are the minimum permissions that a low-privileged user will need for different operations on the table/view that we created. Note that this is not an exhaustive list of possible operations or permissions on our table; it is just an example of some new and interesting permissions you may want to get familiar with. I strongly suggest to review the SQL Server 2005 documentation for these objects to get a complete list of permissions. In this particular example, I want to emphasize the following operations and permissions:

  • Perform identity lookup queries (i.e. SSN = ‘xxx’ ):
    • GRANT SELECT ON v_Customer to LUA_principal
    • GRANT EXECUTE ON dbo.MAC to LUA_principal. The SP will run under elevated context, therefore no explicit permission on the symmetric key protecting the MAC-keys is needed.
    • GRANT VIEW DEFINITION ON dbo.t_RawCustomer to LUA_principal. This principal will need o access the base table index.
    • NOTE: With these set of permissions, the principal can select from the view v_Customer, but cannot recover the SSN plaintext.
  • Insert a new record
    • All of the Identity lookup queries permissions
    • GRANT INSERT ON t_RawCustomer TO LUA_principal
    • GRANT VIEW DEFINITION ON SYMMETRIC KEY::key_Encryption TO LUA_principal. Needed for the key_guid operation.
    • Grant some way to open the symmetric key. In the example we show 1 mechanism.

 

-- Create a database-only principal for testing purposes

--

CREATE USER LUA_principal WITHOUT LOGIN

go

 

GRANT SELECT ON v_Customer to LUA_principal

go

 

-- We want this principal to be able to perform indexed queries,

-- so we need to grant him permissions on the MAC procedure

GRANT EXECUTE ON dbo.MAC to LUA_principal

go

 

-- We also need to grant the principal view definition on the

-- base table, so he can obtain its index

GRANT VIEW DEFINITION ON dbo.t_RawCustomer to LUA_principal

go

 

-- Run a simple test as this new principal

-- You will notice that the SSN column shows only NULLs.

-- This is because this principal has no access to the encryption key

EXEC ('select * from v_Customer') as USER = 'LUA_principal'

-- Note the new principal has no permissions at all on the base table!

EXEC ('select * from t_RawCustomer') as USER = 'LUA_principal'

go

 

-- Now run a select with a WHERE clause

-- As it is possible to execute the MAC function,

-- and the caller knows exactly what SSN he is looking for,

-- it is possible to run the query.

-- Notice that the SSN column shows NULL.

EXECUTE AS USER = 'LUA_principal'

go

SELECT * FROM v_Customer

        where SSN_Id = dbo.MAC( N'111-11-1112', object_id('t_RawCustomer') )

go

REVERT

go

 

-- Now let's give the LUA permission to update the data

GRANT UPDATE ON v_Customer(ExtraData) TO LUA_principal

go

 

EXECUTE AS USER = 'LUA_principal'

go

update v_Customer set ExtraData=N'data updated by LUA'

        where SSN_Id = dbo.MAC( N'111-11-1112', object_id('t_RawCustomer') )

go

SELECT * FROM v_Customer

        where SSN_Id = dbo.MAC( N'111-11-1112', object_id('t_RawCustomer') )

go

REVERT

go

 

-- Now give our LUA_principal permission to insert data into

-- the table

GRANT INSERT ON t_RawCustomer TO LUA_principal

go

 

EXECUTE AS USER = 'LUA_principal'

go

-- NOTE: This one will fail as we don't have access to the key

-- for the data or the certificate protecting it!

insert into t_RawCustomer values ( null, N'111-11-1113',

     N'customer 3', N'New data 3' )

go

SELECT * FROM v_Customer

go

REVERT

go

 

-- To give access to the encryption key to the LUA principal,

-- we have 2 options:

-- Option 1: Grant direct permission to the key

--     GRANT VIEW DEFINITION ON SYMMETRIC KEY::key_Encryption

--       TO LUA_principal

--     GRANT CONTROL ON CERTIFICATE::cert_ProtectEncryptionKeys

--       TO LUA_principal

-- Option 2:

--     GRANT VIEW DEFINITION ON SYMMETRIC KEY::key_Encryption

--       TO LUA_principal

--  Create a SP that opens the key for the calling context

--  and grant the LUA principal access to it.

--

-- For this demo we will use option 2.

-- The main advantage is to limit the control

-- permission on the certificate

-- as it may be protecting more than 1 key

--

 

-- We need it for key_guid('key_Encryption'),

-- otherwise we cannot look for the GUID

GRANT VIEW DEFINITION ON SYMMETRIC KEY::key_Encryption TO LUA_principal

go

 

CREATE PROC sp_GetCustomersKeyAccess

WITH EXECUTE AS 'dbo'

as

        OPEN SYMMETRIC KEY key_Encryption DECRYPTION BY CERTIFICATE cert_ProtectEncryptionKeys

go

 

GRANT EXECUTE ON sp_GetCustomersKeyAccess TO LUA_principal

go

 

 

--- Test the new SP

EXECUTE AS USER = 'LUA_principal'

go

-- Empty

SELECT * FROM sys.openkeys

EXEC sp_GetCustomersKeyAccess

-- Now we have the required key open

SELECT * FROM sys.openkeys

go

-- and we can close it when it is not needed anymore

CLOSE SYMMETRIC KEY key_Encryption

go

SELECT * FROM sys.openkeys

go

REVERT

go

 

 

-- Now try the failed insert once more...

EXECUTE AS USER = 'LUA_principal'

go

EXEC sp_GetCustomersKeyAccess

go

insert into t_RawCustomer values ( null, N'111-11-1113', N'customer 3', N'New data 3 inserted by ' + user_name() )

insert into t_RawCustomer values ( null, N'111-11-1114', N'customer 4', N'New data 4 inserted by ' + user_name() )

go

-- Note that because LUA_principal doesn't have direct access

-- to the certificate protecting the encryption key,

-- the SSN column will still display NULLs.

-- As we only wanted to grant this principal permission

-- to insert new data and not to read arbitrary data,

-- this should be sufficient

SELECT * FROM v_Customer

go

-- Close the key after using it

CLOSE SYMMETRIC KEY key_Encryption

go

REVERT

go

 

-- finally, if you want to allow the LUA principal to see

-- all entries in the table.

-- SECURITY NOTE; After granting control on the certificate,

-- the principal will have full access to the PVK,

-- this means thsi principal can decrypt

-- and sign anything using this certificate.

-- Make sure this permission is granted only to trusted principals

-- and try to monitor activity on this certificate

GRANT CONTROL ON CERTIFICATE::cert_ProtectEncryptionKeys TO LUA_principal

go

 

EXEC ('select * from v_Customer') as USER = 'LUA_principal'

Go

 

Search

This Blog

Syndication

Page view tracker