Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

On Quickly Investigating a SQL Monitor Custom Security Alert

DZone 's Guide to

On Quickly Investigating a SQL Monitor Custom Security Alert

What do you do if your SQL Monitor starts sending you custom security alerts?

· Security Zone ·
Free Resource

I recently wrote a series of articles that described four SQL Monitor custom metrics, designed purely to monitor for the signs of intrusion, or other unauthorized database changes. These metrics allow you to implement separate strategies for:

These four metrics, combined, will provide evidence of an attack. It would also be possible to check for suspicious clauses in the SQL that has been executed, but that would take rather more CPU resources.

This article pulls together everything we've learned so far to provide a way of reporting on an intrusion. It will provide a 'narrative' description of all the changes that have been detected, allowing the DBA to investigate fully the sequence of unusual events. If you want to try it out, you'll need to have created all the Extended Events sessions and associated custom metrics, as described in each of the above four articles.

Why the Need to Be Ready?

Imagine that SQL Monitor starts reporting some alarming custom security alerts.

How do you find out quickly what's happened? Given the nature of the alerts, you may be facing an intrusion and a possible leakage of data, so you need to do things quickly, and under stress.

You have the alerts from SQL Monitor, but this will only tell you suspicious things have happened. You now need to drill into the detail to find out what happened and the order in which it happened. To do this, you need to look in different places, which will bring different data to the party. The default trace and Extended Events have rather different information. The logs will sometimes add information and so will a SQL Audit, if you have that running. There will be application-based metrics to add to the mix. You can often get information from the cache, especially the queries. There will be other forensics, which varies between releases such as those in the Logs (SQL Server Forensics is a science that an average geek like me just marvels at, but at a distance).

With these SQL Monitor custom metrics in place, you can query the Extended Event and default trace data underlying each one, as I demonstrated in the previous articles. What you really want though, when you are doing your quick, preliminary investigations, is not the deep details of each one, but the narrative of the changes made in chronological sequence. It is just like the movies where the young geek, surrounded by stern-faced executives, goes clickety-clack on the keyboard, and immediately, the truth is revealed: the scrolling screen flickers, lighting up the grim horror on their faces.

The Strategy

Here, we'll take the Extended events metrics we created and the one that we based on the default trace and blend them together, just using one of the many columns returned, called action, which is a string that tries to recount, in plain English, what the event or trace actually meant. Since the values are simple strings, it is easy to UNION them together and then sort by the date column that provides the local time.

The procedures from which we get the data are slightly more tortuous than one might expect because I wanted each source to be an inline table function. This makes it much easier to UNION together the results and order them by the time and date in which they happened. This meant extracting the routines that fetched the ring-buffer information into variables at the start of the batch.

The Functions

We start by creating the functions. A quick glance will tell you why we want to hide all that stuff in a function; it isn't a pretty sight.

IF Object_Id('dbo.SeeAccessControlChanges') IS NOT NULL
   DROP function dbo.SeeAccessControlChanges
GO
CREATE FUNCTION dbo.SeeAccessControlChanges
/**
Summary: >
  This function gives you a list
  of security events concerning users, roles and logins
  from the default trace
Author: Phil Factor
Date: 04/10/2018
Examples:
   - Select * from dbo.SeeAccessControlChanges(DateAdd(day,-1,SysDateTime()),SysDateTime())
columns: datetime_local, action, data, hostname, ApplicationName, LoginName, traceName, spid, EventClass, objectName, rolename, TargetLoginName, category_id, ObjectType 
Returns: >
   datetime_local datetime2(7)
   action nvarchar(816)
   data ntext
   hostname nvarchar(256)
   ApplicationName nvarchar(256)
   LoginName nvarchar(256)
   traceName nvarchar(128)
   spid int
   EventClass int
   objectName nvarchar(256)
   rolename nvarchar(256)
   TargetLoginName nvarchar(256)
   category_id smallint
   ObjectType nvarchar(128)
        **/
  (
  @Start DATETIME2,--the start of the period
  @finish DATETIME2--the end of the period
  )
RETURNS TABLE
 --WITH ENCRYPTION|SCHEMABINDING, ..
AS
RETURN
  (
  SELECT 
      Convert(--correct the date for local time.
        DATETIME2, 
        SwitchOffset(StartTime, DateName(TzOffset, SysDateTimeOffset()))
             ) AS datetime_local, 
       'User '+Coalesce( LoginName+ ' ','unknown ')+ 
      CASE EventSubclass --interpret the subclass for these traces
        WHEN 1 THEN 'added ' WHEN 2 THEN 'dropped ' WHEN 3 THEN 'granted database access for ' 
        WHEN 4 THEN 'revoked database access from ' ELSE 'did something to ' END+ Coalesce(TargetLoginName,'') 
      + Coalesce( CASE EventSubclass WHEN 1 THEN ' to object ' ELSE ' from object ' end+objectname,databasename)
      +' using the "'+ApplicationName+ '" application' AS action,
      Coalesce(TextData,'') AS [data], hostname, ApplicationName, LoginName, TE.name AS traceName, spid, 
      EventClass, objectName, rolename, TargetLoginName, TE.category_id, 
    SysTSV.subclass_name AS ObjectType
     FROM::fn_trace_gettable(--just use the latest trace
         (SELECT TOP 1 traces.path FROM sys.traces 
            WHERE traces.is_default = 1), DEFAULT) AS DT
      LEFT OUTER JOIN sys.trace_events AS TE
        ON DT.EventClass = TE.trace_event_id
      LEFT OUTER JOIN sys.trace_subclass_values AS SysTSV
        ON DT.EventClass = SysTSV.trace_event_id
       AND DT.ObjectType = SysTSV.subclass_value
    WHERE StartTime BETWEEN @start AND @finish
    AND TargetLoginName IS NOT NULL
  )
GO
/*
now the code to see object changes 
*/

IF Object_Id('dbo.SeeDatabaseObjectChanges') IS NOT NULL
   DROP function dbo.SeeDatabaseObjectChanges
GO
CREATE FUNCTION dbo.SeeDatabaseObjectChanges
/**
Summary: >
  This function gives you a list
  of database object changes that happened between
  the two dates, taken from the default trace
Author: Phil Factor
Date: 04/10/2018
Examples:
   - Select * from dbo.SeeDatabaseObjectChanges(DateAdd(day,-1,SysDateTime()),SysDateTime())
Columns: datetime_local, action, databaseID, TransactionID, Hostname, ApplicationName, LoginName, spid, objectid 
Returns: >
   datetime_local datetime
   action nvarchar(4000)
   databaseID int
   TransactionID bigint
   Hostname nvarchar(256)
   ApplicationName nvarchar(256)
   LoginName nvarchar(256)
   spid int
   objectid int
        **/
  (
  @Start DATETIME2,--the start of the period
  @finish DATETIME2--the end of the period
  )
RETURNS TABLE
 --WITH ENCRYPTION|SCHEMABINDING, ..
AS
RETURN
  (
    SELECT     
      Convert(--correct the date for local time.
        DATETIME2, 
        SwitchOffset(StartTime, DateName(TzOffset, SysDateTimeOffset()))
             ) AS datetime_local, 
         'User '+Coalesce(SessionLoginName,loginName,'') +' '+ Replace(name, 'Object:','')
             +Coalesce(' '+objtype,'')+' '+ Coalesce(DatabaseName+'.'+ObjectName,databasename)
             +' using the "'+ApplicationName+ '" application'  AS action,
      databaseID, TransactionID, Hostname, ApplicationName, LoginName, spid,objectid
     FROM::fn_trace_gettable(--just use the latest trace
         (SELECT TOP 1 traces.path FROM sys.traces 
            WHERE traces.is_default = 1), DEFAULT) AS DT
      LEFT OUTER JOIN sys.trace_events AS TE
        ON DT.EventClass = TE.trace_event_id
      LEFT OUTER JOIN sys.trace_subclass_values AS SysTSV
        ON DT.EventClass = SysTSV.trace_event_id
       AND DT.ObjectType = SysTSV.subclass_value
      LEFT OUTER JOIN
      (
   VALUES(8259, 'Check Constraint'),( 8260, 'Default (constraint or standalone)'),( 8262, 'Foreign-key Constraint'),( 8272, 'Stored Procedure'),
   ( 8274, 'Rule'),( 8275, 'System Table'),( 8276, 'Trigger on Server'),( 8277, 'User Table'),( 8278, 'View'),
   ( 8280, 'Extended Stored Procedure'),(16724, 'CLR Trigger'),(16964, 'Database'),(16975, 'Object'),(17222, 'FullText Catalog'),
   (17232, 'CLR Stored Procedure'),(17235, 'Schema'),(17475, 'Credential'),(17491, 'DDL Event'),(17741, 'Management Event'),
   (17747, 'Security Event'),(17749, 'User Event'),(17985, 'CLR Aggregate Function'),(17993, 'Inline Table-valued SQL Function'),
   (18000, 'Partition Function'),(18002, 'Replication Filter Procedure'),(18004, 'Table-valued SQL Function'),(18259, 'Server Role'),
   (18263, 'Microsoft Windows Group'),(19265, 'Asymmetric Key'),(19277, 'Master Key'),(19280, 'Primary Key'),(19283, 'ObfusKey'),
   (19521, 'Asymmetric Key Login'),(19523, 'Certificate Login'),(19538, 'Role'),(19539, 'SQL Login'),(19543, 'Windows Login'),
   (20034, 'Remote Service Binding'),(20036, 'Event Notification on Database'),(20037, 'Event Notification'),(20038, 'Scalar SQL Function'),
   (20047, 'Event Notification on Object'),(20051, 'Synonym'),(20549, 'End Point'),(20801, 'Adhoc Queries which may be cached'),
   (20816, 'Prepared Queries which may be cached'),(20819, 'Service Broker Service Queue'),(20821, 'Unique Constraint'),
   (21057, 'Application Role'),(21059, 'Certificate'),(21075, 'Server'),(21076, 'Transact-SQL Trigger'),(21313, 'Assembly'),
   (21318, 'CLR Scalar Function'),(21321, 'Inline scalar SQL Function'),(21328, 'Partition Scheme'),(21333, 'User'),
   (21571, 'Service Broker Service Contract'),(21572, 'Trigger on Database'),(21574, 'CLR Table-valued Function'),
   (21577, 'Internal Table (For example, XML Node Table, Queue Table.)'),(21581, 'Service Broker Message Type'),(21586, 'Service Broker Route'),
   (21587, 'Statistics'),(21825, 'User'),(21827, 'User'),(21831, 'User'),(21843, 'User'),(21847, 'User'),(22099, 'Service Broker Service'),
   (22601, 'Index'),(22604, 'Certificate Login'),(22611, 'XMLSCHEMA'),(22868,  'Type (e.g. Table Type)'))f(objectTypeid, ObjType)
   ON dt.objectType=objecttypeid
   WHERE StartTime BETWEEN @start AND @finish
    AND databasename NOT IN ('tempdb', 'MASTER')
      AND EventSubClass=0
      AND category_id=5

  )

 GO
 /*
Here, we show our suspect error messages with all the extra detail we need in order to see what is going on.
*/
IF Object_Id('dbo.SeeSuspectErrors') IS NOT NULL
   DROP function dbo.SeeSuspectErrors
GO
CREATE FUNCTION dbo.SeeSuspectErrors
/**
Summary: >
  This function gives you a list of database errors that you would see in 
  development but not so usually in a properly-tested production system. 
  They are typical of the sort of errors that intruders make deliberately 
  to explore and understand the databaase they are attcking. It gives a narrative
  of what happened between the two dates, taken from the ring buffer
Author: Phil Factor
Date: 04/10/2018
Examples: >

  Select * from SeeSuspectErrors( @TheRingBuffer, '2018/11/01',getdate())

Returns: >
        **/
  (
  @TargetDataSuspiciousErrors XML,--the ring buffer for the session
  @Start DATETIME2,--the start of the period
  @finish DATETIME2--the end of the period
  )
RETURNS TABLE
 --WITH ENCRYPTION|SCHEMABINDING, ..
AS
RETURN
  (
SELECT 

  datetime_local, UserName+' had error "'+Message+'" ('+Convert(VARCHAR(10),[Error_Number])
  +') with query "'+SQL_Text+'" using the "'+ client_app_name+'" application' AS "Action"
   from
  (SELECT 
  CONVERT(datetime2,
          SwitchOffset(CONVERT(datetimeoffset,xed.event_data.value('(@timestamp)[1]', 'datetime2')),
          DateName(TzOffset, SYSDATETIMEOFFSET()))) AS datetime_local,
  xed.event_data.value('(data[@name="error_number"]/value)[1]', 'int') AS [Error_Number],
  xed.event_data.value('(data[@name="severity"]/value)[1]', 'int') AS Severity,
  xed.event_data.value('(data[@name="message"]/value)[1]', 'varchar(255)') AS [Message],
  xed.event_data.value('(action[@name="username"]/value)[1]', 'varchar(255)') AS UserName,
  xed.event_data.value('(action[@name="nt_username"]/value)[1]', 'varchar(255)') AS NT_Username,
  xed.event_data.value('(action[@name="sql_text"]/value)[1]', 'nvarchar(max)') AS SQL_Text,
  xed.event_data.value('(action[@name="database_name"]/value)[1]', 'varchar(255)') AS [Database_Name],
  xed.event_data.value('(action[@name="client_connection_id"]/value)[1]', 'varchar(255)') AS client_conn,
  xed.event_data.value('(action[@name="client_app_name"]/value)[1]', 'varchar(255)') AS client_app_name
     FROM @TargetDataSuspiciousErrors.nodes('//RingBufferTarget/event') AS xed (event_data)
  WHERE 
    Convert(DATETIME2,
              SwitchOffset(
              Convert(DATETIMEOFFSET, xed.event_data.value('(@timestamp)[1]', 'datetime2')),
              DateName(TzOffset, SysDateTimeOffset())
                          )
              ) BETWEEN @start AND @finish
)f
)
GO 

IF Object_Id('dbo.SeeConfigChanges') IS NOT NULL
   DROP function dbo.SeeConfigChanges
GO

/*
Finally, we pick up the config changes
*/

CREATE FUNCTION dbo.SeeConfigChanges
/**
Summary: >
  This function gives you a list of configuration changes that you might see in 
  development but not so usually in a properly-tied-down production system. 
  These configuration changes are done by attackers to allow them to send
  their payload off remotely.
  It gives a narrative of what happened between the two dates, taken from the 
  ring buffer
Author: Phil Factor
Date: 04/10/2018
Examples: >

  Select * from SeeConfigChanges( @TheRingBuffer,'2018/11/01',getdate())

Returns: >
        **/
  (
  @TargetDataChangesInConfigItems XML,--the ring buffer for the session
  @Start DATETIME2,--the start of the period
  @finish DATETIME2--the end of the period
  )
RETURNS TABLE
 --WITH ENCRYPTION|SCHEMABINDING, ..
AS
RETURN
  (
SELECT 
  datetime_local, 
  UserName+' changed '
  +Replace(Replace(Message,' Run the RECONFIGURE statement to install.',''),'changed','')
  +' ('+Convert(VARCHAR(10),[Error_Number])+') with query "'+SQL_Text+'" using the "'
  + client_app_name+'" application' AS [action]
   from
  (SELECT 
  CONVERT(datetime2,
          SwitchOffset(CONVERT(datetimeoffset,xed.event_data.value('(@timestamp)[1]', 'datetime2')),
          DateName(TzOffset, SYSDATETIMEOFFSET()))) AS datetime_local,
  xed.event_data.value('(data[@name="error_number"]/value)[1]', 'int') AS [Error_Number],
  xed.event_data.value('(data[@name="severity"]/value)[1]', 'int') AS Severity,
  xed.event_data.value('(data[@name="message"]/value)[1]', 'varchar(255)') AS [Message],
  xed.event_data.value('(action[@name="username"]/value)[1]', 'varchar(255)') AS UserName,
  xed.event_data.value('(action[@name="nt_username"]/value)[1]', 'varchar(255)') AS NT_Username,
  xed.event_data.value('(action[@name="sql_text"]/value)[1]', 'nvarchar(max)') AS SQL_Text,
  xed.event_data.value('(action[@name="database_name"]/value)[1]', 'varchar(255)') AS [Database_Name],
  xed.event_data.value('(action[@name="client_connection_id"]/value)[1]', 'varchar(255)') AS client_conn,
  xed.event_data.value('(action[@name="client_app_name"]/value)[1]', 'varchar(255)') AS client_app_name
    FROM @TargetDataChangesInConfigItems.nodes('//RingBufferTarget/event') AS xed (event_data)
    where Convert(DATETIME2,
              SwitchOffset(
              Convert(DATETIMEOFFSET, xed.event_data.value('(@timestamp)[1]', 'datetime2')),
              DateName(TzOffset, SysDateTimeOffset())
                          )
              ) BETWEEN @start AND @finish
)f
)
Go


The Batch

With all these in place, all we need to do is to decide on the time period we need to investigate and see what has been going on that might be suspicious. First, we pick up the ring buffers, and then, we execute the following code.

DECLARE @SuspiciousErrors XML =
              (
              SELECT TOP 1 Cast(xet.target_data AS XML) AS targetdata
                FROM sys.dm_xe_session_targets AS xet
                  INNER JOIN sys.dm_xe_sessions AS xes
                    ON xes.address = xet.event_session_address
                WHERE xes.name = 'MonitorSuspiciousErrors'
                  AND xet.target_name = 'ring_buffer'
              );
  DECLARE @ChangesInConfigItems XML =
              (
              SELECT TOP 1 Cast(xet.target_data AS XML) AS targetdata
                FROM sys.dm_xe_session_targets AS xet
                  INNER JOIN sys.dm_xe_sessions AS xes
                    ON xes.address = xet.event_session_address
                WHERE xes.name = 'ConfigurationItemsChanged'
                  AND xet.target_name = 'ring_buffer'
              );
  DECLARE @StartTime DATETIME, @endTime DATETIME
  SELECT @StartTime=DateAdd(DAY, -20, SysDateTime()),@EndTime=SysDateTime()
  SELECT @StartTime='2018-12-04',@EndTime='2018-12-04 10:40'
  SELECT datetime_local, action
     FROM dbo.SeeDatabaseObjectChanges(@StartTime, @EndTime) AS objectChanges
  UNION ALL
  SELECT datetime_local, action
    FROM dbo.SeeAccessControlChanges(@StartTime, @EndTime) AS PermissionChanges
  UNION ALL
  SELECT datetime_local, action 
  FROM dbo.SeeSuspectErrors(@SuspiciousErrors, @StartTime, @EndTime)
  UNION ALL
  SELECT datetime_local, action 
  FROM dbo.SeeConfigChanges(@ChangesInConfigItems,@StartTime, @EndTime)
   ORDER BY datetime_local;


This produces a narrative of events that one wouldn't hope to see in a production server. In our case, it looks as if someone has been up to no good.

The Narrative, Line by Line

I generated all these changes using the NodeJS interface and the PowerShell 'attack' script. Here, I'll simply walk through the narrative of the changes, as reported above, and explain briefly what they mean.

That first line says:

PhilFactor had error "Invalid column name 'silly'." (207) with query "select * from Sales.Customer where (customerId = silly)" using the "node-mssql" application

That node application is running under my user ID. Via that interface, I've simply submitted a value that isn't an integer. It's been accepted, SQL Server has tried to procure it, and it's failed with a 207 error, which was detected by our Suspicious Errors custom monitor. That is a vulnerability.

That next line...

PhilFactor had error "Incorrect syntax near ';'." (102) with query "select * from Sales.Customer where (customerId = 4;select silly)" using the "node-mssql" application

The user has injected some SQL. OK, they didn't do anything effective but didn't have to because they can see the error and will know it successfully injected SQL. It won't execute anything because there is a bracket in the filter condition for the first SQL Statement.

In the next line, we see that the attacker has added the bracket but has forgotten to comment out the original bracket, after the part of the code where he injected his attack code.

PhilFactor had error "Incorrect syntax near ')'." (102) with query "select * from Sales.Customer where (customerId = 4);select silly)" using the "node-mssql" application

In the next line, the attacker comments-out that terminating bracket and SQL Server now tries to execute the second query.

'PhilFactor had error "Invalid column name 'silly'." (207) with query "select * from Sales.Customer where (customerId = 4);select silly -)" using the "node-mssql"

Eek. The attacker now has a way in, and the damage done will depend on how easily they can broaden the scope of the attack. Cast your eye down further down the narrative and it looks bad.

The next line, for example, shows that he got in and added a new principal.

User PhilFactor added MSSecurityMtr to AdventureWorks2016 using the "node-mssql" application

Oh, my giddy aunt. Now on the next line:

User PhilFactor added MSSecurityMtr to object sysadmin using the "node-mssql" application.

They now have sysadmin access using a SQL Server login. Our custom monitor looks for changes to logins, roles, users, and permissions that it has detected.

Oo-er. Now things get even scarier...

PhilFactor changed Configuration option 'show advanced options' from 1 to 1. (15457) with query "select * from Sales.Customer where (customerId = 1 and 1=2); use AdventureWorks2016 execute sp_configure 'show advanced options',1; reconfigure with override; execute sp_configure 'Ad Hoc Distributed Queries',1; reconfigure with override; -)" using the "node-mssql" application

They enabled the Ad Hoc Distributed Queriesconfiguration setting. They are looking for ways of getting a large payload off the server! On the next line, they will achieve this!

PhilFactor changed Configuration option 'Ad Hoc Distributed Queries' from 0 to 1. (15457)

Now, they are opening up xp_cmdShell!

PhilFactor changed Configuration option 'show advanced options' from 1 to 1. (15457) with query "select * from Sales.Customer where (customerId = 1 and 1=2); use AdventureWorks2016 execute sp_configure 'show advanced options',1; reconfigure with override; execute sp_configure 'xp_cmdshell', 1; reconfigure with override; -)"

They are now going to use xp_cmdShell for something. FTP? Our custom monitor for SQL Server configuration changes picks up this suspicious activity.

Next, the attacker is trying to create a temporary table. Why? The name #directorylisting is a clue. They're probably trying to store and then retrieve all sorts of useful information you can get from executing OS commands and procedures.

PhilFactor had error "Incorrect syntax near ')'." (102) with query "select * from Sales.Customer where (customerId = 1 and 1=2); create table #directoryListing )" using the "node-mssql" application.

That was a silly mistake. They didn't need that error. They forgot the comment. They're trying to use a global temporary table, but it isn't working. Probably, they are the only connection at this point, and the connection is getting closed in the connection pool.

PhilFactor had error "Invalid object name '#directoryListing'." (208) with query "select * from Sales.Customer where (customerId = 1 and 1=2) union all SELECT NULL,NULL,NULL,NULL,TheLine,NULL,NULL FROM #directoryListing-)" using the "node-mssql" application

The next line tells us that they are now using an ordinary table to do the job.

User PhilFactor Created User Table AdventureWorks2016.MS_Temp267 using the "node-mssql" application

And the next line:

User PhilFactor Deleted User Table AdventureWorks2016.MS_Temp267 using the "node-mssql" application

This proves that it was successful. Heaven only knows what information is in it, but it will be in the log. Our Database Drift custom metric alerted us to these changes, at least.

Now, it looks as if our attacker has got everything he needs and is just tidying up.

PhilFactor changed Configuration option 'show advanced options' from 1 to 1. (15457) with query "select * from Sales.Customer where (customerId = 1 and 1=2); use AdventureWorks2016 execute sp_configure 'show advanced options',1; reconfigure with override; Execute xp_cmdshell 'del D:\DataBackups\*.* /Q' Execute xp_cmdshell 'rmdir D:\DataBackups' execute sp_configure 'Ad Hoc Distributed Queries',0; execute sp_configure 'xp_cmdshell', 0; reconfigure with override; -)" using the "node-mssql" application.

Yes, they have injected a script that has gone ahead and tried to remove the traces of the attack. Luckily, we have the Extended Events session data to tell us.

So, it all happened very quickly. In this case, it was a scripted attack, which you can emulate using the NodeJs interface and PowerShell script described here. Real probing attacks can go on for hours, and you can see the early stages of a rash of syntax errors that are inevitable in the early stages of any attack.

Once you see the escalation to sysadmin privileges and those configuration changes, you know that the attacker has full control. You must now assume that the attacker has all your tables dumped out and dispatched to an FTP site somewhere in eastern Europe. In this case, every table from Adventureworks was first saved to the filesystem and then dispatched to a remote FTP site as soon as the attacker had opened up xp_cmdShell.

Conclusion

For anyone of a nervous or sensitive disposition, I must emphasize that this is a simulation, and in another article, we'll go through the PowerShell script and application scenario that did this. You will see that the attacker very soon had access to what could, in reality, have been sensitive private information, and could have gone on to inflict a lot more damage. Even so, the entire AdventureWorks data was leaked.

The script we introduced in this article will give you clues to what has gone on, but it is only a pointer to what you need to drill down to. Its purpose is just to alert you to suspicious activity. It won't give you the full narrative. You will need to know quickly what cached plans are there that will reveal the queries that were executed. That is another story!

Topics:
security ,database ,sql ,sql monitor ,custom security alerts ,dba ,extended events

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}