High count of failed logons by a user
Id | 884c4957-70ea-4f57-80b9-1bca3890315b |
Rulename | High count of failed logons by a user |
Description | Identifies when 100 or more failed attempts by a given user in 10 minutes occur on the IIS Server. This could be indicative of attempted brute force based on known account information. This could also simply indicate a misconfigured service or device. References: IIS status code mapping - https://support.microsoft.com/help/943891/the-http-status-code-in-iis-7-0-iis-7-5-and-iis-8-0 Win32 Status code mapping - https://msdn.microsoft.com/library/cc231199.aspx |
Severity | Medium |
Tactics | CredentialAccess |
Techniques | T1110 |
Required data connectors | AzureMonitor(IIS) |
Kind | Scheduled |
Query frequency | 1d |
Query period | 1d |
Trigger threshold | 0 |
Trigger operator | gt |
Source Uri | https://github.com/Azure/Azure-Sentinel/blob/master/Detections/W3CIISLog/HighFailedLogonCountByUser.yaml |
Version | 1.0.3 |
Arm template | 884c4957-70ea-4f57-80b9-1bca3890315b.json |
let timeBin = 10m;
let failedThreshold = 100;
W3CIISLog
| where scStatus in ("401","403")
| where csUserName != "-"
// Handling Exchange specific items in IIS logs to remove the unique log identifier in the URI
| extend csUriQuery = iff(csUriQuery startswith "MailboxId=", tostring(split(csUriQuery, "&")[0]) , csUriQuery )
| extend csUriQuery = iff(csUriQuery startswith "X-ARR-CACHE-HIT=", strcat(tostring(split(csUriQuery, "&")[0]),tostring(split(csUriQuery, "&")[1])) , csUriQuery )
| extend scStatusFull = strcat(scStatus, ".",scSubStatus)
// Map common IIS codes
| extend scStatusFull_Friendly = case(
scStatusFull == "401.0", "Access denied.",
scStatusFull == "401.1", "Logon failed.",
scStatusFull == "401.2", "Logon failed due to server configuration.",
scStatusFull == "401.3", "Unauthorized due to ACL on resource.",
scStatusFull == "401.4", "Authorization failed by filter.",
scStatusFull == "401.5", "Authorization failed by ISAPI/CGI application.",
scStatusFull == "403.0", "Forbidden.",
scStatusFull == "403.4", "SSL required.",
"See - https://support.microsoft.com/help/943891/the-http-status-code-in-iis-7-0-iis-7-5-and-iis-8-0")
// Mapping to Hex so can be mapped using website in comments above
| extend scWin32Status_Hex = tohex(tolong(scWin32Status))
// Map common win32 codes
| extend scWin32Status_Friendly = case(
scWin32Status_Hex =~ "775", "The referenced account is currently locked out and cannot be logged on to.",
scWin32Status_Hex =~ "52e", "Logon failure: Unknown user name or bad password.",
scWin32Status_Hex =~ "532", "Logon failure: The specified account password has expired.",
scWin32Status_Hex =~ "533", "Logon failure: Account currently disabled.",
scWin32Status_Hex =~ "2ee2", "The request has timed out.",
scWin32Status_Hex =~ "0", "The operation completed successfully.",
scWin32Status_Hex =~ "1", "Incorrect function.",
scWin32Status_Hex =~ "2", "The system cannot find the file specified.",
scWin32Status_Hex =~ "3", "The system cannot find the path specified.",
scWin32Status_Hex =~ "4", "The system cannot open the file.",
scWin32Status_Hex =~ "5", "Access is denied.",
scWin32Status_Hex =~ "8009030e", "SEC_E_NO_CREDENTIALS",
scWin32Status_Hex =~ "8009030C", "SEC_E_LOGON_DENIED",
"See - https://msdn.microsoft.com/library/cc231199.aspx")
// decode URI when available
| extend decodedUriQuery = url_decode(csUriQuery)
// Count of failed logons by a user
| summarize makeset(decodedUriQuery), makeset(cIP), makeset(sSiteName), makeset(sPort), makeset(csUserAgent), makeset(csMethod), makeset(csUriQuery), makeset(scStatusFull), makeset(scStatusFull_Friendly), makeset(scWin32Status_Hex), makeset(scWin32Status_Friendly), FailedConnectionsCount = count() by bin(TimeGenerated, timeBin), csUserName, Computer, sIP
| where FailedConnectionsCount >= failedThreshold
| project TimeGenerated, csUserName, set_decodedUriQuery, Computer, set_sSiteName, sIP, set_cIP, set_sPort, set_csUserAgent, set_csMethod, set_scStatusFull, set_scStatusFull_Friendly, set_scWin32Status_Hex, set_scWin32Status_Friendly, FailedConnectionsCount
| order by FailedConnectionsCount
| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
| extend AccountName = tostring(split(csUserName, "@")[0]), AccountUPNSuffix = tostring(split(csUserName, "@")[1])
queryFrequency: 1d
description: |
'Identifies when 100 or more failed attempts by a given user in 10 minutes occur on the IIS Server.
This could be indicative of attempted brute force based on known account information.
This could also simply indicate a misconfigured service or device.
References:
IIS status code mapping - https://support.microsoft.com/help/943891/the-http-status-code-in-iis-7-0-iis-7-5-and-iis-8-0
Win32 Status code mapping - https://msdn.microsoft.com/library/cc231199.aspx'
severity: Medium
version: 1.0.3
relevantTechniques:
- T1110
name: High count of failed logons by a user
triggerThreshold: 0
kind: Scheduled
query: |
let timeBin = 10m;
let failedThreshold = 100;
W3CIISLog
| where scStatus in ("401","403")
| where csUserName != "-"
// Handling Exchange specific items in IIS logs to remove the unique log identifier in the URI
| extend csUriQuery = iff(csUriQuery startswith "MailboxId=", tostring(split(csUriQuery, "&")[0]) , csUriQuery )
| extend csUriQuery = iff(csUriQuery startswith "X-ARR-CACHE-HIT=", strcat(tostring(split(csUriQuery, "&")[0]),tostring(split(csUriQuery, "&")[1])) , csUriQuery )
| extend scStatusFull = strcat(scStatus, ".",scSubStatus)
// Map common IIS codes
| extend scStatusFull_Friendly = case(
scStatusFull == "401.0", "Access denied.",
scStatusFull == "401.1", "Logon failed.",
scStatusFull == "401.2", "Logon failed due to server configuration.",
scStatusFull == "401.3", "Unauthorized due to ACL on resource.",
scStatusFull == "401.4", "Authorization failed by filter.",
scStatusFull == "401.5", "Authorization failed by ISAPI/CGI application.",
scStatusFull == "403.0", "Forbidden.",
scStatusFull == "403.4", "SSL required.",
"See - https://support.microsoft.com/help/943891/the-http-status-code-in-iis-7-0-iis-7-5-and-iis-8-0")
// Mapping to Hex so can be mapped using website in comments above
| extend scWin32Status_Hex = tohex(tolong(scWin32Status))
// Map common win32 codes
| extend scWin32Status_Friendly = case(
scWin32Status_Hex =~ "775", "The referenced account is currently locked out and cannot be logged on to.",
scWin32Status_Hex =~ "52e", "Logon failure: Unknown user name or bad password.",
scWin32Status_Hex =~ "532", "Logon failure: The specified account password has expired.",
scWin32Status_Hex =~ "533", "Logon failure: Account currently disabled.",
scWin32Status_Hex =~ "2ee2", "The request has timed out.",
scWin32Status_Hex =~ "0", "The operation completed successfully.",
scWin32Status_Hex =~ "1", "Incorrect function.",
scWin32Status_Hex =~ "2", "The system cannot find the file specified.",
scWin32Status_Hex =~ "3", "The system cannot find the path specified.",
scWin32Status_Hex =~ "4", "The system cannot open the file.",
scWin32Status_Hex =~ "5", "Access is denied.",
scWin32Status_Hex =~ "8009030e", "SEC_E_NO_CREDENTIALS",
scWin32Status_Hex =~ "8009030C", "SEC_E_LOGON_DENIED",
"See - https://msdn.microsoft.com/library/cc231199.aspx")
// decode URI when available
| extend decodedUriQuery = url_decode(csUriQuery)
// Count of failed logons by a user
| summarize makeset(decodedUriQuery), makeset(cIP), makeset(sSiteName), makeset(sPort), makeset(csUserAgent), makeset(csMethod), makeset(csUriQuery), makeset(scStatusFull), makeset(scStatusFull_Friendly), makeset(scWin32Status_Hex), makeset(scWin32Status_Friendly), FailedConnectionsCount = count() by bin(TimeGenerated, timeBin), csUserName, Computer, sIP
| where FailedConnectionsCount >= failedThreshold
| project TimeGenerated, csUserName, set_decodedUriQuery, Computer, set_sSiteName, sIP, set_cIP, set_sPort, set_csUserAgent, set_csMethod, set_scStatusFull, set_scStatusFull_Friendly, set_scWin32Status_Hex, set_scWin32Status_Friendly, FailedConnectionsCount
| order by FailedConnectionsCount
| extend HostName = tostring(split(Computer, ".")[0]), DomainIndex = toint(indexof(Computer, '.'))
| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)
| extend AccountName = tostring(split(csUserName, "@")[0]), AccountUPNSuffix = tostring(split(csUserName, "@")[1])
triggerOperator: gt
OriginalUri: https://github.com/Azure/Azure-Sentinel/blob/master/Detections/W3CIISLog/HighFailedLogonCountByUser.yaml
requiredDataConnectors:
- connectorId: AzureMonitor(IIS)
dataTypes:
- W3CIISLog
metadata:
source:
kind: Community
author:
name: Microsoft Security Research
support:
tier: Community
categories:
domains:
- Security - Others
tactics:
- CredentialAccess
id: 884c4957-70ea-4f57-80b9-1bca3890315b
queryPeriod: 1d
entityMappings:
- fieldMappings:
- columnName: csUserName
identifier: FullName
- columnName: AccountName
identifier: Name
- columnName: AccountUPNSuffix
identifier: UPNSuffix
entityType: Account
- fieldMappings:
- columnName: Computer
identifier: FullName
- columnName: HostName
identifier: HostName
- columnName: HostNameDomain
identifier: NTDomain
entityType: Host
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"workspace": {
"type": "String"
}
},
"resources": [
{
"apiVersion": "2024-01-01-preview",
"id": "[concat(resourceId('Microsoft.OperationalInsights/workspaces/providers', parameters('workspace'), 'Microsoft.SecurityInsights'),'/alertRules/884c4957-70ea-4f57-80b9-1bca3890315b')]",
"kind": "Scheduled",
"name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/884c4957-70ea-4f57-80b9-1bca3890315b')]",
"properties": {
"alertRuleTemplateName": "884c4957-70ea-4f57-80b9-1bca3890315b",
"customDetails": null,
"description": "'Identifies when 100 or more failed attempts by a given user in 10 minutes occur on the IIS Server.\nThis could be indicative of attempted brute force based on known account information.\nThis could also simply indicate a misconfigured service or device.\nReferences:\nIIS status code mapping - https://support.microsoft.com/help/943891/the-http-status-code-in-iis-7-0-iis-7-5-and-iis-8-0\nWin32 Status code mapping - https://msdn.microsoft.com/library/cc231199.aspx'\n",
"displayName": "High count of failed logons by a user",
"enabled": true,
"entityMappings": [
{
"entityType": "Account",
"fieldMappings": [
{
"columnName": "csUserName",
"identifier": "FullName"
},
{
"columnName": "AccountName",
"identifier": "Name"
},
{
"columnName": "AccountUPNSuffix",
"identifier": "UPNSuffix"
}
]
},
{
"entityType": "Host",
"fieldMappings": [
{
"columnName": "Computer",
"identifier": "FullName"
},
{
"columnName": "HostName",
"identifier": "HostName"
},
{
"columnName": "HostNameDomain",
"identifier": "NTDomain"
}
]
}
],
"OriginalUri": "https://github.com/Azure/Azure-Sentinel/blob/master/Detections/W3CIISLog/HighFailedLogonCountByUser.yaml",
"query": "let timeBin = 10m;\nlet failedThreshold = 100;\nW3CIISLog\n| where scStatus in (\"401\",\"403\")\n| where csUserName != \"-\"\n// Handling Exchange specific items in IIS logs to remove the unique log identifier in the URI\n| extend csUriQuery = iff(csUriQuery startswith \"MailboxId=\", tostring(split(csUriQuery, \"&\")[0]) , csUriQuery )\n| extend csUriQuery = iff(csUriQuery startswith \"X-ARR-CACHE-HIT=\", strcat(tostring(split(csUriQuery, \"&\")[0]),tostring(split(csUriQuery, \"&\")[1])) , csUriQuery )\n| extend scStatusFull = strcat(scStatus, \".\",scSubStatus)\n// Map common IIS codes\n| extend scStatusFull_Friendly = case(\nscStatusFull == \"401.0\", \"Access denied.\",\nscStatusFull == \"401.1\", \"Logon failed.\",\nscStatusFull == \"401.2\", \"Logon failed due to server configuration.\",\nscStatusFull == \"401.3\", \"Unauthorized due to ACL on resource.\",\nscStatusFull == \"401.4\", \"Authorization failed by filter.\",\nscStatusFull == \"401.5\", \"Authorization failed by ISAPI/CGI application.\",\nscStatusFull == \"403.0\", \"Forbidden.\",\nscStatusFull == \"403.4\", \"SSL required.\",\n\"See - https://support.microsoft.com/help/943891/the-http-status-code-in-iis-7-0-iis-7-5-and-iis-8-0\")\n// Mapping to Hex so can be mapped using website in comments above\n| extend scWin32Status_Hex = tohex(tolong(scWin32Status))\n// Map common win32 codes\n| extend scWin32Status_Friendly = case(\nscWin32Status_Hex =~ \"775\", \"The referenced account is currently locked out and cannot be logged on to.\",\nscWin32Status_Hex =~ \"52e\", \"Logon failure: Unknown user name or bad password.\",\nscWin32Status_Hex =~ \"532\", \"Logon failure: The specified account password has expired.\",\nscWin32Status_Hex =~ \"533\", \"Logon failure: Account currently disabled.\",\nscWin32Status_Hex =~ \"2ee2\", \"The request has timed out.\",\nscWin32Status_Hex =~ \"0\", \"The operation completed successfully.\",\nscWin32Status_Hex =~ \"1\", \"Incorrect function.\",\nscWin32Status_Hex =~ \"2\", \"The system cannot find the file specified.\",\nscWin32Status_Hex =~ \"3\", \"The system cannot find the path specified.\",\nscWin32Status_Hex =~ \"4\", \"The system cannot open the file.\",\nscWin32Status_Hex =~ \"5\", \"Access is denied.\",\nscWin32Status_Hex =~ \"8009030e\", \"SEC_E_NO_CREDENTIALS\",\nscWin32Status_Hex =~ \"8009030C\", \"SEC_E_LOGON_DENIED\",\n\"See - https://msdn.microsoft.com/library/cc231199.aspx\")\n// decode URI when available\n| extend decodedUriQuery = url_decode(csUriQuery)\n// Count of failed logons by a user\n| summarize makeset(decodedUriQuery), makeset(cIP), makeset(sSiteName), makeset(sPort), makeset(csUserAgent), makeset(csMethod), makeset(csUriQuery), makeset(scStatusFull), makeset(scStatusFull_Friendly), makeset(scWin32Status_Hex), makeset(scWin32Status_Friendly), FailedConnectionsCount = count() by bin(TimeGenerated, timeBin), csUserName, Computer, sIP\n| where FailedConnectionsCount >= failedThreshold\n| project TimeGenerated, csUserName, set_decodedUriQuery, Computer, set_sSiteName, sIP, set_cIP, set_sPort, set_csUserAgent, set_csMethod, set_scStatusFull, set_scStatusFull_Friendly, set_scWin32Status_Hex, set_scWin32Status_Friendly, FailedConnectionsCount\n| order by FailedConnectionsCount\n| extend HostName = tostring(split(Computer, \".\")[0]), DomainIndex = toint(indexof(Computer, '.'))\n| extend HostNameDomain = iff(DomainIndex != -1, substring(Computer, DomainIndex + 1), Computer)\n| extend AccountName = tostring(split(csUserName, \"@\")[0]), AccountUPNSuffix = tostring(split(csUserName, \"@\")[1])\n",
"queryFrequency": "P1D",
"queryPeriod": "P1D",
"severity": "Medium",
"subTechniques": [],
"suppressionDuration": "PT1H",
"suppressionEnabled": false,
"tactics": [
"CredentialAccess"
],
"techniques": [
"T1110"
],
"templateVersion": "1.0.3",
"triggerOperator": "GreaterThan",
"triggerThreshold": 0
},
"type": "Microsoft.OperationalInsights/workspaces/providers/alertRules"
}
]
}