7 – Securing access to SQL Server 185 ServerName Service_Name Service_Account Server1 MSSQLServerOLAPService LocalSystem Server1 SQLAgent$SRVSAT LocalSystem Server1 SQLBackupAgent LocalSystem Server1 SQLBackupAgent_SRVSAT LocalSystem Server1 SQLBrowser LocalSystem Server1 SQLSERVERAGENT LocalSystem Server1 NULL NULL Table 7.4: Service credentials query results. While I do not use this query often, it always saves me many frustrating minutes of trying to manually find the same information, via tools such as Computer Management and Services. Surveillance To this point, I have focused on finding logins, users, groups and service accounts. The queries presented so far have all been useful for managing many hundreds if not thousands of accounts, all with some level of privilege to my SQL Server instances. However, knowing who has access to the data, and what level of access they have, is only one aspect of security I want to touch on in this chapter. It is also crucial for the DBA to track such actions as failed login attempts and to audit, as far as possible, the actions of users once they are in amongst the data. In this section, I will introduce three surveillance techniques to help with these issues: Error Log interrogation with T-SQL, DDL Triggers and Server-side Tracing. Error log interrogation Unlike a lot of DBAs that I know, I do not scour the SQL Error logs daily. I tend to review them when looking for a specific error, or when conducting a periodic security review. It is not that I think it is a waste of time to do it, I just think that I 7 – Securing access to SQL Server 186 would much prefer to read the logs with T-SQL. Fortunately, SQL Server offers two stored procedures to make this possible, namely sp_enumerrorlogs and sp_readerrolog. As Figure 7.2 shows, sp_enumerrorlogs simply lists the available SQL Server error logs. Figure 7.2: Querying the SQL Server error logs with sp_enumerrorlogs. The procedure sp_readerrorlog accepts the Archive #, from sp_enumerrorlogs, as input and displays the error log in table form, as shown in Figure 7.3, where you can see that the first archived log file (1) is passed in as a parameter. Archive number 0 refers to the current error log. It is possible to load and query every error log file by combining the two stored procedures with a bit of iterative code. Listing 7.7 shows the custom code used to loop through each log file, store the data in a temp table, and subsequently query that data to find more than five consecutive failed login attempts, as well as the last good login attempt. In order for this to work, you will need to enable security logging for both successful and failed logins, as most production servers should do. This can be configured via the Security tab of the Server Properties. Finally, note that this query will only work for SQL Server 2005 and 2008. 7 – Securing access to SQL Server 187 Figure 7.3: Using sp_readerrorlog. DECLARE @TSQL NVARCHAR(2000) DECLARE @lC INT CREATE TABLE #TempLog ( LogDate DATETIME, ProcessInfo NVARCHAR(50), [Text] NVARCHAR(MAX)) CREATE TABLE #logF ( ArchiveNumber INT, LogDate DATETIME, LogSize INT ) INSERT INTO #logF EXEC sp_enumerrorlogs SELECT @lC = MIN(ArchiveNumber) FROM #logF WHILE @lC IS NOT NULL BEGIN INSERT INTO #TempLog EXEC sp_readerrorlog @lC SELECT @lC = MIN(ArchiveNumber) FROM #logF WHERE ArchiveNumber > @lC END 7 – Securing access to SQL Server 188 Failed login counts. Useful for security audits. SELECT Text,COUNT(Text) Number_Of_Attempts FROM #TempLog where Text like '%failed%' and ProcessInfo = 'LOGON' Group by Text Find Last Successful login. Useful to know before deleting "obsolete" accounts. SELECT Distinct MAX(logdate) last_login,Text FROM #TempLog where ProcessInfo = 'LOGON'and Text like '%SUCCEEDED%' and Text not like '%NT AUTHORITY%' Group by Text DROP TABLE #TempLog DROP TABLE #logF Listing 7.7: Searching for failed login attempts. The results of this query are shown in Figure 7.4. Figure 7.4: Querying for successful and unsuccessful login attempts. We can see that there is a "BadPerson" out there who has tried 15 times to access this server. The second result set shows the st successful login for a certain account, retrieved using the MAX() function for the last_login field. While this particular example probes login attempts for security auditing purposes, the same solution can be easily tweaked to accommodate all manner of error log analysis, from database errors to backup failures. 7 – Securing access to SQL Server 189 DDL triggers In Chapter 1, I included in the Configuration script (Listing 1.2) code to create a DDL trigger that would alert the DBA to any database creation or deletion (drop). I'm now going to demonstrate how to use this to track DDL actions, and what you can expect to see with this DDL trigger enabled on your SQL Servers. DDL (Data Definition Language) triggers are very similar to the DML (Data Manipulation Language) triggers, with which you are undoubtedly familiar. DDL triggers can be scoped at either the database or server level, meaning they can be set to fire when a particular statement, such as ALTER TABLE, is issued against a specific database, or when a DDL statement is issued at the server level, such as CREATE LOGIN. Listing 7.7 shows the code to create the DDL trigger, AuditDatabaseDDL, which you may have missed amongst everything else going on in Listing 1.2. Notice that the scope of the trigger, in this case, is ALL SERVER. The Eventdata() function is employed to set the values of the variables that will ultimately be mailed to the DBAs when the DDL event occurs, in this case when a database is created or dropped from the server where the trigger is created. Setup DDL Triggers Setup Create Database or Drop Database DDL Trigger /****** Object: DdlTrigger [AuditDatabaseDDL] Script Date: 02/05/2009 19:56:33 ******/ SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO CREATE TRIGGER [AuditDatabaseDDL] ON ALL SERVER FOR CREATE_DATABASE, DROP_DATABASE AS DECLARE @data XML, @tsqlCommand NVARCHAR(MAX), @eventType NVARCHAR(100), @serverName NVARCHAR(100), @loginName NVARCHAR(100), @username NVARCHAR(100), @databaseName NVARCHAR(100), @objectName NVARCHAR(100), @objectType NVARCHAR(100), @emailBody NVARCHAR(MAX) . access to SQL Server 185 ServerName Service_Name Service_Account Server1 MSSQLServerOLAPService LocalSystem Server1 SQLAgent$SRVSAT LocalSystem Server1 SQLBackupAgent LocalSystem Server1 SQLBackupAgent_SRVSAT. SQLBackupAgent LocalSystem Server1 SQLBackupAgent_SRVSAT LocalSystem Server1 SQLBrowser LocalSystem Server1 SQLSERVERAGENT LocalSystem Server1 NULL NULL Table 7.4: Service credentials query results time to do it, I just think that I 7 – Securing access to SQL Server 186 would much prefer to read the logs with T -SQL. Fortunately, SQL Server offers two stored procedures to make this possible,