Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Repackaged Microsoft Entra ID Solution #10987

Merged
merged 1 commit into from
Aug 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified Solutions/Microsoft Entra ID/Package/3.2.8.zip
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -908,7 +908,7 @@
"name": "analytic54-text",
"type": "Microsoft.Common.TextBlock",
"options": {
"text": "Identifies when a user account successfully logs onto an Azure App from one IP and within 10 mins failed to logon to the same App via a different IP (may indicate a malicious attempt at password guessing with known account). \nUEBA added for context to gather all asoociated information assocaited with IP addressed initiating Faile Logon and affected user."
"text": "Identifies when a user account successfully logs onto an Azure App from one IP and within 10 mins failed to logon to the same App via a different IP (may indicate a malicious attempt at password guessing with known account). \nUEBA added for context to gather all asoociated information assocaited with IP addressed initiating Faile Logon and affected user. \nPlease note, Failed logons from known IP ranges can be benign depending on the conditional access policies. In case of noisy behavior, consider tuning the source IP ranges after careful consideration"
}
}
]
Expand Down
8 changes: 4 additions & 4 deletions Solutions/Microsoft Entra ID/Package/mainTemplate.json
Original file line number Diff line number Diff line change
Expand Up @@ -446,11 +446,11 @@
"_analyticRulecontentProductId53": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','48607a29-a26a-4abf-8078-a06dbdd174a4','-', '1.0.7')))]"
},
"analyticRuleObject54": {
"analyticRuleVersion54": "2.1.10",
"analyticRuleVersion54": "2.1.11",
"_analyticRulecontentId54": "02ef8d7e-fc3a-4d86-a457-650fa571d8d2",
"analyticRuleId54": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', '02ef8d7e-fc3a-4d86-a457-650fa571d8d2')]",
"analyticRuleTemplateSpecName54": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring('02ef8d7e-fc3a-4d86-a457-650fa571d8d2')))]",
"_analyticRulecontentProductId54": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','02ef8d7e-fc3a-4d86-a457-650fa571d8d2','-', '2.1.10')))]"
"_analyticRulecontentProductId54": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-','02ef8d7e-fc3a-4d86-a457-650fa571d8d2','-', '2.1.11')))]"
},
"analyticRuleObject55": {
"analyticRuleVersion55": "1.0.4",
Expand Down Expand Up @@ -8859,10 +8859,10 @@
"kind": "Scheduled",
"location": "[parameters('workspace-location')]",
"properties": {
"description": "Identifies when a user account successfully logs onto an Azure App from one IP and within 10 mins failed to logon to the same App via a different IP (may indicate a malicious attempt at password guessing with known account). \nUEBA added for context to gather all asoociated information assocaited with IP addressed initiating Faile Logon and affected user.",
"description": "Identifies when a user account successfully logs onto an Azure App from one IP and within 10 mins failed to logon to the same App via a different IP (may indicate a malicious attempt at password guessing with known account). \nUEBA added for context to gather all asoociated information assocaited with IP addressed initiating Faile Logon and affected user. \nPlease note, Failed logons from known IP ranges can be benign depending on the conditional access policies. In case of noisy behavior, consider tuning the source IP ranges after careful consideration",
"displayName": "Successful logon from IP and failure from a different IP",
"enabled": false,
"query": "let riskScoreCutoff = 20; //Adjust this based on volume of results\nlet logonDiff = 10m; let aadFunc = (tableName:string){ table(tableName)\n| where ResultType == \"0\"\n| where AppDisplayName !in (\"Office 365 Exchange Online\", \"Skype for Business Online\") // To remove false-positives, add more Apps to this array\n// ---------- Fix for SuccessBlock to also consider IPv6\n| extend SuccessIPv6Block = strcat(split(IPAddress, \":\")[0], \":\", split(IPAddress, \":\")[1], \":\", split(IPAddress, \":\")[2], \":\", split(IPAddress, \":\")[3])\n| extend SuccessIPv4Block = strcat(split(IPAddress, \".\")[0], \".\", split(IPAddress, \".\")[1])\n// ------------------\n| project SuccessLogonTime = TimeGenerated, UserPrincipalName, SuccessIPAddress = IPAddress, SuccessLocation = Location, AppDisplayName, SuccessIPBlock = iff(IPAddress contains \":\", strcat(split(IPAddress, \":\")[0], \":\", split(IPAddress, \":\")[1]), strcat(split(IPAddress, \".\")[0], \".\", split(IPAddress, \".\")[1])), Type\n| join kind= inner (\n table(tableName)\n | where ResultType !in (\"0\", \"50140\")\n | where ResultDescription !~ \"Other\"\n | where AppDisplayName !in (\"Office 365 Exchange Online\", \"Skype for Business Online\")\n | project FailedLogonTime = TimeGenerated, UserPrincipalName, FailedIPAddress = IPAddress, FailedLocation = Location, AppDisplayName, ResultType, ResultDescription, Type \n) on UserPrincipalName, AppDisplayName\n| where SuccessLogonTime < FailedLogonTime and FailedLogonTime - SuccessLogonTime <= logonDiff and FailedIPAddress !startswith SuccessIPBlock\n| summarize FailedLogonTime = max(FailedLogonTime), SuccessLogonTime = max(SuccessLogonTime) by UserPrincipalName, SuccessIPAddress, SuccessLocation, AppDisplayName, FailedIPAddress, FailedLocation, ResultType, ResultDescription, Type\n| extend timestamp = SuccessLogonTime\n| extend UserPrincipalName = tolower(UserPrincipalName)};\nlet aadSignin = aadFunc(\"SigninLogs\");\nlet aadNonInt = aadFunc(\"AADNonInteractiveUserSignInLogs\");\nunion isfuzzy=true aadSignin, aadNonInt\n| extend Name = tostring(split(UserPrincipalName,'@',0)[0]), UPNSuffix = tostring(split(UserPrincipalName,'@',1)[0])\n// UEBA context below - make sure you have these 2 datatypes, otherwise the query will not work. If so, comment all that is below.\n| join kind=leftouter (\n IdentityInfo\n | summarize LatestReportTime = arg_max(TimeGenerated, *) by AccountUPN\n | project AccountUPN, Tags, JobTitle, GroupMembership, AssignedRoles, UserType, IsAccountEnabled\n | summarize\n Tags = make_set(Tags, 1000),\n GroupMembership = make_set(GroupMembership, 1000),\n AssignedRoles = make_set(AssignedRoles, 1000),\n UserType = make_set(UserType, 1000),\n UserAccountControl = make_set(UserType, 1000)\n by AccountUPN\n | extend UserPrincipalName=tolower(AccountUPN)\n) on UserPrincipalName\n//Below it will be joined with BehaviorAnalytics table to the Failed IP Addresses\n| join kind=leftouter (\n BehaviorAnalytics\n | where ActivityType in (\"FailedLogOn\", \"LogOn\")\n | where isnotempty(SourceIPAddress)\n | project UsersInsights, DevicesInsights, ActivityInsights, InvestigationPriority, SourceIPAddress\n | project-rename FailedIPAddress = SourceIPAddress\n | summarize\n Total_IPInvestigationPriority = sum(InvestigationPriority) // Sum of all Investigation Property for IP of all Users\n by FailedIPAddress)\non FailedIPAddress\n| extend UEBARiskScoreforIP = Total_IPInvestigationPriority\n| where UEBARiskScoreforIP > riskScoreCutoff\n| sort by UEBARiskScoreforIP desc \n",
"query": "let riskScoreCutoff = 3; //Adjust this score threshold based on volume of results. Activities identified as the most abnormal receive the highest scores (on a scale of 0-10)\nlet logonDiff = 10m; \nlet aadFunc = (tableName:string)\n{ \ntable(tableName)\n| where ResultType == \"0\"\n| where AppDisplayName !in (\"Office 365 Exchange Online\", \"Skype for Business Online\") // To remove false-positives, add more Apps to this array\n// ---------- Fix for SuccessBlock to also consider IPv6\n| extend SuccessIPv6Block = strcat(split(IPAddress, \":\")[0], \":\", split(IPAddress, \":\")[1], \":\", split(IPAddress, \":\")[2], \":\", split(IPAddress, \":\")[3])\n| extend SuccessIPv4Block = strcat(split(IPAddress, \".\")[0], \".\", split(IPAddress, \".\")[1])\n// ------------------\n| project SuccessLogonTime = TimeGenerated, UserPrincipalName, SuccessIPAddress = IPAddress, SuccessLocation = Location, AppDisplayName, SuccessIPBlock = iff(IPAddress contains \":\", strcat(split(IPAddress, \":\")[0], \":\", split(IPAddress, \":\")[1]), strcat(split(IPAddress, \".\")[0], \".\", split(IPAddress, \".\")[1])), Type\n| join kind= inner (\n table(tableName)\n | where ResultType !in (\"0\", \"50140\")\n | where ResultDescription !~ \"Other\"\n | where AppDisplayName !in (\"Office 365 Exchange Online\", \"Skype for Business Online\")\n | project FailedLogonTime = TimeGenerated, UserPrincipalName, FailedIPAddress = IPAddress, FailedLocation = Location, AppDisplayName, ResultType, ResultDescription, Type \n) on UserPrincipalName, AppDisplayName\n| where SuccessLogonTime < FailedLogonTime and FailedLogonTime - SuccessLogonTime <= logonDiff and FailedIPAddress !startswith SuccessIPBlock\n| summarize FailedLogonTime = max(FailedLogonTime), SuccessLogonTime = max(SuccessLogonTime) by UserPrincipalName, SuccessIPAddress, SuccessLocation, AppDisplayName, FailedIPAddress, FailedLocation, ResultType, ResultDescription, Type\n| extend timestamp = SuccessLogonTime\n| extend UserPrincipalName = tolower(UserPrincipalName)};\nlet aadSignin = aadFunc(\"SigninLogs\");\nlet aadNonInt = aadFunc(\"AADNonInteractiveUserSignInLogs\");\nunion isfuzzy=true aadSignin, aadNonInt\n| extend Name = tostring(split(UserPrincipalName,'@',0)[0]), UPNSuffix = tostring(split(UserPrincipalName,'@',1)[0])\n// UEBA context below - make sure you have these 2 datatypes, otherwise the query will not work. If so, comment all that is below.\n| join kind=leftouter (\n IdentityInfo\n | summarize LatestReportTime = arg_max(TimeGenerated, *) by AccountUPN\n | project AccountUPN, Tags, JobTitle, GroupMembership, AssignedRoles, UserType, IsAccountEnabled\n | summarize\n Tags = make_set(Tags, 1000),\n GroupMembership = make_set(GroupMembership, 1000),\n AssignedRoles = make_set(AssignedRoles, 1000),\n UserType = make_set(UserType, 1000),\n UserAccountControl = make_set(UserType, 1000)\n by AccountUPN\n | extend UserPrincipalName=tolower(AccountUPN)\n) on UserPrincipalName\n//Below it will be joined with BehaviorAnalytics table to the Failed IP Addresses\n| join kind=leftouter (\n BehaviorAnalytics\n | where ActivityType in (\"FailedLogOn\", \"LogOn\")\n | where isnotempty(SourceIPAddress)\n | project UsersInsights, DevicesInsights, ActivityInsights, InvestigationPriority, SourceIPAddress, UserName\n | project-rename FailedIPAddress = SourceIPAddress, Name = UserName\n | summarize\n MaxInvestigationScore = max(InvestigationPriority) // Only retrieve maximum Investigation Property score for both FailedIP and User\n by FailedIPAddress, Name)\non FailedIPAddress, Name // Joining on both IP and User so as to only return context associated with same user\n| extend UEBARiskScore = MaxInvestigationScore\n| project-away *1 // removing duplicate columns post outer join from output\n| where UEBARiskScore > riskScoreCutoff\n| sort by UEBARiskScore desc\n",
"queryFrequency": "P1D",
"queryPeriod": "P1D",
"severity": "Medium",
Expand Down
Loading