xp_cmdshell - Naming, Shaming and Taming

I think most people dealing with SQL Server understand that xp_cmdshell can be a bit of a security hole if not used correctly. But... how do you know it is being used appropriately? Wouldn't it be a nice feature to be able to see exactly who is using xp_cmdshell, when and why?

You can with a SQL Server Audit. SQL Server Audits are built on top of the Extended Events engine, but because they are security related, they cannot be access directly with a

[sql]
CREATE EVENT SESSION ...
[/sql]

syntax. So, how do we do this?.. Here's how:-

First, we create a Server Audit

[sql]
CREATE SERVER AUDIT [xp_cmdshell]
TO FILE (FILEPATH = N'C:\Program Files\Microsoft SQL Server\MSSQL13.MSSQLSERVER\MSSQL\Log')
WHERE object_name = 'xp_cmdshell'
GO
[/sql]

 

Hopefully this is pretty self-explanatory... the Audit log file resides under 'FILEPATH' and the object we are interested in monitoring is
object_name = 'xp_cmdshell'.

ok, cool.

Now we need to create a Server Audit Specification

[sql]
CREATE SERVER AUDIT SPECIFICATION [Audit-xp_cmdshell]
FOR SERVER AUDIT [xp_cmdshell]
ADD (SCHEMA_OBJECT_ACCESS_GROUP),
ADD (AUDIT_CHANGE_GROUP);
GO
[/sql]

 

The final two steps are to simply enable the specification and the audit:

[sql]
ALTER SERVER AUDIT SPECIFICATION [Audit-xp_cmdshell]
WITH (STATE = ON)
GO
[/sql]

and

[sql]
ALTER SERVER AUDIT [xp_cmdshell]
WITH (STATE = ON)
GO
[/sql]

Now, if any programs try to use 'xp_cmdshell', the audit will track it. So, for my example, I did:

[sql]
sp_configure 'xp_cmdshell',1
reconfigure
GO
EXEC xp_cmdshell 'dir *.exe'
GO
[/sql]

and in my server audit, I get:

image

and details of:

image

Nice!... Now to clean up:

[sql]
ALTER SERVER AUDIT [xp_cmdshell]
WITH (STATE = OFF)
GO

ALTER SERVER AUDIT SPECIFICATION [Audit-xp_cmdshell]
WITH (STATE = OFF)
GO
DROP SERVER AUDIT SPECIFICATION [Audit-xp_cmdshell]
GO
DROP SERVER AUDIT [xp_cmdshell]
GO
[/sql]

So, with this approach, we can name and shame the programs using xp_cmdshell thus ensuring we KNOW exactly who, what and when they are doing their work.

 

How about stopping them from using xp_cmdshell at all? How do we go about that one? Obviously we can disable xp_cmdshell with an sp_configure:

[sql]
sp_configure 'xp_cmdshell', 0
reconfigure
go
[/sql]

But what stops a program / job with sufficient access from simply turning it back on again? How about a Server level trigger:

[sql]
CREATE TRIGGER Audit_XP_CMDSHELL
ON ALL SERVER
FOR ALTER_INSTANCE
AS
BEGIN
DECLARE @SQL NVARCHAR(4000);

SET @SQL = (SELECT EVENTDATA().value('(/EVENT_INSTANCE/TSQLCommand/CommandText)[1]', 'nvarchar(4000)'));

IF (CHARINDEX('sp_configure', @SQL) > 0) AND (CHARINDEX('xp_cmdshell', @SQL) > 0)
BEGIN
RAISERROR('Attempt to enable xp_cmdshell detected. This operation is denied!', 16, 1) WITH LOG;
ROLLBACK;
END;
END;
[/sql]

Now, the Server level trigger will fire if someone does an ALTER_INSTANCE type command, collect the EVENTDATA() for the event and look inside it to see if an 'sp_configure' command with an 'xp_cmdshell' has been issued. If we match, we rollback the operation and send a copy of the EVENTDATA to the windows event log.

So - now we have tools to put a measure of control around the use of 'xp_cmdshell'. Obviously this is not foolproof - someone with sufficient access rights could disable the Server trigger, use xp_cmdshell, and re-enable the trigger. But hopefully this gives a good idea of what can be accomplished.