← Back to SOC feed Coverage →

Potential IIS code injection attempt

kql MEDIUM Azure-Sentinel
T1189T1190
W3CIISLog
huntingmicrosoftofficial
This rule was pulled from an open-source repository and enriched with AI. Validate in a test environment before deploying to production.
View original rule at Azure-Sentinel →
Retrieved: 2026-06-04T23:00:00Z · Confidence: medium

Hunt Hypothesis

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

Analytic Rule Definition

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

Required Data Sources

Sentinel TableNotes
W3CIISLogEnsure this data connector is enabled

MITRE ATT&CK Context

References

False Positive Guidance

Original source: https://github.com/Azure/Azure-Sentinel/blob/main/Hunting Queries/W3CIISLog/Potential_IIS_CodeInject.yaml