Send an alert (to Discord, Slack, or any webhook) based on a condition, from a Linux or Windows host.
The code examples and scripts are copy & paste ready to use. Add your webhook, and change the variables or filepaths as needed.
This project is based on two of IppSec's videos documenting the same concept:
- Creating Webhooks in Slack for PowerShell
- Send Notifications to Slack via Scheduled Task Event Filter
Combined with the concepts taught in Antisyphon's Cyber Deception course:
This type of alerting provides huge value without the need to set up much infrastructure, as Discord and Slack are free to use, and operating systems have actionable logging avaialble we can leverage with minial configuration.
This repo details examples on how to impliment the alerting itself and expands upon the references above. IppSec's videos go into further detail on Sysmon logs, ways to use the Task Scheduler and deploying it as a GPO. Cyber Deception will show you the fundamentals of how to proactively detect suspicious activity. With all three combined, the goal is you'll hopefully have greater visibility into your environment with minimal overhead.
Specific steps and menus may change over time, this is the general process in both Slack and Discord for reference.
- Create a channel
- Set it to private (or read-only by admins)
- Slack: Create a Slack app > Enable Incoming Webhooks > Create an Incoming Webhook
- Discord: Go to Server Settings > Integrations > View Webhooks > New Webhook
This allows the Webhook to post / send messages to this channel.
Steps for Windows auditing and alerting.
Configurations required to generate log entries.
- By default logon / logoff events are logged to Security events
- We will also enable auditing for workstation unlock events to the Security event log
- GPO Path: Local Computer Policy > Windows Settings > Security Settings > Local Policies > Audit Policy > Audit logon events > âś… Success
Enable workstation unlock audit policy with:
auditpol.exe /set /Category:"Logon/Logoff" /success:enable
Obtain related event logs:
# Logon with explicit credentials
Get-WinEvent -FilterHashtable @{ Logname='Security'; StartTime=(Get-Date).AddDays(-1); Id='4648' }
# Workstation unlock events
Get-WinEvent -FilterHashtable @{ Logname='Security'; StartTime=(Get-Date).AddDays(-1); Id='4801' }
Event IDs:
- 4648: Logon with explicit credentials (most accurate for logins)
- 4801: Workstation unlock
- 4624: Account was logged in (noisy and harder to parse compared to 4648)
Steps and detailed screenshots are available in the link below. This section builds on that information.
Antisyphon Cyber Deception - File Auditing
- GPO Path: Local Computer Policy > Windows Settings > Security Settings > Local Policies > Audit Policy > Audit object access > âś… Success & âś… Failure
Enable audit object access policy with:
auditpol.exe /set /category:"Object Access" /success:enable /failure:enable
Use PowerShell to apply full auditing to a file:
- Matches the same settings described above, logging any type of access (aka FullControl) to the file by any user
- Syntax is essentially the same as
FileSystemAccessRule
's .NET method, only for auditing instead of accessing (Credit to ChatGPT for pointing this out) - FileSystemAuditRule
- Set-Acl: Grant Administrators Full Control of a File
# Enable-AuditingForFile.ps1
# Target file to audit
$path = "C:\Path\To\Secrets.txt"
# Get the ACL of the file as a variable to begin working with it
$NewAcl = Get-Acl -Path $path
# Set audit rule to log any kind of access to the file by any user
$identity = "Everyone"
$fileSystemRights = "FullControl"
$type = "Success,Failure"
$fileSystemAuditRuleArgumentList = $identity, $fileSystemRights, $type
$fileSystemAuditRule = New-Object System.Security.AccessControl.FileSystemAuditRule -ArgumentList $fileSystemAuditRuleArgumentList
# Add the audit rule to the file's ACL we're currently editing
$NewAcl.AddAuditRule($fileSystemAuditRule)
# Apply the updated ACL
Set-Acl -Path $path -AclObject $NewAcl
Event IDs:
- 4656: A handle to an object was requested (use this Event ID for monitoring files)
- 4663: An attempt was made to access an object
Additional references from Microsoft:
- Apply a basic audit policy on a file or folder
- Security Recommendations for Event Id 4663 (for additional considerations)
- Monitoring Recommendations for Audit Events
This section is built upon information taken from the following video:
IppSec - Send Notifications to Slack via Scheduled Task Event Filter
Overview:
- Use Sysmon, or any built in event log (e.g. Security, System)
- Create a Scheduled Task to perform an action on an event (or any trigger that works for your use case)
- Send a useful message to Slack or Discord (we'll use a PowerShell script to handle this)
Each Scheduled Task uses it's own PowerShell script to send event data to the webhook:
- File Audit Alert: Send-FileAlert.ps1
- Unlock Alert: Send-UnlockAlert.ps1
- Login Alert: Send-UnlockAlert.ps1
- Run the Task Scheduler as Administrator you want the task author to be an administrative user to restrict modification
- When debugging issues with a task, get task history logs with
Enable All Tasks History
- Tasks saved to
\
appear under the top folder,Task Scheduler Library
. This is the default location when creating tasks. - Oppsec note: task details are typically restricted based on Author. Restrict read-access to any sensitive scripts. This is detailed below.
- General: Change user to
SYSTEM
for tasks that require elevated prvileges (e.g. reading and parsing event logs) - General: Check "run with highest privilege" only if you need it (e.g. reading and parsing event logs)
- General: Check
Hidden
to have the task run hidden from the UI (silently in the background)
- Triggers: New > "On an event" > select
Custom
>New Event Filter...
- New Event Filter: We'll choose any existing log to generate the XML for us to use
- Next to
By log Event logs:
you could useWindows Logs
> then checkSecurity
- Enter an Event Id number (we'll use 4801) into the bar that says
<All Event IDs>
- Now at the top next to the
Filter
tab, chooseXML
- This is your raw query in XML, copy it to a file to work with it, it can be adapted manually or generated for other logs by following these steps
Tech Community: XML Event Filtering
- Generate an XML template from the Task Scheduler GUI (described just above)
- You can instead use
Export-ScheduleTask
to print the raw XML of a task to the console for review - Raw XML fields for event logs can be extracted from the EventViewer under the XML tab
This is an example for the XML Filter required to execute on an audited file (the file being Secrets.txt):
<QueryList>
<Query Id="0" Path="Security">
<Select Path="Security">
*[System[(EventID=4656)]] and
*[EventData[Data[@Name='ObjectName'] and (Data='C:\Path\To\Secrets.txt')]]
</Select>
</Query>
</QueryList>
Example XML Filter required to execute on Event ID 4801, workstation unlock:
<QueryList>
<Query Id="0" Path="Security">
<Select Path="Security">*[System[(EventID=4801)]]</Select>
</Query>
</QueryList>
Wildcards: There does not appear to be any way to do regex or wildcard matching with the XML. If there's a way to use wildcards to audit a specific set of files, for example anything with Z:\Share
and .xlsx
in the ObjectName, that could be implemented here, and a matching list or regex would be used instead of the full path to a single file in the Send-FileAlert.ps1 PowerShell script.
- It's best to use
powershell.exe
orcmd.exe
as the program to execute scripts (even though script paths work) - If you use a script path: You must embed arguments like
-nop -ep bypass -w hidden
into the script itself - If you use a shell as the program: You can specify those arguments here in the Scheduled Task Actions
- Actions: Program/script:
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
- Actions: Arguments:
-nop -ep bypass -w hidden C:\Tools\Scripts\Send-FileAlert.ps1
Avoid the Task Scheduler GUI and deploy these with PowerShell.
There are a few things to be aware of before starting when handling your scheduled tasks entirely in PowerShell.
- There's no way to import the raw XML of a Task using PowerShell like how you can
Export-ScheduledTask
- There's no built in way to specify certain triggers, or describe events, for the
New-ScheduledTaskTrigger
cmdlet
Let's compare the output of the Trigger properties of two separate Scheduled Tasks, one to Trigger on Workstation Unlock, and one to trigger on Event ID 4801 (which is the Event ID for a Workstation Unlock):
PS C:\Windows\system32> Get-ScheduledTask "Event Unlock Alert" | Select -ExpandProperty Triggers
Enabled : True
EndBoundary :
ExecutionTimeLimit :
Id :
Repetition : MSFT_TaskRepetitionPattern
StartBoundary :
Delay :
Subscription : <QueryList><Query Id="0" Path="Security"><Select Path="Security">*[System[(EventID=4801)]]</Select></Query></QueryList>
ValueQueries :
PSComputerName :
PS C:\Windows\system32> Get-ScheduledTask "Unlock Alert" | Select -ExpandProperty Triggers
Enabled : True
EndBoundary :
ExecutionTimeLimit :
Id :
Repetition : MSFT_TaskRepetitionPattern
StartBoundary :
Delay :
StateChange : 8
UserId :
PSComputerName :
You'll see Subscription
and StateChange
are unique and have data.
We want to write these in PowerShell as properties of the -Trigger
argument to Register-ScheduledTask
.
A way to visualize this is by running this on Scheduled Tasks we've already created in the GUI (if you followed along above) and using -ExpandProperty
on each argument required to register a Scheduled Task to see what each argument's property value is:
Get-ScheduledTask "Unlock Alert" | Select -ExpandProperty Principal
Get-ScheduledTask "Unlock Alert" | Select -ExpandProperty Actions
Get-ScheduledTask "Unlock Alert" | Select -ExpandProperty Triggers
Get-ScheduledTask "Unlock Alert" | Select -ExpandProperty Settings
So we need a way of passing raw XML to the trigger using PowerShell. This post: Stack Overflow - Register Scheduled Task (with PowerShell) to Trigger on an Event shows an example of how to work with the raw XML in PowerShell.
Credit goes to this answer for demonstrating how to pass XML as an argument to Scheduled Task triggers. That code block has been modified and used below.
Through trial and error we'll find that the Subscription
Trigger property works as expected, while the StateChange
property does not. For now we'll move ahead with Subscription
. So what does this look like? We need to use the following cmdlets from the Stack Overflow example:
- Get-CimClass
Get-CimClass -Namespace Root/Microsoft/Windows/TaskScheduler
- Enumerates all possible
CimClassNames
we can use to write custom filters for Scheduled Tasks $CIMTriggerClass = Get-CimClass -Namespace Root/Microsoft/Windows/TaskScheduler -ClassName MSFT_TaskEventTrigger
- Puts
MSFT_TaskEventTrigger
into a variable to work with - Most importantly this will print an error if the CimClassName does not exist or is incorrect
- New-CimInstance
$trigger = New-CimInstance -CimClass $CIMTriggerClass -ClientOnly
New-CimInstance
does not return errors if the CimClassName does not exist, it simply creates a new CimInstance-ClientOnly
creates the CIM instance in-memory locally for PowerShell operations alone- Now that we have a writable CimInstance to use with our Scheduled Task, we can write our XML filter to the
Subscription
property
PowerShell block to create a Scheduled Task that triggers on Event Id 4801 (Workstation Unlock):
# RegisterTask-UnlockAlert.ps1
# Name of the task
$taskname = "Unlock Alert"
# Execute our PowerShell script to send an alert to our webhook
$action = New-ScheduledTaskAction -Execute "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -Argument "-nop -ep bypass -w hidden C:\Tools\Scripts\Send-UnlockAlert.ps1"
# Create a list of triggers, and add logon trigger to start working with
$triggers = @()
$triggers += New-ScheduledTaskTrigger -AtLogOn
# Create a TaskEventTrigger using CIM, store it in a variable, and write our XML to the Subscription property of that variable
$CIMTriggerClass = Get-CimClass -ClassName MSFT_TaskEventTrigger -Namespace Root/Microsoft/Windows/TaskScheduler:MSFT_TaskEventTrigger
$trigger = New-CimInstance -CimClass $CIMTriggerClass -ClientOnly
$trigger.Subscription = @"
<QueryList>
<Query Id="0" Path="Security">
<Select Path="Security">*[System[(EventID=4801)]]</Select>
</Query>
</QueryList>
"@
# Set the Enabled property to $True, add this information to our trigger list
$trigger.Enabled = $True
$triggers += $trigger
# Run this hidden, as SYSTEM
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -RunLevel Highest
$settings = New-ScheduledTaskSettingsSet -Hidden
# Register the new task
Register-ScheduledTask "$taskname" -Action $action -Trigger $trigger -Principal $principal -Settings $settings
PowerShell Block to create a Scheduled Task that triggers on login (any account):
# RegisterTask-LoginAlert.ps1
$taskname = "Login Alert"
$action = New-ScheduledTaskAction -Execute "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -Argument "-nop -ep bypass -w hidden C:\Tools\Scripts\Send-LoginAlert.ps1"
$trigger = New-ScheduledTaskTrigger -AtLogon
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -RunLevel Highest
$settings = New-ScheduledTaskSettingsSet -Hidden
Register-ScheduledTask "$taskname" -Action $action -Trigger $trigger -Principal $principal
PowerShell Block to create a Scheduled Task that triggers on login (unique user, honey account):
- The only difference from the previous example is adding
-User "$honeyaccount"
toNew-ScheduledTaskTrigger
- This task will only run if $honeyaccount logs in
# RegisterTask-HoneyAccountAlert.ps1
$honeyaccount = "<account-name>"
$taskname = "Honey Account Alert"
$action = New-ScheduledTaskAction -Execute "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -Argument "-nop -ep bypass -w hidden C:\Tools\Scripts\Send-LoginAlert.ps1"
$trigger = New-ScheduledTaskTrigger -AtLogon -User "$honeyaccount"
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -RunLevel Highest
$settings = New-ScheduledTaskSettingsSet -Hidden
Register-ScheduledTask "$taskname" -Action $action -Trigger $trigger -Principal $principal
PowerShell block to create a Scheduled Task that triggers on Event Id 4656, handle to an object requested (honey files):
- Change the
$filepath
variable to point to your audited file (note the double and single quotes) - *NOTE: If there's a syntax
# RegisterTask-FileAlert.ps1
# Name of the task
$taskname = "File Audit Alert"
# File to monitor
$filepath = "Data='C:\Path\To\Secrets.txt'"
# Execute our PowerShell script to send an alert to our webhook
$action = New-ScheduledTaskAction -Execute "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" -Argument "-nop -ep bypass -w hidden C:\Tools\Scripts\Send-FileAlert.ps1"
# Create a list of triggers, and add logon trigger to start working with
$triggers = @()
$triggers += New-ScheduledTaskTrigger -AtLogOn
# Create a TaskEventTrigger using CIM, store it in a variable, and write our XML to the Subscription property of that variable
$CIMTriggerClass = Get-CimClass -ClassName MSFT_TaskEventTrigger -Namespace Root/Microsoft/Windows/TaskScheduler:MSFT_TaskEventTrigger
$trigger = New-CimInstance -CimClass $CIMTriggerClass -ClientOnly
$trigger.Subscription = @"
<QueryList>
<Query Id="0" Path="Security">
<Select Path="Security">
*[System[(EventID=4656)]] and
*[EventData[Data[@Name='ObjectName'] and ($filepath)]]
</Select>
</Query>
</QueryList>
"@
# Set the Enabled property to $True, add this information to our trigger list
$trigger.Enabled = $True
$triggers += $trigger
# Run this hidden, as SYSTEM
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -RunLevel Highest
$settings = New-ScheduledTaskSettingsSet -Hidden
# Register the new task
Register-ScheduledTask "$taskname" -Action $action -Trigger $trigger -Principal $principal -Settings $settings
There are a few notes to consider for the webhook scripts:
- Running the task as BUILTIN\Users, extract environment variables via simple PowerShell scripts requires the scripts to be readable by the user
- Running as SYSTEM can restrict the scripts to administrators only
- SYSTEM allows you to extract event log information
- Running as SYSTEM and extracting log data when a particular user triggers a task to run also prevents issues where scripts are executed as the wrong user when multiple sessions are active (or at least this was encounter while testing all of this).
Utlimately you want the task information and scripts to be owned by the administrators group and restrict regular users from accessing or reading them.
NOTE: This method is not as resistant to log tampering as embedding the variables directly into the Task XML data like in IppSec's video. However, even if logs are cleared you'll still receive the empty alert letting you know someting happened. Details are below this section.
IMPORTANT!
Change the script's permissions to prevent read/write/execute from non administrative users:
icacls.exe C:\Tools\Scripts\Send-FileAlert.ps1 /reset
takeown.exe /F C:\Tools\Scripts\Send-FileAlert.ps1 /A
icacls.exe C:\Tools\Scripts\Send-FileAlert.ps1 /inheritance:r
icacls.exe C:\Tools\Scripts\Send-FileAlert.ps1 /grant SYSTEM:"(F)"
icacls.exe C:\Tools\Scripts\Send-FileAlert.ps1 /grant BUILTIN\Administrators:"(F)"
This sets the ownership of the file to the Administrators group, and removes object inheritance, meaning folder permissions are not inherited (this effectively removes all access to the file until you grant specific access). Finally the SYSTEM account and the Administrators group are the only principals given access to the file. No other user or account will be able to modify, delete, or read this file.
This PowerShell script can be used with any scheduled task to record a username, timestamp, hostname, (really anything about the environment you want to put into a PowerShell variable) to your alerts channel. This is a base script used to show what you can do with PowerShell and webhooks.
# Send-GenericAlert.ps1
# The full URL to your webhook
$webhook = '<your-webhook-url>'
# The timestamp of the event
$timestamp = Get-Date -Format "yyyy.MM.dd HH:mm:ss"
# The body of the webhook POST content populated with environment variables
$body = ConvertTo-Json @{ content="=======[ Account Login Alert ]========`nTimestamp: $timestamp`nHostname: $env:COMPUTERNAME`nUser: $env:USERDOMAIN\$env:USERNAME"}
# The POST request to your webhook
Invoke-RestMethod "$webhook" -Method Post -Body "$body" -ContentType 'application/json'
- Uses
Get-WinEvent
to pull the most recent log matching our criteria (in this example a honey file) - Extract the most important fields as variables to send to the webhook
A quick explaination of what's happening so you can build your own queries:
- Here
$_.properties[6].value
is known to be theObjectName
which means the filename being audited. - If you wanted to obtain these property values for any event log, you can list all of them by using
[0..25]
,25
being an arbitrary number (most event logs don't have more than 25 fields, so by doing this you'll list them all) - Use this example query to see how this works:
Get-WinEvent -FilterHashtable @{ Logname='Security'; StartTime=(Get-Date).AddDays(-1); Id='4656' } | ForEach-Object { Out-String -InputObject $_.properties[0..25].value } | select -First 1 | fl
- Count and choose which fields you want to print or match with something like
$_.properties[1,6,15].value
(The first field is always0
)
The full script once all the pieces are together:
# Send-FileAlert.ps1
# The full URL to your webhook
$webhook=''
# Set the full path (backslashes escaped) of the file to be audited
$filepath = 'C:\\Path\\To\\Secrets.txt'
# Set a short start date to reduce log parsing time
$StartDate = (Get-Date).AddMinutes(-1)
# Get the event properties as variables
Get-WinEvent -FilterHashtable @{ Logname='Security'; StartTime=$StartDate; Id='4656' } | Where-Object { (Out-String -InputObject $_.properties[6].value) -imatch "$filepath" } | Select -First 1 | ForEach-Object {
$timecreated = Out-String -InputObject $_.TimeCreated;
$username = Out-String -InputObject $_.properties[1].value;
$domain = Out-String -InputObject $_.properties[2].value;
$file = Out-String -InputObject $_.properties[6].value;
$pid = Out-String -InputIObject $_.properties[14].value;
$process = Out-String -InputObject $_.properties[15].value;
}
# The body of the webhook POST content
$body = ConvertTo-Json @{ content="=======[ Honey File Alert ]========`nTimestamp: $timestamp`nHostname: $env:COMPUTERNAME`nDomain: $domain`nUser: $username`nFile: $file`nProcess: $process`nPID: $pid" }
# The POST request to your webhook
Invoke-RestMethod "$webhook" -Method Post -Body "$body" -ContentType 'application/json'
- Parses Security events for ID 4648
- Filters out the
winlogon.exe
process (this helps retrieve only user sessions) - Extracts all properties as variables
- Built upon Seatbelt's ExplicitLogonEventsCommand.cs
# Send-LoginAlert.ps1
# The full URL to your webhook
$webhook=''
# Set a short start date to reduce log parsing time
$StartDate = (Get-Date).AddMinutes(-1)
# Filter out the winlogon.exe process (this helps retrieve only user sessions)
$pattern = 'winlogon.exe'
# Get the event properties as variables
Get-WinEvent -FilterHashtable @{ Logname='Security'; StartTime=$StartDate; Id='4648' } | Where-Object { (Out-String -InputObject $_.properties[11].value) -inotmatch "$pattern" } | Select -First 1 | ForEach-Object {
# Variable names were kept the same as they are in Seatbelt's ExplicitLogonEventsCommand.cs
# Variable properties can be extracted from the Windows Event ID 4648 $_.Message object:
$creationTime = $_.TimeCreated
$subjectUserSid = $_.properties[0].value
$subjectUserName = $_.properties[1].value
$subjectDomainName = $_.properties[2].value
$subjectLogonId = $_.properties[3].value
$logonGuid = $_.properties[4].value
$targetUserName = $_.properties[5].value
$targetDomainName = $_.properties[6].value
$targetLogonGuid = $_.properties[7].value
$targetServerName = $_.properties[8].value
$targetServerInfo = $_.properties[9].value
$processId = $_.properties[10].value
$processName = $_.properties[11].value
$ipAddress = $_.properties[12].value
$ipPort = $_.properties[13].value
}
# The body of the webhook POST content
$body = ConvertTo-Json @{ content="=======[ Account Login Alert ]========`nTimestamp: $creationTime`nHostname: $targetServerName`nUser: $targetDomainName\$targetUserName`nIp: $ipAddress"}
# The POST request to your webhook
Invoke-RestMethod "$webhook" -Method Post -Body "$body" -ContentType 'application/json'
- Parses Security Events for ID 4801
- Extracts all properties as variables
# Send-UnlockAlert.ps1
# The full URL to your webhook
$webhook=''
# Set a short start date to reduce log parsing time
$StartDate = (Get-Date).AddMinutes(-1)
# Get the event properties as variables
Get-WinEvent -FilterHashtable @{ Logname='Security'; StartTime=$StartDate; Id='4801' } | Select -First 1 | ForEach-Object {
$creationTime = $_.TimeCreated
$subjectUserSid = $_.properties[0].value
$subjectUserName = $_.properties[1].value
$subjectDomainName = $_.properties[2].value
$subjectLogonId = $_.properties[3].value
$subjectSessionId = $_.properties[4].value
}
# The body of the webhook POST content
$body = ConvertTo-Json @{ content="=======[ Device Unlock Alert ]========`nTimestamp: $creationTime`nHostname: $env:COMPUTERNAME`nUser: $subjectDomainName\$subjectUserName`nSessionId: $SubjectSessionId"}
# The POST request to your webhook
Invoke-RestMethod "$webhook" -Method Post -Body "$body" -ContentType 'application/json'
IMPORTANT! This is a destructive exercise, perform this in a sandbox environment!
The one downside to handling the webhook data separately from the Scheduled Task (compared to IppSec's video where event information is embedded into the Task XML) is that logs need accessed after the task executes, to obtain the alert data. This creates a situation where an attacker with awareness of your monitoring is able to modify the logs before the data can be sent.
However, the empty alert itself is still sent with a header informing you of what task was executed.
Each PowerShell script in this repo sends data with a header to delineate it from the previous alert, and to detail what it's alerting on (e.g. "Login Alert"). Even if the log is cleared, the Task still triggers and the script is still executed. Here's the result of a file audit alert where the logs were cleared:
=======[ Honey File Alert ]========
Timestamp:
Hostname: HOSTNAME
Domain:
User:
File:
Process:
PID: 12345
You should validate this yourself in a sandbox. The following script was used to test clearing the logs:
# Access the honey file
$filepath = "C:\Path\To\Secrets.xlsx"
Get-Content -Path $filepath
# Run this in a loop for 5 seconds, clearing events immediately doesn't erase the newest log
$EndTime = (Get-Date).AddSeconds(5)
# Clear the audit log (Security)
while ((Get-Date) -lt $EndTime) {
Clear-EventLog -LogName 'Security'
}
What if the attacker removes the PowerShell alerting script AND clears the logs at the same time?
Unfortunately once an adversary successfully has SYSTEM and hasn't been kicked out by AV / EDR, there's nothing limiting what they can do. The monitoring and alerting techniques described in this repo should be a starting point. Ideally you'll have alerts configured that will catch behavior leading to system compromise as it happens. Combine this with things like Attack Surface Reduction rules for defense in depth!
Steps for Linux (and Unix) auditing and alerting.
The scripts as of now were built to run on Ubuntu Linux (tested on 20.04 and 22.04).
They use a webhook to POST information via curl
to an alert channel.
/var/log/auth.log
is used for all login and sudo alerts/var/log/audit/audit.log
is used for specific monitoring and would be based on your auditd rule file(s)
alert.sh
functions as the log monitoring and POST mechanism, tailing any file you require (it's easy to add functions for different logs based on your requirements) and sending lines that match your rule(s) to your webhook.
alert-service.sh
configures this as a systemd service to restart automatically or if it ever stops.
While only root can read the script containing the webhook URL, an unfortunate side effect of using a webhook via curl
is that anyone on the system can pull the URL from the process list with ps aux
if timed correctly, or by using a process monitoring tool like pspy
. This will need addressed in a future revision. For now, having the capability of receiving a text message if your server is logged into outweighs the risk of having a private webhook stolen.