Adversaries may inject malicious code into source files during the build process to compromise the integrity of the software. SOC teams should proactively hunt for this behavior in Azure Sentinel to detect potential supply chain attacks and prevent the deployment of compromised code.
KQL Query
// How far back to look for events from
let timeframe = 1d;
// How close together build events and file modifications should occur to alert (make this smaller to reduce FPs)
let time_window = 5m;
// Edit this to include build processes used
let build_processes = dynamic(["MSBuild.exe", "dotnet.exe", "VBCSCompiler.exe"]);
// Include any processes that you want to allow to edit files during/around the build process
let allow_list = dynamic([""]);
(union isfuzzy=true
(SecurityEvent
| where TimeGenerated > ago(timeframe)
// Look for build process starts
| where EventID == 4688
| where Process has_any (build_processes)
| summarize by BuildParentProcess=ParentProcessName, BuildProcess=Process, BuildAccount = Account, Computer, BuildCommand=CommandLine, timekey= bin(TimeGenerated, time_window), BuildProcessTime=TimeGenerated
| join kind=inner(
SecurityEvent
| where TimeGenerated > ago(timeframe)
// Look for file modifications to code file
| where EventID == 4663
| where Process !in (allow_list)
// Look for code files, edit this to include file extensions used in build.
| where ObjectName endswith ".cs" or ObjectName endswith ".cpp"
// 0x6 and 0x4 for file append, 0x100 for file replacements
| where AccessMask == "0x6" or AccessMask == "0x4" or AccessMask == "0X100"
| summarize by FileEditParentProcess=ParentProcessName, FileEditAccount = Account, Computer, FileEdited=ObjectName, FileEditProcess=ProcessName, timekey= bin(TimeGenerated, time_window), FileEditTime=TimeGenerated)
// join where build processes and file modifications seen at same time on same host
on timekey, Computer
// Limit to only where the file edit happens after the build process starts
| where BuildProcessTime <= FileEditTime
| summarize make_set(FileEdited), make_set(FileEditProcess), make_set(FileEditAccount) by timekey, Computer, BuildParentProcess, BuildProcess
),
(WindowsEvent
| where TimeGenerated > ago(timeframe)
// Look for build process starts
| where EventID == 4688 and EventData has_any (build_processes)
| extend NewProcessName = tostring(EventData.NewProcessName)
| extend Process=tostring(split(NewProcessName, '\\')[-1])
| where Process has_any (build_processes)
| extend ParentProcessName = tostring(EventData.ParentProcessName)
| extend Account = strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
| extend CommandLine = tostring(EventData.CommandLine)
| summarize by BuildParentProcess=ParentProcessName, BuildProcess=Process, BuildAccount = Account, Computer, BuildCommand=CommandLine, timekey= bin(TimeGenerated, time_window), BuildProcessTime=TimeGenerated
| join kind=inner(
WindowsEvent
| where TimeGenerated > ago(timeframe)
// Look for file modifications to code file
| where EventID == 4663 and EventData has_any ("0x6", "0x4", "0X100") and EventData has_any (".cs", ".cpp")
| extend NewProcessName = tostring(EventData.NewProcessName)
| extend Process=tostring(split(NewProcessName, '\\')[-1])
| where Process !in (allow_list)
// Look for code files, edit this to include file extensions used in build.
| extend ObjectName = tostring(EventData.ObjectName)
| where ObjectName endswith ".cs" or ObjectName endswith ".cpp"
// 0x6 and 0x4 for file append, 0x100 for file replacements
| extend AccessMask = tostring(EventData.AccessMask)
| where AccessMask == "0x6" or AccessMask == "0x4" or AccessMask == "0X100"
| extend ParentProcessName = tostring(EventData.ParentProcessName)
| extend Account = strcat(tostring(EventData.SubjectDomainName),"\\", tostring(EventData.SubjectUserName))
| extend ProcessName = tostring(EventData.ProcessName)
| summarize by FileEditParentProcess=ParentProcessName, FileEditAccount = Account, Computer, FileEdited=ObjectName, FileEditProcess=ProcessName, timekey= bin(TimeGenerated, time_window), FileEditTime=TimeGenerated)
// join where build processes and file modifications seen at same time on same host
on timekey, Computer
// Limit to only where the file edit happens after the build process starts
| where BuildProcessTime <= FileEditTime
| summarize make_set(FileEdited), make_set(FileEditProcess), make_set(FileEditAccount) by timekey, Computer, BuildParentProcess, BuildProcess
))
| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
| project-away DomainIndex
id: 5ef06767-b37c-4818-b035-47de950d0046
name: Potential Build Process Compromise
description: |
'The query looks for source code files being modified immediately after a build process is started. The purpose of this is to look for malicious code injection during the build process.
More details: https://techcommunity.microsoft.com/t5/azure-sentinel/monitoring-the-software-supply-chain-with-azure-sentinel/ba-p/2176463'
severity: Medium
requiredDataConnectors:
- connectorId: SecurityEvents
dataTypes:
- SecurityEvent
- connectorId: WindowsSecurityEvents
dataTypes:
- SecurityEvent
- connectorId: WindowsSecurityEvents
dataTypes:
- SecurityEvents
- connectorId: WindowsForwardedEvents
dataTypes:
- WindowsEvent
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- Persistence
relevantTechniques:
- T1554
tags:
- Solorigate
- NOBELIUM
query: |
// How far back to look for events from
let timeframe = 1d;
// How close together build events and file modifications should occur to alert (make this smaller to reduce FPs)
let time_window = 5m;
// Edit this to include build processes used
let build_processes = dynamic(["MSBuild.exe", "dotnet.exe", "VBCSCompiler.exe"]);
// Include any processes that you want to allow to edit files during/around the build process
let allow_list = dynamic([""]);
(union isfuzzy=true
(SecurityEvent
| where TimeGenerated > ago(timeframe)
// Look for build process starts
| where EventID == 4688
| where Process has_any (build_processes)
| summarize by BuildParentProcess=ParentProcessName, BuildProcess=Process, BuildAccount = Account, Computer, BuildCommand=CommandLine, timekey= bin(TimeGenerated, time_window), BuildProcessTime=TimeGenerated
| join kind=inner(
SecurityEvent
| where TimeGenerated > ago(timeframe)
// Look for file modifications to code file
| where EventID == 4663
| where Process !in (allow_list)
// Look for code files, edit this to include file extensions used in build.
| where ObjectName endswith ".cs" or ObjectName endswith ".cpp"
// 0x6 and 0x4 for file append, 0x100 for file replacements
| where AccessMask == "0x6" or AccessMask == "0x4" or AccessMask == "0X100"
| summarize by FileEditParentProcess=ParentProcessName, FileEditAccount = Account, Computer, FileEdited=ObjectName, FileEditProcess=ProcessName, timekey= bin(TimeGenerated, time_window), FileEditTime=TimeGenerated)
// join where build processes and file modifications seen at same time on same host
on timekey, Computer
// Limit to only where the file edit happens after the build process starts
| where BuildProcessTime <= FileEditTime
| summarize make_set(FileEdited), make_set(FileEditProcess), make_set(FileEditAccount) by timekey, Computer, BuildParentProcess, BuildProcess
),
(WindowsEvent
| where TimeGenerated > ago(timeframe)
// Look for build process starts
| where EventID == 4688 and EventData
| Sentinel Table | Notes |
|---|---|
SecurityEvent | Ensure this data connector is enabled |
WindowsEvent | Ensure this data connector is enabled |
Scenario: Scheduled CI/CD Pipeline Job Execution
Description: A legitimate scheduled job in a CI/CD pipeline (e.g., Jenkins, GitHub Actions) modifies source code files as part of a deployment or artifact generation process.
Filter/Exclusion: process.name:*jenkins* OR process.name:*github-actions* OR process.name:*circleci*
Scenario: Admin Task for Code Refactoring
Description: A system administrator or developer manually refactors code, which involves modifying source files shortly after a build is initiated.
Filter/Exclusion: user.name:*admin* OR user.name:*dev* OR process.name:*code-coverage* OR process.name:*refactor*
Scenario: Artifact Generation or Packaging
Description: A build tool (e.g., Maven, Gradle, MSBuild) generates artifacts or packages code, which may involve temporary file modifications.
Filter/Exclusion: process.name:*maven* OR process.name:*gradle* OR process.name:*msbuild* OR process.name:*npm*
Scenario: Code Coverage Tool Integration
Description: A code coverage tool (e.g., JaCoCo, OpenCover) modifies source files to inject instrumentation code during testing or build phases.
Filter/Exclusion: process.name:*jacoco* OR process.name:*opencover* OR process.name:*coverage*
Scenario: Automated Dependency Update Job
Description: An automated job updates dependencies (e.g., using npm update, pip install, or mvn dependency:resolve) which may involve temporary file changes.
Filter/Exclusion: process.name:*npm* OR process.name:*pip* OR `process.name:*mv