DDL in Stored Procedures and The Magic Of WITH EXECUTE AS
Posted by Josh | Posted in Notes From The Lab, SQL Server | Posted on 05-04-2011
Tags: Security, SQL Server
1
Twice over the last week I’ve run into a scenario where a user is trying to execute a stored procedure and gets a message similar to the following:
Msg 1088, Level 16, State 7, Procedure p_TruncateTest, Line 5
Cannot find the object "TestTable" because it does not exist or you do not have permissions.
At first this was a bit puzzling, because the user in question had both EXECUTE rights on the stored procedure, along with VIEW DEFINITION rights across the schema. I’m used to the idea that as long as a user is granted rights to execute a procedure, and the procedure is owned by the same principal as the underlying object being manipulated, no other permissions are checked. It’s one of the fundamental reasons why stored procedures are such a good practice.
In this case though, things were a little more complex. It turns out that the line in question was issuing a TRUNCATE statement. Now, if this were executed via dynamic SQL, I could understand this being an issue. But it was hardcoded; nothing dynamic about it. That being the case, I wanted to see if the behavior was reproducible.
To the lab!
CREATE TABLE dbo.TestTable (c CHAR(1)); CREATE USER TruncateUser WITHOUT LOGIN; GO CREATE PROCEDURE dbo.p_TruncateTest AS BEGIN TRUNCATE TABLE TestDB; END GO GRANT EXECUTE ON dbo.p_TruncateTest TO [TruncateUser]; GO EXECUTE AS USER = 'TruncateUser'; EXEC dbo.p_TruncateTest;
Sure enough, the same message popped up. So it seems the TRUNCATE statement does not behave the same way as other DML style statements, such as SELECT,INSERT,UPDATE,DELETE. So that got me thinking: was this the same for other DDL statements?
REVERT; GO CREATE PROCEDURE dbo.p_AlterTest AS BEGIN ALTER TABLE TestTable ADD c2 CHAR(1); END GO GRANT EXECUTE ON dbo.p_AlterTest TO TruncateUser; EXECUTE AS USER = 'TruncateUser'; EXEC dbo.p_AlterTest; GO
After executing this I was greeted with the same exact error message.
Ok, so the behavior is consistent with other forms of DDL. That being the case, I needed to find some alternatives to actually granting the user the full ALTER rights on the objects in question, since that would allow them to do all sorts of other nasty things.
Enter EXECUTE AS.
From this BOL article, we see that we can use this nifty clause to effectively elevate a calling user’s rights for the duration of the stored procedure execution. In this case, we’re going to have the procedure execute as the ‘dbo’ user. Normally I’d say it’s a better practice to have the execution be under a less privileged user, such as one that owns the application schema, and not one in the db_owner role.
REVERT; GO ALTER PROCEDURE dbo.p_TruncateTest WITH EXECUTE AS 'dbo' AS BEGIN TRUNCATE TABLE dbo.TestTable; END GO EXECUTE AS USER = 'TruncateUser'; EXEC dbo.p_TruncateTest; GO
Voila! The procedure executes without issue.
Now there is one catch here: when using that WITH EXECUTE AS clause you have be careful about how you determine the name of the user performing the operation. If, for example, you use the CURRENT_USER function to log who issued the command, what you’ll see is the user named in the WITH EXECUTE AS clause, not the calling user. Instead, make sure you use the ORIGINAL_LOGIN function. Thanks to Kimberly Tripp for pointing this out.
