An adversary may be attempting to inject malicious code into a web server via IIS logs to gain initial access through a drive-by compromise. SOC teams should proactively hunt for this behavior in Azure Sentinel to identify and mitigate potential code injection attacks that could lead to broader network compromise.
KQL Query
// set cIP and csMethod count limit to indicate potentially noisy events, this will be listed at the top of the results
// for any returns that are gt or equal to the default of 50
let cIP_MethodCountLimit = 50;
// Exclude common csMethods, add/modify this list as needed for your environment
let csMethodExclude = dynamic(['GET', 'DEBUG', 'DELETE', 'LOCK', 'MKCOL', 'MOVE', 'PATCH', 'POST', 'PROPPATCH',
'PUT', 'SEARCH', 'TRACE', 'TRACK', 'UNLOCK', 'OPTIONS', 'HEAD', 'RPC_IN_DATA', 'RPC_OUT_DATA', 'PROPFIND','BITS_POST','CCM_POST']);
// Include in the list expected IPs where remote methods such as vuln scanning may be expected for your environment
let expectedIPs = dynamic(['X.X.X.X', 'Y.Y.Y.Y']);
let codeInjectionAttempts = W3CIISLog
// Exclude private ip ranges from cIP list
| where ipv4_is_private(cIP) == false
| where cIP != "::1"
| where cIP !in (expectedIPs)
| project TimeGenerated, cIP, csUserName, csMethod, csCookie, csHost, sIP, scStatus, csUriStem, csUriQuery, csUserAgent, csReferer
// Throwing entire record into a single string column for attributable string matching
| extend pak = tostring(pack_all())
// Adding "arr" column containing indicators of matched suspicious strings
| extend arr = dynamic([])
| extend arr = iff(pak contains '<script' , array_concat(arr, pack_array('STRING MATCH : script')), arr)
| extend arr = iff(pak contains '%3Cscript' , array_concat(arr, pack_array('STRING MATCH : script')), arr)
| extend arr = iff(pak contains '%73%63%72%69%70%74' , array_concat(arr, pack_array('STRING MATCH : encoded script')), arr)
| extend arr = iff(pak contains '<img' , array_concat(arr, pack_array('STRING MATCH : img')), arr)
| extend arr = iff(pak contains '%3Cimg' , array_concat(arr, pack_array('STRING MATCH : img')), arr)
| extend arr = iff(pak contains 'passwd' , array_concat(arr, pack_array('STRING MATCH : passwd')), arr)
| extend arr = iff(csUserAgent contains 'nmap' , array_concat(arr, pack_array('STRING MATCH : nmap')), arr)
| extend arr = iff(csUserAgent contains 'nessus' , array_concat(arr, pack_array('STRING MATCH : nessus')), arr)
| extend arr = iff(csUserAgent contains 'qualys' , array_concat(arr, pack_array('STRING MATCH : qualys')), arr)
| extend arr = iff(csMethod !in (csMethodExclude), array_concat(arr, pack_array('INVALID HTTP METHOD')), arr)
| extend arr = iff(csUriStem == '/current_config/passwd' , array_concat(arr, pack_array('STRING MATCH : dahua scan url' )), arr)
| extend arr = iff(csUriQuery contains '..' and csUriQuery !endswith '...', array_concat(arr, pack_array('BACKTRACK ATTEMPT IN QUERY')), arr)
| extend arr = iff(csUriQuery contains 'http://www.webscantest.com' , array_concat(arr, pack_array('STRING MATCH : webscantest')), arr)
| extend arr = iff(csUriQuery contains 'http://appspidered.rapid7.com' , array_concat(arr, pack_array('STRING MATCH : appspider')), arr)
| where array_length(arr) > 0
| project-away pak;
let cIP_MethodHighCount = codeInjectionAttempts
| summarize StartTime = max(TimeGenerated), EndTime = min(TimeGenerated), cIP_MethodCount = count()
by cIP, tostring(arr), cIP_MethodCountType = "High Count of cIP and csMethod, this may be noise"
| where cIP_MethodCount >= cIP_MethodCountLimit;
let codeInjectAtt =
codeInjectionAttempts
| summarize StartTime = max(TimeGenerated), EndTime = min(TimeGenerated), cIP_MethodCount = count()
by cIP, cIP_MethodCountType = "Count of repeated entries, this is to reduce rowsets returned", csMethod,
tostring(arr), csHost, scStatus, sIP, csUriStem, csUriQuery, csUserName, csUserAgent, csCookie, csReferer;
// union the events and sort by cIP_MethodCount to identify potentially noisy entries. Additionally, cIP_MethodCountType
// indicates whether it is a high count or simply a count of repeated entries
(union isfuzzy=true
cIP_MethodHighCount, codeInjectAtt
| sort by cIP_MethodCount desc, cIP desc, StartTime desc)
| extend timestamp = StartTime, IPCustomEntity = cIP, HostCustomEntity = csHost, AccountCustomEntity = csUserName, URLCustomEntity = csUriQuery
id: 96977c95-74b4-4cc2-b1a7-6a3ab17bd3f9
name: Potential IIS code injection attempt
description: |
'Potential code injection into web server roles via IIS logs scan. Represents attempt to gain initial access using drive-by compromise technique. Detection flags events for review and filtering of authorized activity.'
description_detailed: |
'Potential code injection into web server roles via scan of IIS logs. This represents an attempt to gain initial access to a system using a
drive-by compromise technique. This sort of attack happens routinely as part of security scans, of both authorized and malicious types.
The initial goal of this detection is to flag these events when they occur and give an opportunity to review the data and filter out authorized activity.'
requiredDataConnectors:
- connectorId: AzureMonitor(IIS)
dataTypes:
- W3CIISLog
tactics:
- InitialAccess
relevantTechniques:
- T1189
- T1190
query: |
// set cIP and csMethod count limit to indicate potentially noisy events, this will be listed at the top of the results
// for any returns that are gt or equal to the default of 50
let cIP_MethodCountLimit = 50;
// Exclude common csMethods, add/modify this list as needed for your environment
let csMethodExclude = dynamic(['GET', 'DEBUG', 'DELETE', 'LOCK', 'MKCOL', 'MOVE', 'PATCH', 'POST', 'PROPPATCH',
'PUT', 'SEARCH', 'TRACE', 'TRACK', 'UNLOCK', 'OPTIONS', 'HEAD', 'RPC_IN_DATA', 'RPC_OUT_DATA', 'PROPFIND','BITS_POST','CCM_POST']);
// Include in the list expected IPs where remote methods such as vuln scanning may be expected for your environment
let expectedIPs = dynamic(['X.X.X.X', 'Y.Y.Y.Y']);
let codeInjectionAttempts = W3CIISLog
// Exclude private ip ranges from cIP list
| where ipv4_is_private(cIP) == false
| where cIP != "::1"
| where cIP !in (expectedIPs)
| project TimeGenerated, cIP, csUserName, csMethod, csCookie, csHost, sIP, scStatus, csUriStem, csUriQuery, csUserAgent, csReferer
// Throwing entire record into a single string column for attributable string matching
| extend pak = tostring(pack_all())
// Adding "arr" column containing indicators of matched suspicious strings
| extend arr = dynamic([])
| extend arr = iff(pak contains '<script' , array_concat(arr, pack_array('STRING MATCH : script')), arr)
| extend arr = iff(pak contains '%3Cscript' , array_concat(arr, pack_array('STRING MATCH : script')), arr)
| extend arr = iff(pak contains '%73%63%72%69%70%74' , array_concat(arr, pack_array('STRING MATCH : encoded script')), arr)
| extend arr = iff(pak contains '<img' , array_concat(arr, pack_array('STRING MATCH : img')), arr)
| extend arr = iff(pak contains '%3Cimg' , array_concat(arr, pack_array('STRING MATCH : img')), arr)
| extend arr = iff(pak contains 'passwd' , array_concat(arr, pack_array('STRING MATCH : passwd')), arr)
| extend arr = iff(csUserAgent contains 'nmap' , array_concat(arr, pack_array('STRING MATCH : nmap')), arr)
| extend arr
| Sentinel Table | Notes |
|---|---|
W3CIISLog | Ensure this data connector is enabled |
Scenario: Legitimate scheduled job execution via IIS
Description: A system administrator schedules a PowerShell script using Task Scheduler to run via IIS, which includes a parameter that resembles code injection syntax (e.g., Invoke-Command -ScriptBlock { ... }).
Filter/Exclusion: Check for EventID=10000 (Task Scheduler event) and filter by TaskName matching known administrative tasks (e.g., DailySystemCheck), or use a regex to exclude Invoke-Command when the script is known to be legitimate.
Scenario: Admin using IIS Manager to configure application settings
Description: An administrator uses IIS Manager to configure a web application, which may involve entering a script-like string in a configuration field (e.g., Set-ItemProperty -Path "IIS:\Sites\DefaultWebSite\" -Name "applicationPool" -Value "MyAppPool").
Filter/Exclusion: Filter by EventID=1 (IIS log entry) and check for cs-method=GET or cs-uri-stem containing known admin tools (e.g., iismanager.exe, inetmgr.exe).
Scenario: Automated patching tool using IIS to deploy updates
Description: A patching tool like Microsoft System Center Configuration Manager (SCCM) or Ansible deploys updates to IIS via a script that includes code injection-like syntax (e.g., Invoke-WebRequest -Uri "http://patchserver/update.dll").
Filter/Exclusion: Use a filter for EventID=1 and check for cs-uri-stem containing known patching tool URLs or cs-method=POST with known deployment payloads.
Scenario: Internal tool for log analysis using IIS logs
Description: A security analyst uses a