- Audit on SQL Server (Server Audit)
- How to Create an Audit to Monitor Job Creation, Modification, and Deletion in SQL Server
- How to create an Audit trigger to log object manipulation in SQL Server
- SQL Server - How to implement audit and control of logins (Logon Trigger)
- Monitoring DDL and DCL Operations Using SQL Server fn_trace_gettable
- Using SQL Server Standard Trace to Audit Events (fn_trace_gettable)
- SQL Server - Permissions and privileges audit trigger at database and instance level (GRANT and REVOKE)
- SQL Server - How to Monitor and Audit Data Changes in Tables Using Change Data Capture (CDC)
- SQL Server 2016 - How to “Time Travel” Using the Temporal Tables Feature
- SQL Server - How to use auditing to map actual required permissions on a user
- SQL Server - Trigger to prevent and prevent changes to tables
- SQL Server - How to Create a Data Change History for Your Tables (Audit Logs)
- SQL Server - How to Avoid Brute Force Attacks on Your Database
- SQL Server Security Checklist - An SP with more than 70 security items to validate your database.
- SQL Server - How to know the date of a user's last login
- SQL Server - How to Avoid and Protect Against Ransomware Attacks Like WannaCry on Your Database Server
- SQL Server – Beware of server role securityadmin! Using privilege elevation to become sysadmin
- SQL Server - How to Avoid SQL Injection? Stop using Dynamic Query as EXEC (@Query). Now.
- SQL Server - Understanding the Risks of TRUSTWORTHY Property Enabled in a Database
- SQL Server - Password Policies, Password Expiration, Mandatory Password Change, and Login Lockout after N attempts
Speak guys!
In this article, I will demonstrate how to use auditing to map a user's actual required permissions, identifying everything that that user actually did in the database over the observed time, to generate a script granting only the permissions he really needs, thus eliminating need to have integration users and users other than the main application with permissions on all database objects such as db_owner, db_datareader and / or db_datawriter, for example.
During customer service, it is very common to see situations where users of integrations between systems or processes are created in the database exclusively for this process (good practice), but even having to access / change few tables, they end up being granted permissions on all. objects of this database. Especially in integration users, which are not always developed by the internal team and in many cases, the integration users' system is not even the responsibility of the local IT team and can be accessed via the internet, for example, a breach in this system. External marketing can end up being catastrophic for your business.
To mitigate this issue, I will show you how to log everything these users do into the database and allow you to apply only the required permissions. For this need, I will use the Server Audit feature, available since SQL Server 2008 in the Enterprise version and from SQL Server 2012 in the Standard version. I will also use the Database Audit feature, available in the Standard version only from SQL Server 2016 SP1.
In short, to be able to run the scripts in this article, you will need to meet one of the following conditions:
- SQL Server 2012 or above, Enterprise, Datacenter (2008), or Developer Edition
- SQL Server 2016 SP1 or above (any edition, up to Express)
Creating the table to store access history
After the introduction above, let's now start monitoring table accesses. First of all, I will create the table that will store the history of the accesses collected by the audit.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
CREATE TABLE [dbo].[Auditoria_Acesso] ( [Id_Auditoria] [bigint] NOT NULL IDENTITY(1, 1), [Dt_Auditoria] [datetime] NOT NULL, [Cd_Acao] [varchar] (60) COLLATE SQL_Latin1_General_CP1_CI_AI NOT NULL, [Ds_Maquina] [varchar] (128) COLLATE SQL_Latin1_General_CP1_CI_AI NOT NULL, [Ds_Usuario] [varchar] (128) COLLATE SQL_Latin1_General_CP1_CI_AI NOT NULL, [Ds_Database] [varchar] (128) COLLATE SQL_Latin1_General_CP1_CI_AI NOT NULL, [Ds_Schema] [varchar] (128) COLLATE SQL_Latin1_General_CP1_CI_AI NOT NULL, [Ds_Objeto] [varchar] (128) COLLATE SQL_Latin1_General_CP1_CI_AI NOT NULL, [Ds_Query] [varchar] (max) COLLATE SQL_Latin1_General_CP1_CI_AI NULL, [Fl_Sucesso] [bit] NOT NULL, [Ds_IP] [varchar] (128) COLLATE SQL_Latin1_General_CP1_CI_AI NOT NULL, [Ds_Programa] [varchar] (128) COLLATE SQL_Latin1_General_CP1_CI_AI NOT NULL, [Qt_Duracao] [bigint] NOT NULL, [Qt_Linhas_Retornadas] [bigint] NOT NULL, [Qt_Linhas_Alteradas] [bigint] NOT NULL ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] WITH ( DATA_COMPRESSION = PAGE ) GO ALTER TABLE [dbo].[Auditoria_Acesso] ADD CONSTRAINT [PK__Auditori__E9F1DAD4EE3743FE] PRIMARY KEY CLUSTERED ([Id_Auditoria]) WITH (DATA_COMPRESSION = PAGE) ON [PRIMARY] GO |
Creating Server Audit by Filtering Users
The second step to being able to audit the hits made by certain users, let's create a server audit on the instance that will be used in the next topic to capture these events. Remember that the Server Audit feature is available since SQL Server 2008 in the Enterprise version and SQL Server 2012 in the Standard version. Also, you can only use filters in Server Audit from SQL Server 2012.
In the example below, I am defining that auditing will create up to 16 files of 10 MB each. As files are being filled, new files with the collected data will be created until they reach the set limit (16 files). When all files have already been created and are fully populated, then the oldest files will be overwritten with the new information. For this reason, it is important to have a routine for collecting data from files and storing in tables.
In addition, starting with SQL Server 2012, we can apply a number of filters to refine our searches, such as capturing user data that ends with '% User' or that starts with LS_% and also bypass SSMS Intellisense and RedGate tools, in addition to not collecting data from users who are in the domain “MEUDOMINIO” and also not collecting data from the user “usrDirceuResende”.
In this step we can filter the names of users, software, IP's or Hostnames that will be used to refine the searches to return only the desired data.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
USE [master] GO IF ((SELECT COUNT(*) FROM sys.server_audits WHERE [name] = 'Auditoria_Acessos') > 0) BEGIN ALTER SERVER AUDIT [Auditoria_Acessos] WITH (STATE = OFF); DROP SERVER AUDIT [Auditoria_Acessos] END CREATE SERVER AUDIT [Auditoria_Acessos] TO FILE ( FILEPATH = N'C:\Audit\', MAXSIZE = 10 MB, MAX_ROLLOVER_FILES = 16, RESERVE_DISK_SPACE = OFF ) WITH ( QUEUE_DELAY = 1000, ON_FAILURE = CONTINUE, AUDIT_GUID = '0b5ad307-ee47-43db-a169-9af67cb661f9' ) WHERE (([server_principal_name] LIKE '%User' OR [server_principal_name] LIKE 'LS_%') AND [application_name]<>'Microsoft SQL Server Management Studio - Transact-SQL IntelliSense' AND NOT [application_name] LIKE 'Red Gate Software%') GO ALTER SERVER AUDIT [Auditoria_Acessos] WITH (STATE = ON) GO |
Creating Database Audit capturing accesses
In the third step in setting up our monitoring, let's now create a database audit for each database that you want to monitor for permissions. In the example below, I will monitor the INSERT / DELETE / UPDATE / SELECT / EXECUTE events performed by any bank user (public).
Remember that while Database Audit seems to capture the events of all users, in Server Audit we already limit the users that must be returned and this filter is also respected in Database Audit.
The code below will go through all databases not in the exceptions list (master, tempdb, msdb and model) and will create the database audit.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
DECLARE @Query VARCHAR(MAX) SET @Query = ' IF (''?'' NOT IN (''master'', ''tempdb'', ''model'', ''msdb'')) BEGIN USE [?]; IF ((SELECT COUNT(*) FROM sys.database_audit_specifications WHERE [name] = ''Auditoria_Acessos'') > 0) BEGIN ALTER DATABASE AUDIT SPECIFICATION [Auditoria_Acessos] WITH (STATE = OFF); DROP DATABASE AUDIT SPECIFICATION [Auditoria_Acessos]; END CREATE DATABASE AUDIT SPECIFICATION [Auditoria_Acessos] FOR SERVER AUDIT [Auditoria_Acessos] ADD (DELETE ON DATABASE::[?] BY [public]), ADD (EXECUTE ON DATABASE::[?] BY [public]), ADD (INSERT ON DATABASE::[?] BY [public]), ADD (SELECT ON DATABASE::[?] BY [public]), ADD (UPDATE ON DATABASE::[?] BY [public]) WITH (STATE = ON); END' EXEC sys.sp_MSforeachdb @Query |
Creating a Stored Procedure to Store Collected Data
In the 4 step of the routine, we will create the Stored Procedure responsible for reading the data from the audit files and writing the data to the table created in step 1. Remember to create a SQL Agent job to perform the procedure below every X minutes.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
IF (OBJECT_ID('dbo.stpAuditoria_Acessos_Carrega_Dados') IS NULL) EXEC('CREATE PROCEDURE dbo.stpAuditoria_Acessos_Carrega_Dados AS SELECT 1') GO ALTER PROCEDURE dbo.stpAuditoria_Acessos_Carrega_Dados AS BEGIN DECLARE @TimeZone INT = DATEDIFF(HOUR, GETUTCDATE(), GETDATE()) DECLARE @Dt_Max DATETIME = DATEADD(SECOND, 1, ISNULL((SELECT MAX(Dt_Auditoria) FROM dirceuresende..Auditoria_Acesso), '1900-01-01')) INSERT INTO dirceuresende.dbo.Auditoria_Acesso ( Dt_Auditoria, Cd_Acao, Ds_Maquina, Ds_Usuario, Ds_Database, Ds_Schema, Ds_Objeto, Ds_Query Fl_Sucesso, Ds_IP, Ds_Programa, Qt_Duracao, Qt_Linhas_Retornadas, Qt_Linhas_Alteradas ) SELECT DISTINCT DATEADD(HOUR, @TimeZone, event_time) AS event_time, action_id, server_instance_name, server_principal_name, [database_name], [schema_name], [object_name], [statement], succeeded, client_ip, application_name, duration_milliseconds, response_rows, affected_rows FROM sys.fn_get_audit_file('C:\Audit\*.sqlaudit', DEFAULT, DEFAULT) WHERE DATEADD(HOUR, @TimeZone, event_time) >= @Dt_Max END |
Querying the collected data
Finally, in the 5 step of this routine, we will read the data collected and stored in the dirceuresende.dbo.Audit_Access table and identify the accesses made by each user in each database. This will help you identify what these users are currently accessing in the database. No more excuse for all users to be db_owner from databases.
If you also want to generate the scripts to grant current permissions, you can use this script:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
SELECT DISTINCT Ds_Usuario, Ds_Database, Cd_Acao, Ds_Objeto, 'USE [' + Ds_Database + ']; GRANT ' + (CASE Cd_Acao WHEN 'UP' THEN 'UPDATE' WHEN 'IN' THEN 'INSERT' WHEN 'DL' THEN 'DELETE' WHEN 'SL' THEN 'SELECT' WHEN 'EX' THEN 'EXECUTE' END) + ' ON [' + Ds_Schema + '].[' + Ds_Objeto + '] TO [' + Ds_Usuario + '];' AS Comando FROM dirceuresende..Auditoria_Acesso WHERE Cd_Acao <> 'UNDO' ORDER BY Ds_Usuario, Ds_Database, Ds_Objeto |
Ready! Access audit routine implemented. The next step now is to use the collected data to grant the permissions users actually used during the time Audit was enabled and remove elevated permissions from those users, such as sysadmin and db_owner.
Observation: A very important point to keep in mind is how many records this table will keep. In environments that have many concurrent users, this access data collection can end up generating a very large data volume and consuming a lot of disk space. Because of this, it is always important to keep track of data volume and create a table cleanup routine, keeping only X days of history.
Besides, I don't need to mention that I recommend that you try it a lot before applying it in production, right? Depending on the amount of transactions per second, this can even have a performance impact by enabling this feature (although I've already tested in very critical environments and haven't noticed any changes).
Well guys, I hope it helped you have a way to validate all objects accessed by certain users, helping you in mapping the required permissions.
A big hug and even more!
Good Morning.
very cool will help me a lot!
meanwhile I ran all the commands
created everything just right, but I'm forcing insert, updade and when I select in the table Auditoria_Acesso or execute the procedure stpAuditoria_Acessos_Coada_Dados
only the tables return me empty .. could you help me?
Grato
Cavendish
Excellent…
A piece of code is missing a comma
Ds_Object,
Ds_Query
Fl_Success,
INSERT INTO dirceuresende.dbo.Auditoria_Accesso
(
Date_Audit,
Cd_Action,
Ds_machine,
Ds_User,
Ds_Database,
Ds_Schema,
Ds_Object,
Ds_Query
Fl_Success,
Ds_IP,
Ds_Program,
Qt_Duration,
Returned Qt_Lines,
Qt_Lines_Changed
)
Very good Dirceu, thanks for sharing.
I'm even using where I work hehehehehe
Dirceu, good morning.
I really liked the post, if you allow me, I will use this material in my database class at Fatec São Roque, in the security part.
Hugs.