I am trying to understand details of wrapping xp_cmdshell functionality in user defined stored procs, so that other users can just be given execute permission to the stored procs rather than xp_cmdshell.
The steps in this scenario are:
- xp_cmdshell is already enabled and a xp_cmdshell proxy has been created
- A user with db_owner membership creates a stored proc WITH EXECUTE AS
OWNER which calls xp_cmdshell - The user executes the stored proc and therefore executes arbitrary shell code
This is unexpected to me. I would not expect a user with only db_owner to be able to achieve this. (Obviously assuming xp_cmdshell has already been enabled by a sysadmin.)
When the database owner is changed from sa to another low privileged login, then the user stored proc is no longer able to call xp_cmdshell.
USE MASTER;
CREATE DATABASE testdb;
CREATE LOGIN testuser WITH PASSWORD = 'password', CHECK_POLICY=OFF;
CREATE LOGIN dummyuser WITH PASSWORD = 'password', CHECK_POLICY=OFF;
SELECT * from sys.credentials WHERE NAME LIKE '%cmdshell%';
-- returned: 101 ##xp_cmdshell_proxy_account## .....
USE testdb;
EXEC sp_changedbowner 'sa';
CREATE USER testuser FOR LOGIN testuser;
ALTER ROLE db_owner ADD MEMBER testuser;
EXECUTE AS LOGIN = 'testuser';
GO
CREATE PROCEDURE [dbo].[testproc]
WITH EXECUTE AS OWNER
AS
SELECT SUSER_NAME() as [SUSER_NAME()], USER_NAME() as [USER_NAME()];
exec xp_cmdshell 'echo %time%';
GO
SELECT SUSER_NAME() as [SUSER_NAME()], USER_NAME() as [USER_NAME()];
EXEC dbo.testproc;
-- returned: sa dbo proving that the call to xp_cmdshell has succeeded
EXEC xp_cmdshell 'echo %time%';
-- returned: The EXECUTE permission was denied on the object 'xp_cmdshell'
REVERT
EXEC sp_changedbowner 'dummyuser';
EXECUTE AS LOGIN = 'testuser';
EXEC dbo.testproc;
-- returned: The EXECUTE permission was denied on the object 'xp_cmdshell'
-- proving that the sysadmin role of the database owner is relevent
REVERT
Note that I have not granted execute xp_cmdshell permission to any particular user.
I thought that enabling xp_cmdshell was ok if care was taken to only grant execute xp_cmdshell permission carefully, but my example seems to show otherwise.
Since a sysadmin is often a database owner, does this example show a serious security problem, or am I misunderstanding something?
Best Answer
You are misunderstanding what is actually going on here.
That is true in this case, but that is merely due to how you did (or more accurately: did not) configure things (more on that in a moment).
It is.
I would disagree with that. This is just a matter of not understanding the security mechanism, and this does happen to be a tricky case of it, so that is quite understandable.
A few things:
EXECUTE AS OWNER
is not specifically the issue. You could have doneEXECUTE AS N'dbo'
to get the same effect.The database being owned by a
sysadmin
is not specifically the issue. You could have doneEXECUTE AS N'other'
whereother
is the name of a User that is associated with a Login that has a User in[master]
that has been granted execute onxp_cmdshell
to get the same effect.No, server level permissions are not being granted. To prove this, add the following two lines to your test stored procedure:
Go ahead and change the db owner back to
sa
and execute the proc. You will get errors on thesp_configure
and theRECONFIGURE
, and you will only get 1 row back fromsys.dm_exec_sessions
, your session's row, because you do not have theVIEW SERVER STATE
server level permission, somethingsa
definitely has.What you are experiencing is a function of two things:
master
that was granted permission to execxp_cmdshell
(this associated is derived from theEXECUTE AS
combined with the DB ownership)To prove this, exec the following (at the end of your current test code in the question, with
dummyuser
owning the DB):Then do:
and you will now, instead of getting the permission denied error, either have success with
xp_cmdshell
, or you will get the following error if the proxy account is not configured:Even better would be to do the following, which removes the permission from
dummyuser
, creates a new low-privileged account, and grants that account thexp_cmdshell
permission:And then, change the
EXECUTE AS
clause in the stored procedure to beWITH EXECUTE AS 'cmdshell'
. Execute the proc again and you will either have success or the proxy account error if it isn't set up. In this case:sysadmin
login.This works because the
EXECUTE AS
user is associated with a login that has a user inmaster
that has been granted execute onxp_cmdshell
.ALSO, this behavior is most likely tied to the object (i.e.
xp_cmdshell
) truly existing in themssqlsystemresource
database. You can't create an object in[master]
, grant a user (the same being used as theEXECUTE AS
user of the stored procedure, even if that user isdbo
/OWNER
and the database is owned bysa
)EXEC
on the user proc in[master]
, and have it work (not without enablingTRUSTWORTHY
, which you shouldn't do). Not even if the user stored procedure in[master]
is marked as a system stored procedure (I've tried).Of course, ideally you would use Module Signing to take care of the permission instead of
EXECUTE AS
(either associate withsysadmin
server role to use SQL Server service account, or associate with user in[master]
that has been grantedEXECUTE
onxp_cmdshell
to use proxy account, assuming that it has been configured).