SQL Server 2005: Demo for enabling database impersonation for cross database access

There is an excellent article on this topic in Books Online: Extending Database Impersonation by Using Execute As. I just wrote a small demo to illustrate the techniques described in that article. It can be used as a companion to that article, if you're looking for a more hands-on approach to learning about this subject. The demo is quite long and I have plenty of comments describing what is going on, but I'll include a short description as well: We have two database owners, Alice and Bob. Charles is a third principal that has access to some objects in Bob's database. We're looking at methods that allow (and restrict) how Alice can impersonate Charles inside her database and how she can then attempt to access the objects from Bob's database. The demo shows how the TRUSTWORTHY bit can be used and then it shows an alternative method of using a certificate as an authenticator. For the latter method, I also showed how Bob can allow Alice to sign a procedure without granting her access to the private key of the certificate used for signing. So here's the demo:

-- A demo for cross database access
--

-- create our principals: alice, bob, charles
--
create login alice with password = 'SimplePwd01'
create login bob with password = 'SimplePwd01'
create login charles with password = 'SimplePwd01'

-- create two databases owned by alice and bob
--
create database db_alice
create database db_bob

alter authorization on database::db_alice to alice
alter authorization on database::db_bob to bob

--###################
--#Setting up db_bob#
--###################

use db_bob

-- execute the following as bob, this database's dbo
--
execute as login = 'bob'

-- add charles to bob's database
--
create user charles

-- create a table and a procedure accessing it,
-- both owned by charles
--
create table t (c int)
insert into t values (42)

alter authorization on t to charles

create procedure proc_select_t as
begin
   
select * from sys.login_token
   
select db_name()
   
select * from sys.user_token

    print 'Selecting from t...'
   
select * from t
end

alter authorization on proc_select_t to charles

create procedure proc_drop_t as
begin
   
select * from sys.login_token
   
select db_name()
   
select * from sys.user_token

    print 'Dropping t...'
   
drop table t
end

alter authorization on proc_drop_t to charles

-- verify the selection procedure
--
execute as user = 'charles'

exec proc_select_t

-- revert impersonation of charles
--
revert

-- revert impersonation of bob
--
revert

--#####################
--#Setting up db_alice#
--#####################

use db_alice

-- execute the following as alice, this database's dbo
--
execute as login = 'alice'

-- add charles to alice's database
--
create user charles

-- note that alice cannot impersonate charles at server level
-- the following call will produce an ERROR!
--
execute as login = 'charles'

-- however, being the dbo, she can impersonate charles at database level
--
execute as user = 'charles'

select * from sys.login_token
select * from sys.user_token

revert

-- create a procedure for cross database access
--
go
create procedure proc_crossdb
 
with execute as 'charles'
as
begin
   
select * from sys.login_token
   
select db_name()
   
select * from sys.user_token

    exec db_bob.dbo.proc_select_t
end
go

-- call the procedure, to impersonate charles and access bob's database
--
exec dbo.proc_crossdb
--
-- won't work - database is not trustworthy
-- user token is "sandboxed"

-- revert the impersonation of alice
--
revert

--####################################
--#Setting up db_alice as trustworthy#
--####################################

-- let's mark alice's database as trustworthy
--
alter database db_alice set trustworthy on

--###################################
--#Checking proc_crossdb in db_alice#
--###################################

-- now let's see if this changes anything
--
execute as login = 'alice'

exec dbo.proc_crossdb
--
-- we now see alice as an authenticator,
-- but bob doesn't trust alice to authenticate,
-- so it still doesn't work

revert

--#######################################################
--#Setting up alice as trusted to authenticate in db_bob#
--#######################################################

-- let's make bob trust alice to authenticate
--
use db_bob

execute as login = 'bob'

create user alice

grant authenticate to alice

revert

--###################################
--#Checking proc_crossdb in db_alice#
--###################################

-- let's see how things work now
--
use db_alice

execute as login = 'alice'

exec dbo.proc_crossdb
--
-- the token authenticated by alice is now accepted

revert

--##########
--#Comments#
--##########

-- unfortunately, this method would also allow alice
-- to rewrite the proc_crossdb procedure and call proc_drop_t instead
--
-- what do we do if we don't trust alice, but we'd like to enable the select scenario?
--
-- signing allows better control because it requires trusting code not principals

--##################################################
--#Cleaning up db_bob and setting it up for signing#
--##################################################

-- first, let's remove the trustworthy setting on alice's database
-- and bob's trust in alice as an authenticator
--
alter database db_alice set trustworthy off

use db_bob

execute as login = 'bob'

-- remove alice, which will drop any permissions as well
--
drop user alice

-- now we'll use signing to enable cross database access
-- we'll create a certificate and a principal mapped to it
-- that will be used as authenticator
--
create certificate cert_sign_alice
  encryption
by password = 'SimplePwd01'
 
with subject = 'Sign procedures in Alice''s database'

create user u_cert_sign_alice from certificate cert_sign_alice

grant authenticate to u_cert_sign_alice

-- we create and sign the proc_crossdb procedure in bob's database,
-- because we don't want to give the private key of the certificate to alice
--
go
create procedure proc_crossdb
 
with execute as 'charles'
as
begin
   
select * from sys.login_token
   
select db_name()
   
select * from sys.user_token

    exec db_bob.dbo.proc_select_t
end
go

add signature to proc_crossdb by certificate cert_sign_alice with password = 'SimplePwd01'

-- retrieve the signature from the catalogs
--
select crypt_property from sys.crypt_properties where major_id = object_id('proc_crossdb')
--
-- the value returned will change because the certificate will be generated randomly
-- we need to copy the returned value
-- for my execution, I got back:
-- 0x17B803D0550C450AD4D815CB2CEB730E941E2F8BD41B6848B2546D0657E7F85FEDA69FF9048F358AECCBE0B7E9B4AC3F7420513AB539E6B87C8E638FB9AF2F557A3CB389D4ECA72DE1C34523AEDF48E2AB290AA94EC496CBAF527D3D0B95B7395DD7A77ED06A0894102C653DFC6425A317DD383B4F79C8AF8A7A5A62E5AA1899

-- backup the public key of the certificate to a file alice can access
-- remember that the private key is needed for signing and the public key
-- is only used for verification, hence alice won't be able to arbitrarily sign code
--
backup certificate cert_sign_alice to file = 'cert_sign_alice.cer'
--
-- if you rerun this script, you'll get an error if you already created the file
-- you can either change the name of the file or drop the old file

revert
--
-- we're done, now alice just has to take the signature and the certificate
-- and apply the signature to the exact same procedure - she cannot change the code
-- or the signature won't work
-- bob can provide all these (procedure, certificate, and signature) to alice

--#################################
--#Setting db_alice to use signing#
--#################################

use db_alice

execute as login = 'alice'

create certificate cert_sign_alice from file = 'cert_sign_alice.cer'

create user u_cert_sign_alice from certificate cert_sign_alice

add signature to proc_crossdb by certificate cert_sign_alice
 
with signature = 0x17B803D0550C450AD4D815CB2CEB730E941E2F8BD41B6848B2546D0657E7F85FEDA69FF9048F358AECCBE0B7E9B4AC3F7420513AB539E6B87C8E638FB9AF2F557A3CB389D4ECA72DE1C34523AEDF48E2AB290AA94EC496CBAF527D3D0B95B7395DD7A77ED06A0894102C653DFC6425A317DD383B4F79C8AF8A7A5A62E5AA1899

-- note that the certificate will appear as authenticator,
-- which will enable the context to be trusted in bob's database,
-- allowing the call to go through
--
exec proc_crossdb

-- Let's see what happens if alice attempts to do something different in the procedure,
-- such as attempting to drop the table
--
alter procedure proc_crossdb
  with execute as 'charles'
as
begin
   
select * from sys.login_token
   
select db_name()
   
select * from sys.user_token

    exec db_bob.dbo.proc_drop_t
end

-- won't work because impersonated context is not authenticated
--
exec proc_crossdb

-- let's try to add the signature
--
add signature to proc_crossdb by certificate cert_sign_alice
 
with signature = 0x17B803D0550C450AD4D815CB2CEB730E941E2F8BD41B6848B2546D0657E7F85FEDA69FF9048F358AECCBE0B7E9B4AC3F7420513AB539E6B87C8E638FB9AF2F557A3CB389D4ECA72DE1C34523AEDF48E2AB290AA94EC496CBAF527D3D0B95B7395DD7A77ED06A0894102C653DFC6425A317DD383B4F79C8AF8A7A5A62E5AA1899
--
-- it won't work because signature is not valid
-- alice's attempt has failed - she can only use the signature
-- with the code provided by bob

revert

--#########
--#Cleanup#
--#########

use master

drop database db_alice
drop database db_bob

drop login alice
drop login bob
drop login charles
--
-- EOD

Published 24 October 06 07:23 by lcris

Comments

No Comments
Anonymous comments are disabled

Search

This Blog

Syndication

Page view tracker