1380 строки
87 KiB
JSON
1380 строки
87 KiB
JSON
{
|
||
"version": "Notebook/1.0",
|
||
"items": [
|
||
{
|
||
"type": 1,
|
||
"content": {
|
||
"json": "## Solorigate Post Compromise Hunting\n---\n\nThis hunting workbook is intended to help identify activity related to the SolarWinds compromise and subsequent attacks discovered in December 2020.This activity is refered to a Solorigate and UNC2452.<br>\nMore details can be found in the following reports:\n - https://aka.ms/solorigate\n - https://aka.ms/sentinelsolorigatehunt\n - https://techcommunity.microsoft.com/t5/azure-active-directory-identity/understanding-quot-solorigate-quot-s-identity-iocs-for-identity/ba-p/2007610\n - https://www.microsoft.com/security/blog/2020/12/21/advice-for-incident-responders-on-recovery-from-systemic-identity-compromises/\n - https://www.fireeye.com/blog/threat-research/2020/12/evasive-attacker-leverages-solarwinds-supply-chain-compromises-with-sunburst-backdoor.html\n - https://www.fireeye.com/blog/products-and-services/2020/12/global-intrusion-campaign-leverages-software-supply-chain-compromise.html\n - https://www.solarwinds.com/securityadvisory\n - https://www.microsoft.com/security/blog/2021/01/20/deep-dive-into-the-solorigate-second-stage-activation-from-sunburst-to-teardrop-and-raindrop/\n - https://www.fireeye.com/content/dam/collateral/en/wp-m-unc2452.pdf"
|
||
},
|
||
"name": "text - 2"
|
||
},
|
||
{
|
||
"type": 11,
|
||
"content": {
|
||
"version": "LinkItem/1.0",
|
||
"style": "tabs",
|
||
"links": [
|
||
{
|
||
"id": "fbc91177-748c-4880-946a-b5d014e32aa6",
|
||
"cellValue": "nav_val",
|
||
"linkTarget": "parameter",
|
||
"linkLabel": "Suspicious Signins",
|
||
"subTarget": "1",
|
||
"style": "link"
|
||
},
|
||
{
|
||
"id": "c2a98db3-8b74-459d-a4f5-c61ab64b7756",
|
||
"cellValue": "nav_val",
|
||
"linkTarget": "parameter",
|
||
"linkLabel": "Suspicious App Modifications",
|
||
"subTarget": "2",
|
||
"style": "link"
|
||
},
|
||
{
|
||
"id": "bafb8fbb-3d10-43d7-97ea-808d17742c52",
|
||
"cellValue": "nav_val",
|
||
"linkTarget": "parameter",
|
||
"linkLabel": "Suspicious Lateral Movement",
|
||
"subTarget": "3",
|
||
"style": "link"
|
||
},
|
||
{
|
||
"id": "e7ed0b51-0c07-4189-a0bf-d64f97e0a124",
|
||
"cellValue": "nav_val",
|
||
"linkTarget": "parameter",
|
||
"linkLabel": "Suspicious Host Activity",
|
||
"subTarget": "4",
|
||
"style": "link"
|
||
},
|
||
{
|
||
"id": "c6477625-71d2-4d9c-a4f9-1ccb1a08d5df",
|
||
"cellValue": "nav_val",
|
||
"linkTarget": "parameter",
|
||
"linkLabel": "Suspicious Network Activity",
|
||
"subTarget": "5",
|
||
"style": "link"
|
||
}
|
||
]
|
||
},
|
||
"name": "links - 6"
|
||
},
|
||
{
|
||
"type": 12,
|
||
"content": {
|
||
"version": "NotebookGroup/1.0",
|
||
"groupType": "editable",
|
||
"title": "Suspicious Signin Activity",
|
||
"items": [
|
||
{
|
||
"type": 1,
|
||
"content": {
|
||
"json": "## Suspicious Signins\r\n------------------------\r\n\r\nThis section hunts for suspicious sign-in events within your Azure AD tenant. It takes TTPs reported by Microsoft, FireEye and the NSA to identify logon events from known VPS provider IP ranges where the only logons using SAML tokens provided by external identity providers, or refresh tokens have been used. This helps identify instances where an attacker is using SAML tokens minted by stolen ADFS key material to access your environment and bypass MFA. This hunting query may produce false positive if users are accessing services via VPN services. \r\n\r\nSelect a user session in the initial query to populate the further queries that provide context on the users other logon activity, this is to help distinguish legitimate logons from malicious ones.\r\n"
|
||
},
|
||
"name": "text - 6"
|
||
},
|
||
{
|
||
"type": 9,
|
||
"content": {
|
||
"version": "KqlParameterItem/1.0",
|
||
"parameters": [
|
||
{
|
||
"id": "ea3c6cdf-3199-4b98-845f-cfdba057542f",
|
||
"version": "KqlParameterItem/1.0",
|
||
"name": "timeframe",
|
||
"label": "Hunting Timeframe",
|
||
"type": 4,
|
||
"description": "Used to set the scope of other queries",
|
||
"value": {
|
||
"durationMs": 2592000000
|
||
},
|
||
"typeSettings": {
|
||
"selectableValues": [
|
||
{
|
||
"durationMs": 43200000
|
||
},
|
||
{
|
||
"durationMs": 86400000
|
||
},
|
||
{
|
||
"durationMs": 172800000
|
||
},
|
||
{
|
||
"durationMs": 259200000
|
||
},
|
||
{
|
||
"durationMs": 604800000
|
||
},
|
||
{
|
||
"durationMs": 1209600000
|
||
},
|
||
{
|
||
"durationMs": 2419200000
|
||
},
|
||
{
|
||
"durationMs": 2592000000
|
||
},
|
||
{
|
||
"durationMs": 5184000000
|
||
},
|
||
{
|
||
"durationMs": 7776000000
|
||
}
|
||
],
|
||
"allowCustom": true
|
||
},
|
||
"timeContext": {
|
||
"durationMs": 86400000
|
||
}
|
||
}
|
||
],
|
||
"style": "pills",
|
||
"queryType": 0,
|
||
"resourceType": "microsoft.operationalinsights/workspaces"
|
||
},
|
||
"customWidth": "30",
|
||
"name": "parameters - 0"
|
||
},
|
||
{
|
||
"type": 1,
|
||
"content": {
|
||
"json": "Set the timeframe you wish to hunt in using the dropdown to the right.\r\nNote that using a large timeframe may cause queries to timeout depending on the size of your environment. If you have difficulties try reducing your timeframe.",
|
||
"style": "info"
|
||
},
|
||
"customWidth": "70",
|
||
"name": "text - 1"
|
||
},
|
||
{
|
||
"type": 3,
|
||
"content": {
|
||
"version": "KqlItem/1.0",
|
||
"query": "let IP_Data = (externaldata(network:string)\r\n [@\"https://raw.githubusercontent.com/Azure/Azure-Sentinel/master/Sample%20Data/Feeds/VPS_Networks.csv\"] with (format=\"csv\"));\r\nSigninLogs\r\n| where ResultType == 0\r\n| extend additionalDetails = tostring(Status.additionalDetails)\r\n| evaluate ipv4_lookup(IP_Data, IPAddress, network, return_unmatched = false)\r\n| summarize make_set(additionalDetails), min(TimeGenerated), max(TimeGenerated) by IPAddress, UserPrincipalName\r\n| where array_length(set_additionalDetails) == 2\r\n| where (set_additionalDetails[1] == \"MFA requirement satisfied by claim in the token\" and set_additionalDetails[0] == \"MFA requirement satisfied by claim provided by external provider\") or (set_additionalDetails[0] == \"MFA requirement satisfied by claim in the token\" and set_additionalDetails[1] == \"MFA requirement satisfied by claim provided by external provider\")\r\n| project IPAddress, UserPrincipalName, min_TimeGenerated, max_TimeGenerated",
|
||
"size": 0,
|
||
"title": "Successful User Signins from VPS providers where only Tokens were used to authenticate.",
|
||
"noDataMessage": "No user signins in the timeframe set.",
|
||
"timeContext": {
|
||
"durationMs": 2592000000
|
||
},
|
||
"timeContextFromParameter": "timeframe",
|
||
"exportedParameters": [
|
||
{
|
||
"fieldName": "IPAddress",
|
||
"parameterName": "ip_addr",
|
||
"parameterType": 1
|
||
},
|
||
{
|
||
"fieldName": "UserPrincipalName",
|
||
"parameterName": "upn",
|
||
"parameterType": 1
|
||
}
|
||
],
|
||
"queryType": 0,
|
||
"resourceType": "microsoft.operationalinsights/workspaces"
|
||
},
|
||
"name": "Suspicious Signins"
|
||
},
|
||
{
|
||
"type": 3,
|
||
"content": {
|
||
"version": "KqlItem/1.0",
|
||
"query": "SigninLogs\r\n| where ResultType == 0\r\n| where UserPrincipalName == '{upn}'\r\n| extend additionalDetails = tostring(Status.additionalDetails)\r\n | extend Browser = tostring(DeviceDetail.browser)\r\n | extend Country = tostring(LocationDetails.countryOrRegion)\r\n | extend City = tostring(LocationDetails.city)\r\n | project TimeGenerated, ResultType, UserPrincipalName, UserAgent, Browser, City, Country, additionalDetails, IPAddress\r\n",
|
||
"size": 0,
|
||
"title": "All Signin Attempts by {upn}",
|
||
"timeContext": {
|
||
"durationMs": 2592000000
|
||
},
|
||
"timeContextFromParameter": "timeframe",
|
||
"queryType": 0,
|
||
"resourceType": "microsoft.operationalinsights/workspaces",
|
||
"gridSettings": {
|
||
"sortBy": [
|
||
{
|
||
"itemKey": "UserPrincipalName",
|
||
"sortOrder": 1
|
||
}
|
||
]
|
||
},
|
||
"sortBy": [
|
||
{
|
||
"itemKey": "UserPrincipalName",
|
||
"sortOrder": 1
|
||
}
|
||
]
|
||
},
|
||
"customWidth": "50",
|
||
"conditionalVisibility": {
|
||
"parameterName": "upn",
|
||
"comparison": "isNotEqualTo"
|
||
},
|
||
"name": "user logons"
|
||
},
|
||
{
|
||
"type": 3,
|
||
"content": {
|
||
"version": "KqlItem/1.0",
|
||
"query": "SigninLogs\r\n| where ResultType == 0\r\n| where UserPrincipalName == '{upn}'\r\n| extend additionalDetails = tostring(Status.additionalDetails)\r\n | extend Browser = tostring(DeviceDetail.browser)\r\n | extend Country = tostring(LocationDetails.countryOrRegion)\r\n | extend City = tostring(LocationDetails.city)\r\n | extend Lat = tostring(LocationDetails.geoCoordinates.latitude)\r\n | extend Long = tostring(LocationDetails.geoCoordinates.longitude)\r\n | project TimeGenerated, ResultType, UserPrincipalName, UserAgent, Browser, City, Country, additionalDetails, IPAddress, LocationDetails, Lat, Long\r\n\r\n",
|
||
"size": 0,
|
||
"title": "Sucessful Logon Locations for {upn}",
|
||
"timeContext": {
|
||
"durationMs": 2592000000
|
||
},
|
||
"timeContextFromParameter": "timeframe",
|
||
"queryType": 0,
|
||
"resourceType": "microsoft.operationalinsights/workspaces",
|
||
"visualization": "map",
|
||
"mapSettings": {
|
||
"locInfo": "LatLong",
|
||
"latitude": "Lat",
|
||
"longitude": "Long",
|
||
"sizeAggregation": "Sum",
|
||
"defaultSize": 10,
|
||
"labelSettings": "City",
|
||
"legendMetric": "City",
|
||
"legendAggregation": "Count",
|
||
"itemColorSettings": null
|
||
}
|
||
},
|
||
"customWidth": "50",
|
||
"conditionalVisibility": {
|
||
"parameterName": "upn",
|
||
"comparison": "isNotEqualTo"
|
||
},
|
||
"name": "query - 4"
|
||
},
|
||
{
|
||
"type": 3,
|
||
"content": {
|
||
"version": "KqlItem/1.0",
|
||
"query": "SigninLogs\r\n| where ResultType == 0\r\n| where UserPrincipalName == '{upn}'\r\n| extend additionalDetails = tostring(Status.additionalDetails)\r\n | extend Browser = tostring(DeviceDetail.browser)\r\n | extend Country = tostring(LocationDetails.countryOrRegion)\r\n | extend City = tostring(LocationDetails.city)\r\n | project TimeGenerated, ResultType, UserPrincipalName, UserAgent, Browser, City, Country, additionalDetails, IPAddress\r\n | summarize count() by bin(TimeGenerated, 1h)\r\n",
|
||
"size": 0,
|
||
"title": "Successful Logons Time Distribution for {upn} ",
|
||
"timeContext": {
|
||
"durationMs": 2592000000
|
||
},
|
||
"timeContextFromParameter": "timeframe",
|
||
"queryType": 0,
|
||
"resourceType": "microsoft.operationalinsights/workspaces",
|
||
"visualization": "categoricalbar",
|
||
"chartSettings": {
|
||
"xAxis": "TimeGenerated",
|
||
"yAxis": [
|
||
"count_"
|
||
],
|
||
"group": null,
|
||
"createOtherGroup": 0,
|
||
"showMetrics": false
|
||
}
|
||
},
|
||
"conditionalVisibility": {
|
||
"parameterName": "upn",
|
||
"comparison": "isNotEqualTo"
|
||
},
|
||
"name": "query - 5"
|
||
},
|
||
{
|
||
"type": 3,
|
||
"content": {
|
||
"version": "KqlItem/1.0",
|
||
"query": "SigninLogs\r\n| where ResultType == \"5000811\"\r\n| extend additionalDetails = tostring(Status.additionalDetails)\r\n | extend Browser = tostring(DeviceDetail.browser)\r\n | extend Country = tostring(LocationDetails.countryOrRegion)\r\n | extend City = tostring(LocationDetails.city)\r\n | project-reorder TimeGenerated, ResultType, UserPrincipalName, UserAgent, Browser, City, Country, additionalDetails, IPAddress\r\n",
|
||
"size": 0,
|
||
"title": "All Attempted Logons with Tokens Created with Invalid Key Material.",
|
||
"timeContext": {
|
||
"durationMs": 2592000000
|
||
},
|
||
"timeContextFromParameter": "timeframe",
|
||
"queryType": 0,
|
||
"resourceType": "microsoft.operationalinsights/workspaces"
|
||
},
|
||
"customWidth": "70",
|
||
"name": "query - 8"
|
||
},
|
||
{
|
||
"type": 1,
|
||
"content": {
|
||
"json": "This query looks for logon attempts that fail due to the token being used being signed by invalid key material. In normal operations this should not occur, however after rotating key material attackers attempting to mint SAML tokens using old key material will appear in this query. \r\n\r\nPlease note immediately after rotating key material this query will produce a large number false positive results due to legitimate tokens no longer being valid.\r\n\r\n- For more details on SAML token abuse: https://www.cyberark.com/resources/threat-research-blog/golden-saml-newly-discovered-attack-technique-forges-authentication-to-cloud-apps ",
|
||
"style": "info"
|
||
},
|
||
"customWidth": "30",
|
||
"name": "text - 7"
|
||
},
|
||
{
|
||
"type": 3,
|
||
"content": {
|
||
"version": "KqlItem/1.0",
|
||
"query": " (union isfuzzy=true\r\n (\r\n AuditLogs\r\n | where OperationName =~ \"Set federation settings on domain\"\r\n //| where Result =~ \"success\" // commenting out, as it may be interesting to capture failed attempts\r\n | mv-expand TargetResources\r\n | extend modifiedProperties = parse_json(TargetResources).modifiedProperties\r\n | mv-expand modifiedProperties\r\n | extend targetDisplayName = tostring(parse_json(modifiedProperties).displayName)\r\n | mv-expand AdditionalDetails\r\n ), \r\n (\r\n AuditLogs\r\n | where OperationName =~ \"Set domain authentication\"\r\n //| where Result =~ \"success\" // commenting out, as it may be interesting to capture failed attempts\r\n | mv-expand TargetResources\r\n | extend modifiedProperties = parse_json(TargetResources).modifiedProperties\r\n | mv-expand modifiedProperties\r\n | extend targetDisplayName = tostring(parse_json(modifiedProperties).displayName), NewDomainValue=tostring(parse_json(modifiedProperties).newValue)\r\n | where NewDomainValue has \"Federated\"\r\n )\r\n )\r\n | extend UserAgent = iff(AdditionalDetails.key == \"User-Agent\",tostring(AdditionalDetails.value),\"\")\r\n | extend InitiatingUserOrApp = iff(isnotempty(InitiatedBy.user.userPrincipalName),tostring(InitiatedBy.user.userPrincipalName), tostring(InitiatedBy.app.displayName))\r\n | extend InitiatingIpAddress = iff(isnotempty(InitiatedBy.user.ipAddress), tostring(InitiatedBy.user.ipAddress), tostring(InitiatedBy.app.ipAddress))\r\n | project-reorder TimeGenerated, OperationName, InitiatingUserOrApp, AADOperationType, targetDisplayName, Result, InitiatingIpAddress, UserAgent, CorrelationId, TenantId, AADTenantId",
|
||
"size": 0,
|
||
"title": "Modified Domain Federation Trust Settings",
|
||
"timeContext": {
|
||
"durationMs": 2592000000
|
||
},
|
||
"timeContextFromParameter": "timeframe",
|
||
"queryType": 0,
|
||
"resourceType": "microsoft.operationalinsights/workspaces"
|
||
},
|
||
"customWidth": "70",
|
||
"name": "query - 9",
|
||
"styleSettings": {
|
||
"padding": "5"
|
||
}
|
||
},
|
||
{
|
||
"type": 1,
|
||
"content": {
|
||
"json": "This will show when a user or application modifies the federation settings on the domain or Update domain authentication from Managed to Federated. For example, when a new Active Directory Federated Service (ADFS) TrustedRealm object, such as a signing certificate, is added to the domain.\r\nModification to domain federation settings should be rare. Confirm the added or modified target domain/URL is legitimate administrator behavior.\r\n- To understand why an authorized user may update settings for a federated domain in Office 365, Azure, or Intune, see: https://docs.microsoft.com/office365/troubleshoot/active-directory/update-federated-domain-office-365.\r\n- For details on security realms that accept security tokens, see the ADFS Proxy Protocol (MS-ADFSPP) specification: https://docs.microsoft.com/openspecs/windows_protocols/ms-adfspp/e7b9ea73-1980-4318-96a6-da559486664b.\r\n- For further information on AuditLogs please see https://docs.microsoft.com/azure/active-directory/reports-monitoring/reference-audit-activities.",
|
||
"style": "info"
|
||
},
|
||
"customWidth": "30",
|
||
"name": "text - 10"
|
||
}
|
||
]
|
||
},
|
||
"conditionalVisibility": {
|
||
"parameterName": "nav_val",
|
||
"comparison": "isEqualTo",
|
||
"value": "1"
|
||
},
|
||
"name": "Signin Activity"
|
||
},
|
||
{
|
||
"type": 12,
|
||
"content": {
|
||
"version": "NotebookGroup/1.0",
|
||
"groupType": "editable",
|
||
"title": "Suspicious Application Activity",
|
||
"items": [
|
||
{
|
||
"type": 1,
|
||
"content": {
|
||
"json": "## Suspicious Application Activity\r\n------------------------\r\n\r\nThis section hunts for suspicious sign-in events within your Azure AD tenant. It takes TTPs reported by Microsoft, FireEye and the NSA to identify suspicious OAuth or Service Principal Activity. This is focused on highlighting applications or accounts with new permissions, key credentials, and access patterns. \r\n"
|
||
},
|
||
"name": "text - 0"
|
||
},
|
||
{
|
||
"type": 9,
|
||
"content": {
|
||
"version": "KqlParameterItem/1.0",
|
||
"parameters": [
|
||
{
|
||
"id": "46282188-a54e-4ff0-a5eb-9d4ef712d9e9",
|
||
"version": "KqlParameterItem/1.0",
|
||
"name": "timeframe",
|
||
"label": "Hunting Time Frame",
|
||
"type": 4,
|
||
"description": "Used to time scope the subsequent hunting queries",
|
||
"isRequired": true,
|
||
"value": {
|
||
"durationMs": 2592000000
|
||
},
|
||
"typeSettings": {
|
||
"selectableValues": [
|
||
{
|
||
"durationMs": 86400000
|
||
},
|
||
{
|
||
"durationMs": 172800000
|
||
},
|
||
{
|
||
"durationMs": 259200000
|
||
},
|
||
{
|
||
"durationMs": 604800000
|
||
},
|
||
{
|
||
"durationMs": 1209600000
|
||
},
|
||
{
|
||
"durationMs": 2419200000
|
||
},
|
||
{
|
||
"durationMs": 2592000000
|
||
},
|
||
{
|
||
"durationMs": 5184000000
|
||
},
|
||
{
|
||
"durationMs": 7776000000
|
||
}
|
||
],
|
||
"allowCustom": true
|
||
},
|
||
"timeContext": {
|
||
"durationMs": 86400000
|
||
}
|
||
}
|
||
],
|
||
"style": "pills",
|
||
"queryType": 0,
|
||
"resourceType": "microsoft.operationalinsights/workspaces"
|
||
},
|
||
"customWidth": "30",
|
||
"name": "parameters - 1"
|
||
},
|
||
{
|
||
"type": 1,
|
||
"content": {
|
||
"json": "Set the timeframe you wish to hunt in using the dropdown to the right.\r\nNote that using a large timeframe may cause queries to timeout depending on the size of your environment. If you have difficulties try reducing your timeframe.",
|
||
"style": "info"
|
||
},
|
||
"customWidth": "70",
|
||
"name": "text - 2"
|
||
},
|
||
{
|
||
"type": 1,
|
||
"content": {
|
||
"json": "## New Key Credential Added\r\n\r\nThis section looks for Applications or Service Principals where new Key Credentials were added. This has been used by attackers to gain persistent access and elevate privileges. Selecting an application shows additional activity from the application and the user who granted the permissions.\r\n\r\nLook for activity from unexpected users or IP addresses, as well as unexpected activity from applications and service principals with new Key Credentials."
|
||
},
|
||
"name": "text - 13"
|
||
},
|
||
{
|
||
"type": 3,
|
||
"content": {
|
||
"version": "KqlItem/1.0",
|
||
"query": " AuditLogs\r\n | where OperationName has_any (\"Add service principal\", \"Certificates and secrets management\") // captures \"Add service principal\", \"Add service principal credentials\", and \"Update application – Certificates and secrets management\" events\r\n | where Result =~ \"success\"\r\n | mv-expand target = TargetResources \r\n | where tostring(InitiatedBy.user.userPrincipalName) has \"@\" or tostring(InitiatedBy.app.displayName) has \"@\"\r\n | extend targetDisplayName = tostring(TargetResources[0].displayName)\r\n | extend targetId = tostring(TargetResources[0].id)\r\n | extend targetType = tostring(TargetResources[0].type)\r\n | extend keyEvents = TargetResources[0].modifiedProperties\r\n | mv-expand keyEvents\r\n | where keyEvents.displayName =~ \"KeyDescription\"\r\n | extend set1 = parse_json(tostring(keyEvents.newValue))\r\n | extend set2 = parse_json(tostring(keyEvents.oldValue))\r\n | extend diff = set_difference(set1, set2)\r\n | where isnotempty(diff)\r\n | parse diff with * \"KeyIdentifier=\" keyIdentifier:string \",KeyType=\" keyType:string \",KeyUsage=\" keyUsage:string \",DisplayName=\" keyDisplayName:string \"]\" *\r\n | where keyUsage == \"Verify\"\r\n or keyUsage == \"\"\r\n | extend UserAgent = iff(AdditionalDetails[0].key == \"User-Agent\",tostring(AdditionalDetails[0].value),\"\")\r\n | extend InitiatingUserOrApp = iff(isnotempty(InitiatedBy.user.userPrincipalName),tostring(InitiatedBy.user.userPrincipalName), tostring(InitiatedBy.app.displayName))\r\n | extend InitiatingIpAddress = iff(isnotempty(InitiatedBy.user.ipAddress), tostring(InitiatedBy.user.ipAddress), tostring(InitiatedBy.app.ipAddress))\r\n // \r\n // The below line is currently commented out but Azure Sentinel users can modify this query to show only Application or only Service Principal events in their environment\r\n //| where targetType =~ \"Application\" // or targetType =~ \"ServicePrincipal\"\r\n | project-away diff, set1, set2\r\n | project-reorder TimeGenerated, OperationName, InitiatingUserOrApp, InitiatingIpAddress, UserAgent, targetDisplayName, targetId, targetType, keyDisplayName, keyType, keyUsage, keyIdentifier, CorrelationId, TenantId\r\n | extend timestamp = TimeGenerated, AccountCustomEntity = InitiatingUserOrApp, IPCustomEntity = InitiatingIpAddress",
|
||
"size": 0,
|
||
"title": "Applications or Service Principals with new Key Credentials Added.",
|
||
"timeContext": {
|
||
"durationMs": 2592000000
|
||
},
|
||
"timeContextFromParameter": "timeframe",
|
||
"exportedParameters": [
|
||
{
|
||
"fieldName": "AppId",
|
||
"parameterName": "app_id_1",
|
||
"parameterType": 1
|
||
},
|
||
{
|
||
"fieldName": "InitiatingUser",
|
||
"parameterName": "init_user",
|
||
"parameterType": 1
|
||
}
|
||
],
|
||
"queryType": 0,
|
||
"resourceType": "microsoft.operationalinsights/workspaces"
|
||
},
|
||
"name": "query - 3"
|
||
},
|
||
{
|
||
"type": 3,
|
||
"content": {
|
||
"version": "KqlItem/1.0",
|
||
"query": "AADServicePrincipalSignInLogs\r\n| where TimeGenerated > ago(30d)\r\n| where ResourceDisplayName == \"Microsoft Graph\"\r\n| where ServicePrincipalId == \"{app_id_1}\"\r\n| summarize count() by bin(TimeGenerated, 1h)\r\n| render timechart ",
|
||
"size": 0,
|
||
"title": "Logons by {app_id_1} in the last 30 days",
|
||
"queryType": 0,
|
||
"resourceType": "microsoft.operationalinsights/workspaces"
|
||
},
|
||
"customWidth": "50",
|
||
"conditionalVisibility": {
|
||
"parameterName": "app_id_1",
|
||
"comparison": "isNotEqualTo"
|
||
},
|
||
"name": "query - 14"
|
||
},
|
||
{
|
||
"type": 3,
|
||
"content": {
|
||
"version": "KqlItem/1.0",
|
||
"query": "AuditLogs\r\n| where Result =~ \"success\"\r\n| where tostring(InitiatedBy.user.userPrincipalName) =~ '{init_user}'",
|
||
"size": 0,
|
||
"title": "All Audit Activity by {inti_user}",
|
||
"timeContext": {
|
||
"durationMs": 2592000000
|
||
},
|
||
"timeContextFromParameter": "timeframe",
|
||
"queryType": 0,
|
||
"resourceType": "microsoft.operationalinsights/workspaces"
|
||
},
|
||
"customWidth": "50",
|
||
"conditionalVisibility": {
|
||
"parameterName": "init_user",
|
||
"comparison": "isNotEqualTo"
|
||
},
|
||
"name": "query - 15"
|
||
},
|
||
{
|
||
"type": 1,
|
||
"content": {
|
||
"json": "## Service Principals Given Additional Privileges\r\n\r\nOnce persistence has been gained via a Service Principal it can be given additional privileges in order to elevate the level of an attackers access. Look for Service Principals being added to high privilege groups.\r\nSelect a Service Principal to get more detail on the Service Principal and the group it was added to.\r\n"
|
||
},
|
||
"name": "text - 16"
|
||
},
|
||
{
|
||
"type": 3,
|
||
"content": {
|
||
"version": "KqlItem/1.0",
|
||
"query": "AuditLogs\r\n| where Result == \"success\"\r\n| where OperationName == \"Add member to group\"\r\n| where tostring(InitiatedBy.user.userPrincipalName) has \"@\" or tostring(InitiatedBy.app.displayName) has \"@\"\r\n| extend Type = tostring(TargetResources[0].type)\r\n| where Type =~ \"ServicePrincipal\"\r\n| extend UserAgent = tostring(AdditionalDetails[0].value)\r\n| extend UserIPAddress = tostring(parse_json(tostring(InitiatedBy.user)).ipAddress)\r\n| extend InitiatingUser = tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName)\r\n| extend TargetUserId = tostring(TargetResources[0].id)\r\n| extend TargetUserName = tostring(TargetResources[0].displayName)\r\n| extend props = parse_json(tostring(TargetResources[0].modifiedProperties))\r\n| mv-expand props\r\n| extend PropName = tostring(props.displayName)\r\n| where PropName == \"Group.DisplayName\"\r\n| extend GroupName = tostring(parse_json(tostring(props.newValue)))\r\n| project-away props\r\n| project-reorder TimeGenerated, InitiatingUser, UserIPAddress, UserAgent, OperationName, TargetUserName, TargetUserId, GroupName",
|
||
"size": 0,
|
||
"title": "ServicePrincipals Added to Groups.",
|
||
"noDataMessage": "No Service Principals Added to Groups in Timeframe",
|
||
"timeContext": {
|
||
"durationMs": 2592000000
|
||
},
|
||
"timeContextFromParameter": "timeframe",
|
||
"exportedParameters": [
|
||
{
|
||
"fieldName": "TargetUserId",
|
||
"parameterName": "target_id",
|
||
"parameterType": 1
|
||
},
|
||
{
|
||
"fieldName": "GroupName",
|
||
"parameterName": "group",
|
||
"parameterType": 1
|
||
}
|
||
],
|
||
"queryType": 0,
|
||
"resourceType": "microsoft.operationalinsights/workspaces"
|
||
},
|
||
"name": "query - 4"
|
||
},
|
||
{
|
||
"type": 3,
|
||
"content": {
|
||
"version": "KqlItem/1.0",
|
||
"query": "AuditLogs\r\n| where * contains 'target_id'",
|
||
"size": 0,
|
||
"title": "Audit Activity for {target_id}",
|
||
"timeContext": {
|
||
"durationMs": 2592000000
|
||
},
|
||
"timeContextFromParameter": "timeframe",
|
||
"queryType": 0,
|
||
"resourceType": "microsoft.operationalinsights/workspaces"
|
||
},
|
||
"customWidth": "50",
|
||
"conditionalVisibility": {
|
||
"parameterName": "target_id",
|
||
"comparison": "isNotEqualTo"
|
||
},
|
||
"name": "query - 17"
|
||
},
|
||
{
|
||
"type": 3,
|
||
"content": {
|
||
"version": "KqlItem/1.0",
|
||
"query": "AuditLogs\r\n| where * contains \"{group}\"",
|
||
"size": 0,
|
||
"title": "Audit Activity involving {group}",
|
||
"timeContext": {
|
||
"durationMs": 2592000000
|
||
},
|
||
"timeContextFromParameter": "timeframe",
|
||
"queryType": 0,
|
||
"resourceType": "microsoft.operationalinsights/workspaces"
|
||
},
|
||
"customWidth": "50",
|
||
"conditionalVisibility": {
|
||
"parameterName": "group",
|
||
"comparison": "isNotEqualTo"
|
||
},
|
||
"name": "query - 18"
|
||
},
|
||
{
|
||
"type": 1,
|
||
"content": {
|
||
"json": "## Azure AD PowerShell Abuse\r\n\r\nAttackers have been observed abusing Azure AD PowerShell to access user data. This query looks for user or application signs in using Microsoft Entra ID PowerShell to access non-Active Directory resources, such as the Microsoft Graph.\r\n\r\nLook for unexpected patterns of activity, or users who would not normally use Azure AD Powershell."
|
||
},
|
||
"name": "text - 19"
|
||
},
|
||
{
|
||
"type": 3,
|
||
"content": {
|
||
"version": "KqlItem/1.0",
|
||
"query": "SigninLogs\r\n | where AppId =~ \"1b730954-1685-4b74-9bfd-dac224a7b894\" // AppDisplayName IS Azure Active Directory PowerShell\r\n | where TokenIssuerType =~ \"AzureAD\"\r\n | where ResourceIdentity != \"00000002-0000-0000-c000-000000000000\" // ResourceDisplayName IS NOT Windows Azure Active Directory\r\n | where Status.errorCode == 0 // Success\r\n | project-reorder IPAddress, UserAgent, ResourceDisplayName, UserDisplayName, UserId, UserPrincipalName\r\n | order by TimeGenerated desc",
|
||
"size": 0,
|
||
"title": "Suspicious Azure AD PowerShell Activity",
|
||
"timeContext": {
|
||
"durationMs": 2592000000
|
||
},
|
||
"timeContextFromParameter": "timeframe",
|
||
"queryType": 0,
|
||
"resourceType": "microsoft.operationalinsights/workspaces"
|
||
},
|
||
"name": "query - 5"
|
||
},
|
||
{
|
||
"type": 1,
|
||
"content": {
|
||
"json": "## Mail Read Access Granted\r\n\r\nThis section looks for Applications or Service Principals granted access to mailbox. Selecting an application shows additional activity from the application and the user who granted the permissions.\r\n\r\n\r\nLook for anomalous privilege granting to unexpected applications. \r\n\r\nFurther below is a timeseries analysis of mailbox access events, use this to identify suspicious access patterns, particularly large spikes to access volumes."
|
||
},
|
||
"name": "text - 12"
|
||
},
|
||
{
|
||
"type": 3,
|
||
"content": {
|
||
"version": "KqlItem/1.0",
|
||
"query": "\tAuditLogs\r\n\t| where Category == \"ApplicationManagement\"\r\n\t| where ActivityDisplayName == \"Add delegated permission grant\"\r\n\t| where Result =~ \"success\"\r\n\t| where tostring(InitiatedBy.user.userPrincipalName) has \"@\" or tostring(InitiatedBy.app.displayName) has \"@\"\r\n\t| extend props = parse_json(tostring(TargetResources[0].modifiedProperties))\r\n\t| mv-expand props\r\n\t| extend UserAgent = tostring(AdditionalDetails[0].value)\r\n\t| extend InitiatingUser = tostring(parse_json(tostring(InitiatedBy.user)).userPrincipalName)\r\n\t| extend UserIPAddress = tostring(parse_json(tostring(InitiatedBy.user)).ipAddress)\r\n\t| extend DisplayName = tostring(props.displayName)\r\n\t| extend Permissions = tostring(parse_json(tostring(props.newValue)))\r\n\t| where Permissions contains \"Mail.Read\"\r\n\t| extend PermissionsAddedTo = tostring(TargetResources[0].displayName)\r\n\t| extend Type = tostring(TargetResources[0].type)\r\n\t| project-away props\r\n\t| join kind=leftouter(\r\n\tAuditLogs\r\n\t| where ActivityDisplayName == \"Consent to application\"\r\n\t| extend AppName = tostring(TargetResources[0].displayName)\r\n\t| extend AppId = tostring(TargetResources[0].id)\r\n\t| project AppName, AppId, CorrelationId) on CorrelationId\r\n| project-reorder TimeGenerated, OperationName, InitiatingUser, UserIPAddress, UserAgent, PermissionsAddedTo, Permissions, AppName, AppId, CorrelationId",
|
||
"size": 0,
|
||
"title": "Applications or Service Principals Granted Mail Read Access.",
|
||
"timeContext": {
|
||
"durationMs": 0
|
||
},
|
||
"timeContextFromParameter": "timeframe",
|
||
"exportedParameters": [
|
||
{
|
||
"fieldName": "AppId",
|
||
"parameterName": "app_id",
|
||
"parameterType": 1
|
||
},
|
||
{
|
||
"fieldName": "AppName",
|
||
"parameterName": "app_name",
|
||
"parameterType": 1
|
||
},
|
||
{
|
||
"fieldName": "InitiatingUser",
|
||
"parameterName": "user",
|
||
"parameterType": 1
|
||
}
|
||
],
|
||
"queryType": 0,
|
||
"resourceType": "microsoft.operationalinsights/workspaces"
|
||
},
|
||
"name": "query - 6"
|
||
},
|
||
{
|
||
"type": 3,
|
||
"content": {
|
||
"version": "KqlItem/1.0",
|
||
"query": "AuditLogs\r\n| where * contains '{app_id}'",
|
||
"size": 0,
|
||
"title": "All Activity for {app_name}",
|
||
"timeContext": {
|
||
"durationMs": 2592000000
|
||
},
|
||
"timeContextFromParameter": "timeframe",
|
||
"queryType": 0,
|
||
"resourceType": "microsoft.operationalinsights/workspaces"
|
||
},
|
||
"customWidth": "50",
|
||
"conditionalVisibility": {
|
||
"parameterName": "app_id",
|
||
"comparison": "isNotEqualTo"
|
||
},
|
||
"name": "query - 10"
|
||
},
|
||
{
|
||
"type": 3,
|
||
"content": {
|
||
"version": "KqlItem/1.0",
|
||
"query": "AuditLogs\r\n| where Result =~ \"success\"\r\n| where tostring(InitiatedBy.user.userPrincipalName) =~ '{user}'",
|
||
"size": 0,
|
||
"title": "All Audit Activity by {user}",
|
||
"timeContext": {
|
||
"durationMs": 2592000000
|
||
},
|
||
"timeContextFromParameter": "timeframe",
|
||
"queryType": 0,
|
||
"resourceType": "microsoft.operationalinsights/workspaces"
|
||
},
|
||
"customWidth": "50",
|
||
"conditionalVisibility": {
|
||
"parameterName": "user",
|
||
"comparison": "isNotEqualTo"
|
||
},
|
||
"name": "query - 11"
|
||
},
|
||
{
|
||
"type": 3,
|
||
"content": {
|
||
"version": "KqlItem/1.0",
|
||
"query": "let starttime = 14d;\r\n let endtime = 1d;\r\n let timeframe = 1h;\r\n let scorethreshold = 1.5;\r\n let percentthreshold = 5;\r\n // Preparing the time series data aggregated on BytesTransferredOut column in the form of multi-value array so that it can be used with time series anomaly function.\r\n let TimeSeriesData=\r\n OfficeActivity \r\n | where TimeGenerated between (startofday(ago(starttime))..startofday(ago(endtime)))\r\n | where OfficeWorkload=~ \"Exchange\" and Operation =~ \"MailItemsAccessed\" and ResultStatus =~ \"Succeeded\"\r\n | project TimeGenerated, Operation, MailboxOwnerUPN \r\n | make-series Total=count() on TimeGenerated from startofday(ago(starttime)) to startofday(ago(endtime)) step timeframe;\r\n // Use the time series data prepared in previous step with time series aomaly function to generate baseline pattern and flag the outlier based on scorethreshold value.\r\n let TimeSeriesAlerts = TimeSeriesData\r\n | extend (anomalies, score, baseline) = series_decompose_anomalies(Total, scorethreshold, -1, 'linefit')\r\n | mv-expand Total to typeof(double), TimeGenerated to typeof(datetime), anomalies to typeof(double), score to typeof(double), baseline to typeof(long)\r\n | where anomalies > 0\r\n | project TimeGenerated, Total, baseline, anomalies, score;\r\n // Joining the flagged outlier from the previous step with the original dataset to present contextual information during the anomalyhour to analysts to conduct investigation or informed decistions.\r\n TimeSeriesAlerts\r\n // Join against base logs since specified timeframe to retrive records associated with the hour of anomoly\r\n | join kind=inner ( \r\n OfficeActivity \r\n | where TimeGenerated between (startofday(ago(starttime))..startofday(ago(endtime)))\r\n | where OfficeWorkload=~ \"Exchange\" and Operation =~ \"MailItemsAccessed\" and ResultStatus =~ \"Succeeded\"\r\n | summarize Count=count(), IPAdresses = make_set(Client_IPAddress), ClientInfoStrings= make_set(ClientInfoString) by bin(TimeGenerated,1h), MailboxOwnerUPN, Logon_Type, TenantId, UserType\r\n | order by Count desc \r\n ) on TimeGenerated\r\n | extend PercentofTotal = round(Count/Total, 2) * 100 \r\n | where PercentofTotal > percentthreshold // Filter Users with count of less than 5 percent of TotalEvents per Hour to remove FPs/ users with very low count of MailItemsAccessed events\r\n | order by PercentofTotal desc ",
|
||
"size": 0,
|
||
"title": "Mail Item Access Events TimeSeries in Last 14 days",
|
||
"timeBrushParameterName": "graph_time",
|
||
"queryType": 0,
|
||
"resourceType": "microsoft.operationalinsights/workspaces",
|
||
"visualization": "timechart"
|
||
},
|
||
"name": "query - 7"
|
||
},
|
||
{
|
||
"type": 1,
|
||
"content": {
|
||
"json": "Drag across the graph above to set a timeframe from which to get additional details.<br>\r\nCurrent timeframe: {graph_time}",
|
||
"style": "info"
|
||
},
|
||
"name": "text - 9"
|
||
},
|
||
{
|
||
"type": 3,
|
||
"content": {
|
||
"version": "KqlItem/1.0",
|
||
"query": " OfficeActivity \r\n | where OfficeWorkload=~ \"Exchange\" and Operation =~ \"MailItemsAccessed\" and ResultStatus =~ \"Succeeded\"\r\n | summarize Count=count(), IPAdresses = make_set(Client_IPAddress), ClientInfoStrings= make_set(ClientInfoString) by bin(TimeGenerated,1h), MailboxOwnerUPN, Logon_Type, TenantId, UserType\r\n | order by Count desc \r\n | take 20\r\n",
|
||
"size": 0,
|
||
"title": "Top 20 Mailboxes Accessed in Selected Timeframe",
|
||
"timeContext": {
|
||
"durationMs": 0
|
||
},
|
||
"timeContextFromParameter": "graph_time",
|
||
"queryType": 0,
|
||
"resourceType": "microsoft.operationalinsights/workspaces"
|
||
},
|
||
"conditionalVisibility": {
|
||
"parameterName": "graph_time",
|
||
"comparison": "isNotEqualTo"
|
||
},
|
||
"name": "query - 8"
|
||
}
|
||
]
|
||
},
|
||
"conditionalVisibility": {
|
||
"parameterName": "nav_val",
|
||
"comparison": "isEqualTo",
|
||
"value": "2"
|
||
},
|
||
"name": "group - 3"
|
||
},
|
||
{
|
||
"type": 12,
|
||
"content": {
|
||
"version": "NotebookGroup/1.0",
|
||
"groupType": "editable",
|
||
"title": "Suspicious Lateral Movement",
|
||
"items": [
|
||
{
|
||
"type": 1,
|
||
"content": {
|
||
"json": "## Lateral Movement\r\n\r\nOnce in the environment an attacker will laterally move within the environment to access resources they require. This is a common element of many attacks and has also been observed with a the SolarWinds supply chain attacks. These queries look for suspicious RDP connections that may indicate lateral movement taking place."
|
||
},
|
||
"name": "text - 0"
|
||
},
|
||
{
|
||
"type": 9,
|
||
"content": {
|
||
"version": "KqlParameterItem/1.0",
|
||
"parameters": [
|
||
{
|
||
"id": "9825da9b-9932-4b35-b6dc-26aa29a58913",
|
||
"version": "KqlParameterItem/1.0",
|
||
"name": "timeframe",
|
||
"label": "Hunting Time Frame",
|
||
"type": 4,
|
||
"value": {
|
||
"durationMs": 2592000000
|
||
},
|
||
"typeSettings": {
|
||
"selectableValues": [
|
||
{
|
||
"durationMs": 86400000
|
||
},
|
||
{
|
||
"durationMs": 172800000
|
||
},
|
||
{
|
||
"durationMs": 259200000
|
||
},
|
||
{
|
||
"durationMs": 604800000
|
||
},
|
||
{
|
||
"durationMs": 1209600000
|
||
},
|
||
{
|
||
"durationMs": 2419200000
|
||
},
|
||
{
|
||
"durationMs": 2592000000
|
||
},
|
||
{
|
||
"durationMs": 5184000000
|
||
},
|
||
{
|
||
"durationMs": 7776000000
|
||
}
|
||
],
|
||
"allowCustom": true
|
||
},
|
||
"timeContext": {
|
||
"durationMs": 86400000
|
||
}
|
||
}
|
||
],
|
||
"style": "pills",
|
||
"queryType": 0,
|
||
"resourceType": "microsoft.operationalinsights/workspaces"
|
||
},
|
||
"customWidth": "30",
|
||
"name": "parameters - 2"
|
||
},
|
||
{
|
||
"type": 1,
|
||
"content": {
|
||
"json": "Set the timeframe you wish to hunt in using the dropdown to the right.\r\nNote that using a large timeframe may cause queries to timeout depending on the size of your environment. If you have difficulties try reducing your timeframe.\r\n\r\n",
|
||
"style": "info"
|
||
},
|
||
"customWidth": "70",
|
||
"name": "text - 3"
|
||
},
|
||
{
|
||
"type": 3,
|
||
"content": {
|
||
"version": "KqlItem/1.0",
|
||
"query": " BehaviorAnalytics\r\n | where ActivityType == \"LogOn\"\r\n | where ActionType == \"RemoteInteractiveLogon\"\r\n | where ActivityInsights has \"True\"\r\n | project TimeGenerated, UserName, UserPrincipalName, UsersInsights, ActivityType, ActionType,ActivityInsights ,SourceIPAddress, SourceIPLocation, SourceDevice, DevicesInsights\r\n",
|
||
"size": 0,
|
||
"title": "Anomalous RDP Activity",
|
||
"timeContext": {
|
||
"durationMs": 259200000
|
||
},
|
||
"timeContextFromParameter": "timeframe",
|
||
"queryType": 0,
|
||
"resourceType": "microsoft.operationalinsights/workspaces"
|
||
},
|
||
"name": "query - 1"
|
||
},
|
||
{
|
||
"type": 3,
|
||
"content": {
|
||
"version": "KqlItem/1.0",
|
||
"query": "let starttime = 14d;\r\n let endtime = 1d;\r\n SecurityEvent\r\n | where TimeGenerated >= ago(endtime) \r\n | where EventID == 4624 and LogonType == 10\r\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), ConnectionCount = count() \r\n by Account = tolower(Account), Computer = toupper(Computer), IpAddress, AccountType, Activity, LogonTypeName, ProcessName\r\n // use left anti to exclude anything from the previous 14 days that is not rare\r\n | join kind=leftanti (\r\n SecurityEvent\r\n | where TimeGenerated between (ago(starttime) .. ago(endtime))\r\n | where EventID == 4624\r\n | summarize by Computer = toupper(Computer), IpAddress, Account = tolower(Account)\r\n ) on Account, Computer\r\n | summarize StartTime = min(StartTime), EndTime = max(EndTime), ConnectionCount = sum(ConnectionCount) \r\n by Account, Computer, IpAddress, AccountType, Activity, LogonTypeName, ProcessName",
|
||
"size": 0,
|
||
"title": "Rare RDP Connections",
|
||
"queryType": 0,
|
||
"resourceType": "microsoft.operationalinsights/workspaces"
|
||
},
|
||
"name": "Rare RDP Connections"
|
||
},
|
||
{
|
||
"type": 3,
|
||
"content": {
|
||
"version": "KqlItem/1.0",
|
||
"query": "let endtime = 1d;\r\n let starttime = 8d;\r\n let threshold = 2.0;\r\n SecurityEvent\r\n | where TimeGenerated >= ago(endtime) \r\n | where EventID == 4624 and LogonType == 10\r\n | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated), ComputerCountToday = dcount(Computer), ComputerSet = makeset(Computer), ProcessSet = makeset(ProcessName) \r\n by Account, IpAddress, AccountType, Activity, LogonTypeName\r\n | join kind=inner (\r\n SecurityEvent\r\n | where TimeGenerated >= ago(starttime) and TimeGenerated < ago(endtime) \r\n | where EventID == 4624 and LogonType == 10\r\n | summarize ComputerCountPrev7Days = dcount(Computer) by Account, IpAddress\r\n ) on Account, IpAddress\r\n | extend Ratio = ComputerCountToday/(ComputerCountPrev7Days*1.0)\r\n // Where the ratio of today to previous 7 days is more than double.\r\n | where Ratio > threshold\r\n | project StartTimeUtc, EndTimeUtc, Account, IpAddress, ComputerSet, ComputerCountToday, ComputerCountPrev7Days, Ratio, AccountType, Activity, LogonTypeName, ProcessSet",
|
||
"size": 0,
|
||
"title": "Multiple RDP Connections from a Single System",
|
||
"queryType": 0,
|
||
"resourceType": "microsoft.operationalinsights/workspaces"
|
||
},
|
||
"name": "query - 5"
|
||
},
|
||
{
|
||
"type": 3,
|
||
"content": {
|
||
"version": "KqlItem/1.0",
|
||
"query": "let endtime = 1d;\r\n let starttime = 8d;\r\n // The threshold below excludes matching on RDP connection computer counts of 5 or more by a given account and IP in a given day. Change the threshold as needed.\r\n let threshold = 5;\r\n SecurityEvent\r\n | where TimeGenerated >= ago(endtime) \r\n | where EventID == 4624 and LogonType == 10\r\n // Labeling the first RDP connection time, computer and ip\r\n | extend FirstHop = TimeGenerated, FirstComputer = toupper(Computer), FirstIPAddress = IpAddress, Account = tolower(Account) \r\n | join kind=inner (\r\n SecurityEvent\r\n | where TimeGenerated >= ago(endtime) \r\n | where EventID == 4624 and LogonType == 10\r\n // Labeling the second RDP connection time, computer and ip\r\n | extend SecondHop = TimeGenerated, SecondComputer = toupper(Computer), SecondIPAddress = IpAddress, Account = tolower(Account)\r\n ) on Account\r\n // Make sure that the first connection is after the second connection --> SecondHop > FirstHop\r\n // Then identify only RDP to another computer from within the first RDP connection by only choosing matches where the Computer names do not match --> FirstComputer != SecondComputer\r\n // Then make sure the IPAddresses do not match by excluding connections from the same computers with first hop RDP connections to multiple computers --> FirstIPAddress != SecondIPAddress\r\n | where FirstComputer != SecondComputer and FirstIPAddress != SecondIPAddress and SecondHop > FirstHop\r\n // where the second hop occurs within 30 minutes of the first hop\r\n | where SecondHop <= FirstHop+30m\r\n | distinct Account, FirstHop, FirstComputer, FirstIPAddress, SecondHop, SecondComputer, SecondIPAddress, AccountType, Activity, LogonTypeName, ProcessName\r\n // use left anti to exclude anything from the previous 7 days where the Account and IP has connected 5 or more computers.\r\n | join kind=leftanti (\r\n SecurityEvent\r\n | where TimeGenerated >= ago(starttime) and TimeGenerated < ago(endtime) \r\n | where EventID == 4624 and LogonType == 10\r\n | summarize makeset(Computer), ComputerCount = dcount(Computer) by bin(TimeGenerated, 1d), Account = tolower(Account), IpAddress\r\n // Connection count to computer by same account and IP to exclude counts of 5 or more on a given day\r\n | where ComputerCount >= threshold\r\n | mvexpand set_Computer\r\n | extend Computer = toupper(set_Computer)\r\n ) on Account, $left.SecondComputer == $right.Computer, $left.SecondIPAddress == $right.IpAddress\r\n | summarize FirstHopFirstSeen = min(FirstHop), FirstHopLastSeen = max(FirstHop) by Account, FirstComputer, FirstIPAddress, SecondHop, SecondComputer, \r\n SecondIPAddress, AccountType, Activity, LogonTypeName, ProcessName",
|
||
"size": 0,
|
||
"title": "Suspected RDP Nesting",
|
||
"queryType": 0,
|
||
"resourceType": "microsoft.operationalinsights/workspaces"
|
||
},
|
||
"name": "query - 6"
|
||
},
|
||
{
|
||
"type": 3,
|
||
"content": {
|
||
"version": "KqlItem/1.0",
|
||
"query": "SecurityAlert\r\n| extend ThreatName = tostring(parse_json(ExtendedProperties).ThreatName)\r\n| where isnotempty(ThreatName)\r\n| where ThreatName =~ \"Behavior:Win32/WmicRemote.A\"\r\n| summarize min(TimeGenerated) by CompromisedEntity",
|
||
"size": 0,
|
||
"title": "Hosts with Defender Alerts to WMIC Lateral Movement",
|
||
"timeContext": {
|
||
"durationMs": 259200000
|
||
},
|
||
"timeContextFromParameter": "timeframe",
|
||
"queryType": 0,
|
||
"resourceType": "microsoft.operationalinsights/workspaces"
|
||
},
|
||
"customWidth": "50",
|
||
"name": "query - 7"
|
||
},
|
||
{
|
||
"type": 3,
|
||
"content": {
|
||
"version": "KqlItem/1.0",
|
||
"query": "SecurityAlert\r\n| where Description contains \"lateral movement\"\r\n| where DisplayName !has \"malware was prevented\" and DisplayName !has \"malware was blocked\"",
|
||
"size": 0,
|
||
"title": "All Lateral Movement Based Alerts",
|
||
"timeContext": {
|
||
"durationMs": 0
|
||
},
|
||
"timeContextFromParameter": "timeframe",
|
||
"queryType": 0,
|
||
"resourceType": "microsoft.operationalinsights/workspaces"
|
||
},
|
||
"customWidth": "50",
|
||
"name": "query - 8"
|
||
}
|
||
]
|
||
},
|
||
"conditionalVisibility": {
|
||
"parameterName": "nav_val",
|
||
"comparison": "isEqualTo",
|
||
"value": "3"
|
||
},
|
||
"name": "group - 4"
|
||
},
|
||
{
|
||
"type": 12,
|
||
"content": {
|
||
"version": "NotebookGroup/1.0",
|
||
"groupType": "editable",
|
||
"title": "Suspicious Host Activity",
|
||
"items": [
|
||
{
|
||
"type": 1,
|
||
"content": {
|
||
"json": "## Suspicious Host Activity\r\nThe FireEye and SolarWinds blogs on recent attacks provide a range of host-based indicators to hunt with. Whilst Microsoft's Defender products have automated detections for this activity you may want to manually hunt across hosts where these products are not installed. The following queries use host data and Microsoft Defender for Endpoint data to highlight suspicious activity.\r\n"
|
||
},
|
||
"name": "text - 0"
|
||
},
|
||
{
|
||
"type": 9,
|
||
"content": {
|
||
"version": "KqlParameterItem/1.0",
|
||
"parameters": [
|
||
{
|
||
"id": "064d84aa-3004-4950-af39-59e04c0c4c68",
|
||
"version": "KqlParameterItem/1.0",
|
||
"name": "timeframe",
|
||
"label": "Hunting Time Frame",
|
||
"type": 4,
|
||
"isRequired": true,
|
||
"value": {
|
||
"durationMs": 2592000000
|
||
},
|
||
"typeSettings": {
|
||
"selectableValues": [
|
||
{
|
||
"durationMs": 86400000
|
||
},
|
||
{
|
||
"durationMs": 172800000
|
||
},
|
||
{
|
||
"durationMs": 259200000
|
||
},
|
||
{
|
||
"durationMs": 604800000
|
||
},
|
||
{
|
||
"durationMs": 1209600000
|
||
},
|
||
{
|
||
"durationMs": 2419200000
|
||
},
|
||
{
|
||
"durationMs": 2592000000
|
||
},
|
||
{
|
||
"durationMs": 5184000000
|
||
},
|
||
{
|
||
"durationMs": 7776000000
|
||
}
|
||
],
|
||
"allowCustom": true
|
||
},
|
||
"timeContext": {
|
||
"durationMs": 86400000
|
||
}
|
||
}
|
||
],
|
||
"style": "pills",
|
||
"queryType": 0,
|
||
"resourceType": "microsoft.operationalinsights/workspaces"
|
||
},
|
||
"customWidth": "30",
|
||
"name": "parameters - 1"
|
||
},
|
||
{
|
||
"type": 1,
|
||
"content": {
|
||
"json": "Set the timeframe you wish to hunt in using the dropdown to the right.\r\nNote that using a large timeframe may cause queries to timeout depending on the size of your environment. If you have difficulties try reducing your timeframe.",
|
||
"style": "info"
|
||
},
|
||
"customWidth": "70",
|
||
"name": "text - 2"
|
||
},
|
||
{
|
||
"type": 3,
|
||
"content": {
|
||
"version": "KqlItem/1.0",
|
||
"query": "(union isfuzzy=true \r\n ( \r\n SecurityEvent \r\n | where EventID == '4688' \r\n | where NewProcessName has 'SolarWinds' \r\n | extend MachineName = Computer , Process = NewProcessName \r\n ), \r\n ( \r\n DeviceProcessEvents \r\n | where InitiatingProcessFolderPath has 'SolarWinds' \r\n | extend MachineName = DeviceName , Process = InitiatingProcessFolderPath \r\n ) \r\n ) \r\n | summarize StartTime = min(TimeGenerated), EndTime = max(TimeGenerated), make_set(Process) by MachineName",
|
||
"size": 0,
|
||
"title": "Hosts with SolarWinds installed",
|
||
"timeContext": {
|
||
"durationMs": 2592000000
|
||
},
|
||
"timeContextFromParameter": "timeframe",
|
||
"queryType": 0,
|
||
"resourceType": "microsoft.operationalinsights/workspaces"
|
||
},
|
||
"customWidth": "50",
|
||
"name": "query - 3"
|
||
},
|
||
{
|
||
"type": 3,
|
||
"content": {
|
||
"version": "KqlItem/1.0",
|
||
"query": "SecurityAlert\r\n| where ProviderName in (\"MDATP\", \"Azure Security Center\")\r\n| extend ThreatName_ = tostring(parse_json(ExtendedProperties).ThreatName)\r\n| where ThreatName_ contains \"Solorigate\"\r\n| summarize min(TimeGenerated) by CompromisedEntity",
|
||
"size": 0,
|
||
"title": "Hosts with Defender Alerts for Compromised SolarWinds Binaries",
|
||
"timeContext": {
|
||
"durationMs": 2592000000
|
||
},
|
||
"timeContextFromParameter": "timeframe",
|
||
"queryType": 0,
|
||
"resourceType": "microsoft.operationalinsights/workspaces"
|
||
},
|
||
"customWidth": "50",
|
||
"name": "query - 4"
|
||
},
|
||
{
|
||
"type": 3,
|
||
"content": {
|
||
"version": "KqlItem/1.0",
|
||
"query": " DeviceEvents\r\n | where ActionType contains \"ExploitGuardNonMicrosoftSignedBlocked\"\r\n | where InitiatingProcessFileName contains \"svchost.exe\" and FileName contains \"NetSetupSvc.dll\"",
|
||
"size": 0,
|
||
"title": "Exploit Guard Events for Known Bad DLL",
|
||
"timeContext": {
|
||
"durationMs": 2592000000
|
||
},
|
||
"timeContextFromParameter": "timeframe",
|
||
"queryType": 0,
|
||
"resourceType": "microsoft.operationalinsights/workspaces"
|
||
},
|
||
"customWidth": "50",
|
||
"name": "query - 6"
|
||
},
|
||
{
|
||
"type": 3,
|
||
"content": {
|
||
"version": "KqlItem/1.0",
|
||
"query": " let SunburstMD5=dynamic([\"b91ce2fa41029f6955bff20079468448\",\"02af7cec58b9a5da1c542b5a32151ba1\",\"2c4a910a1299cdae2a4e55988a2f102e\",\"846e27a652a5e1bfbd0ddd38a16dc865\",\"4f2eb62fa529c0283b28d05ddd311fae\"]);\r\n let SupernovaMD5=\"56ceb6d0011d87b6e4d7023d7ef85676\";\r\n DeviceFileEvents\r\n | where MD5 in(SunburstMD5) or MD5 in(SupernovaMD5)",
|
||
"size": 0,
|
||
"title": "Known Mallicious Hash Detections",
|
||
"timeContext": {
|
||
"durationMs": 2592000000
|
||
},
|
||
"timeContextFromParameter": "timeframe",
|
||
"queryType": 0,
|
||
"resourceType": "microsoft.operationalinsights/workspaces"
|
||
},
|
||
"customWidth": "50",
|
||
"name": "query - 7"
|
||
},
|
||
{
|
||
"type": 3,
|
||
"content": {
|
||
"version": "KqlItem/1.0",
|
||
"query": "(union isfuzzy=true\r\n (Event\r\n | where Source == \"Microsoft-Windows-Sysmon\"\r\n | where EventID in (17,18)\r\n | extend EvData = parse_xml(EventData)\r\n | extend EventDetail = EvData.DataItem.EventData.Data\r\n | extend NamedPipe = EventDetail.[5].[\"#text\"]\r\n | extend ProcessDetail = EventDetail.[6].[\"#text\"]\r\n | where NamedPipe contains '583da945-62af-10e8-4902-a8f205c72b2e'\r\n | extend Account = UserName\r\n | project-away EventDetail, EvData\r\n ),\r\n (\r\n SecurityEvent\r\n | where EventID == '5145'\r\n | where AccessList has '%%4418' // presence of CreatePipeInstance value \r\n | where RelativeTargetName contains '583da945-62af-10e8-4902-a8f205c72b2e'\r\n )\r\n )",
|
||
"size": 0,
|
||
"title": "Known Solorigate Named Pipe Detected",
|
||
"timeContext": {
|
||
"durationMs": 2592000000
|
||
},
|
||
"timeContextFromParameter": "timeframe",
|
||
"queryType": 0,
|
||
"resourceType": "microsoft.operationalinsights/workspaces"
|
||
},
|
||
"customWidth": "50",
|
||
"name": "query - 13"
|
||
},
|
||
{
|
||
"type": 3,
|
||
"content": {
|
||
"version": "KqlItem/1.0",
|
||
"query": "(union isfuzzy=true \r\n(SecurityEvents\r\n| where FilePath =~ \"c:\\\\windows\\\\syswow64\\\\netsetupsvc.dll\" or NewProcessName =~ \"c:\\\\windows\\\\syswow64\\\\netsetupsvc.dll\"),\r\n(DeviceProcessEvents\r\n| where FolderPath =~ \"c:\\\\windows\\\\syswow64\\\\netsetupsvc.dll\"),\r\n(DeviceFileEvents\r\n| where FolderPath =~ \"c:\\\\windows\\\\syswow64\\\\netsetupsvc.dll\"))",
|
||
"size": 0,
|
||
"title": "Hosts Events for Suspicious netsetupsvc.dll Process",
|
||
"timeContext": {
|
||
"durationMs": 2592000000
|
||
},
|
||
"timeContextFromParameter": "timeframe",
|
||
"queryType": 0,
|
||
"resourceType": "microsoft.operationalinsights/workspaces"
|
||
},
|
||
"customWidth": "50",
|
||
"name": "query - 5"
|
||
},
|
||
{
|
||
"type": 3,
|
||
"content": {
|
||
"version": "KqlItem/1.0",
|
||
"query": " DeviceProcessEvents\r\n | where InitiatingProcessFileName =~ \"solarwinds.businesslayerhost.exe\"\r\n | where not(FolderPath endswith @\"\\SolarWinds\\Orion\\APM\\APMServiceControl.exe\"\r\n or FolderPath endswith @\"\\SolarWinds\\Orion\\ExportToPDFCmd.Exe\"\r\n or FolderPath endswith @\"\\SolarWinds.Credentials\\SolarWinds.Credentials.Orion.WebApi.exe\"\r\n or FolderPath endswith @\"\\SolarWinds\\Orion\\Topology\\SolarWinds.Orion.Topology.Calculator.exe\"\r\n or FolderPath endswith @\"\\SolarWinds\\Orion\\Database-Maint.exe\"\r\n or FolderPath endswith @\"\\SolarWinds.Orion.ApiPoller.Service\\SolarWinds.Orion.ApiPoller.Service.exe\"\r\n or FolderPath endswith @\"\\Windows\\SysWOW64\\WerFault.exe\"\r\n )",
|
||
"size": 0,
|
||
"title": "Suspicious SolarWinds Process Creation",
|
||
"timeContext": {
|
||
"durationMs": 2592000000
|
||
},
|
||
"timeContextFromParameter": "timeframe",
|
||
"queryType": 0,
|
||
"resourceType": "microsoft.operationalinsights/workspaces"
|
||
},
|
||
"customWidth": "50",
|
||
"name": "query - 8"
|
||
},
|
||
{
|
||
"type": 3,
|
||
"content": {
|
||
"version": "KqlItem/1.0",
|
||
"query": "let includeProc = dynamic([\"sc.exe\",\"net1.exe\",\"net.exe\", \"taskkill.exe\", \"cmd.exe\", \"powershell.exe\"]);\r\n let action = dynamic([\"stop\",\"disable\", \"delete\"]);\r\n let service1 = dynamic(['sense', 'windefend', 'mssecflt']);\r\n let service2 = dynamic(['sense', 'windefend', 'mssecflt', 'healthservice']);\r\n let params1 = dynamic([\"-DisableRealtimeMonitoring\", \"-DisableBehaviorMonitoring\" ,\"-DisableIOAVProtection\"]);\r\n let params2 = dynamic([\"sgrmbroker.exe\", \"mssense.exe\"]);\r\n let regparams1 = dynamic(['reg add \"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Defender\"', 'reg add \"HKLM\\\\SOFTWARE\\\\Policies\\\\Microsoft\\\\Windows Advanced Threat Protection\"']);\r\n let regparams2 = dynamic(['ForceDefenderPassiveMode', 'DisableAntiSpyware']);\r\n let regparams3 = dynamic(['sense', 'windefend']);\r\n let regparams4 = dynamic(['demand', 'disabled']);\r\n let regparams5 = dynamic(['reg add \"HKLM\\\\SYSTEM\\\\CurrentControlSet\\\\services\\\\HealthService\"', 'reg add \"HKLM\\\\SYSTEM\\\\CurrentControlSet\\\\Services\\\\Sense\"', 'reg add \"HKLM\\\\SYSTEM\\\\CurrentControlSet\\\\Services\\\\WinDefend\"', 'reg add \"HKLM\\\\SYSTEM\\\\CurrentControlSet\\\\Services\\\\MsSecFlt\"', 'reg add \"HKLM\\\\SYSTEM\\\\CurrentControlSet\\\\Services\\\\DiagTrack\"', 'reg add \"HKLM\\\\SYSTEM\\\\CurrentControlSet\\\\Services\\\\SgrmBroker\"', 'reg add \"HKLMSYSTEM\\\\CurrentControlSet\\\\Services\\\\SgrmAgent\"', 'reg add \"HKLM\\\\SYSTEM\\\\CurrentControlSet\\\\Services\\\\AATPSensorUpdater\"' , 'reg add \"HKLM\\\\SYSTEM\\\\CurrentControlSet\\\\Services\\\\AATPSensor\"', 'reg add \"HKLM\\\\SYSTEM\\\\CurrentControlSet\\\\Services\\\\mpssvc\"']);\r\n let regparams6 = dynamic(['/d 4','/d \"4\"','/d 0x00000004']);\r\n let regparams7 = dynamic(['/d 1','/d \"1\"','/d 0x00000001']);\r\n let servicelist = dynamic(['Services\\\\HealthService', 'Services\\\\Sense', 'Services\\\\WinDefend', 'Services\\\\MsSecFlt', 'Services\\\\DiagTrack', 'Services\\\\SgrmBroker', 'Services\\\\SgrmAgent', 'Services\\\\AATPSensorUpdater' , 'Services\\\\AATPSensor', 'Services\\\\mpssvc']);\r\n let filename = dynamic([\"subinacl.exe\",'SetACL.exe']);\r\n let parameters = dynamic (['/deny=SYSTEM', '/deny=S-1-5-18', '/grant=SYSTEM=r', '/grant=S-1-5-18=r', 'n:SYSTEM;p:READ', 'n1:SYSTEM;ta:remtrst;w:dacl']);\r\n let FullAccess = dynamic(['A;CI;KA;;;SY', 'A;ID;KA;;;SY', 'A;CIID;KA;;;SY']);\r\n let ReadAccess = dynamic(['A;CI;KR;;;SY', 'A;ID;KR;;;SY', 'A;CIID;KR;;;SY']);\r\n let DenyAccess = dynamic(['D;CI;KR;;;SY', 'D;ID;KR;;;SY', 'D;CIID;KR;;;SY']);\r\n let timeframe = 1d;\r\n (union isfuzzy=true\r\n (\r\n SecurityEvent\r\n | where EventID == 4670\r\n | where ObjectType == 'Key'\r\n | where ObjectName has_any (servicelist)\r\n | parse EventData with * 'OldSd\">' OldSd \"<\" *\r\n | parse EventData with * 'NewSd\">' NewSd \"<\" *\r\n | extend Reason =\r\n iff ((OldSd has ';;;SY' and NewSd !has ';;;SY'), 'System Account is removed',\r\n iff ((OldSd has_any (FullAccess) and NewSd has_any (ReadAccess)) , 'System permission has been changed to read from full access',\r\n iff ((OldSd has_any (FullAccess) and NewSd has_any (DenyAccess)), 'System account has been given denied permission', 'None')))\r\n | project TimeGenerated, Computer, Account, ProcessName, ProcessId, ObjectName, EventData, Activity, HandleId, SubjectLogonId, OldSd, NewSd , Reason\r\n ),\r\n (\r\n SecurityEvent\r\n | where EventID == 4688\r\n | extend ProcessName = tostring(split(NewProcessName, '\\\\')[-1])\r\n | where ProcessName in~ (includeProc)\r\n | where (CommandLine has_any (action) and CommandLine has_any (service1))\r\n or (CommandLine has_any (params1) and CommandLine has 'Set-MpPreference' and CommandLine has '$true')\r\n or (CommandLine has_any (params2) and CommandLine has \"/IM\")\r\n or (CommandLine has_any (regparams5) and CommandLine has 'Start' and CommandLine has_any (regparams6))\r\n or (CommandLine has_any (regparams1) and CommandLine has_any (regparams2) and CommandLine has_any (regparams7))\r\n or (CommandLine has \"start\" and CommandLine has \"config\" and CommandLine has_any (regparams3) and CommandLine has_any (regparams4))\r\n or (CommandLine has_any (servicelist) and CommandLine has_any (parameters))\r\n | project TimeGenerated, Computer, Account, AccountDomain, ProcessName, ProcessNameFullPath = NewProcessName, EventID, Activity, CommandLine, EventSourceName, Type\r\n ),\r\n (\r\n Event\r\n | where Source =~ \"Microsoft-Windows-SENSE\"\r\n | where EventID == 87 and ParameterXml in (\"<Param>sgrmbroker</Param>\", \"<Param>WinDefend</Param>\")\r\n | project TimeGenerated, Computer, Account = UserName, EventID, Activity = RenderedDescription, EventSourceName = Source, Type\r\n ),\r\n (\r\n DeviceProcessEvents\r\n | where InitiatingProcessFileName in~ (includeProc)\r\n | where (InitiatingProcessCommandLine has_any(action) and InitiatingProcessCommandLine has_any (service2) and InitiatingProcessParentFileName != 'cscript.exe')\r\n or (InitiatingProcessCommandLine has_any (params1) and InitiatingProcessCommandLine has 'Set-MpPreference' and InitiatingProcessCommandLine has '$true')\r\n or (InitiatingProcessCommandLine has_any (params2) and InitiatingProcessCommandLine has \"/IM\")\r\n or ( InitiatingProcessCommandLine has_any (regparams5) and InitiatingProcessCommandLine has 'Start' and InitiatingProcessCommandLine has_any (regparams6))\r\n or (InitiatingProcessCommandLine has_any (regparams1) and InitiatingProcessCommandLine has_any (regparams2) and InitiatingProcessCommandLine has_any (regparams7))\r\n or (InitiatingProcessCommandLine has_any(\"start\") and InitiatingProcessCommandLine has \"config\" and InitiatingProcessCommandLine has_any (regparams3) and InitiatingProcessCommandLine has_any (regparams4))\r\n or (InitiatingProcessFileName in~ (filename) and (InitiatingProcessCommandLine has_any(servicelist) and InitiatingProcessCommandLine has_any (parameters)))\r\n | extend Account = iff(isnotempty(InitiatingProcessAccountUpn), InitiatingProcessAccountUpn, InitiatingProcessAccountName), Computer = DeviceName\r\n | project TimeGenerated, Computer, Account, AccountDomain, ProcessName = InitiatingProcessFileName, ProcessNameFullPath = FolderPath, Activity = ActionType, CommandLine = InitiatingProcessCommandLine, Type, InitiatingProcessParentFileName\r\n )\r\n )",
|
||
"size": 0,
|
||
"title": "Potential Microsoft Defender Services Tampering",
|
||
"timeContext": {
|
||
"durationMs": 0
|
||
},
|
||
"timeContextFromParameter": "timeframe",
|
||
"queryType": 0,
|
||
"resourceType": "microsoft.operationalinsights/workspaces"
|
||
},
|
||
"customWidth": "50",
|
||
"name": "query - 12"
|
||
},
|
||
{
|
||
"type": 3,
|
||
"content": {
|
||
"version": "KqlItem/1.0",
|
||
"query": "// Adjust this to use a longer timeframe to identify ADFS servers\r\n let lookback = 6d;\r\n // Adjust this to adjust the key export detection timeframe\r\n let timeframe = 1d;\r\n // Start be identifying ADFS servers to reduce FP chance\r\n let ADFS_Servers = (\r\n Event\r\n | where TimeGenerated > ago(timeframe+lookback)\r\n | where Source == \"Microsoft-Windows-Sysmon\"\r\n | extend EventData = parse_xml(EventData).DataItem.EventData.Data\r\n | mv-expand bagexpansion=array EventData\r\n | evaluate bag_unpack(EventData)\r\n | extend Key=tostring(['@Name']), Value=['#text']\r\n | evaluate pivot(Key, any(Value), TimeGenerated, Source, EventLog, Computer, EventLevel, EventLevelName, EventID, UserName, RenderedDescription, MG, ManagementGroupName, Type, _ResourceId)\r\n | extend process = split(Image, '\\\\', -1)[-1]\r\n | where process =~ \"Microsoft.IdentityServer.ServiceHost.exe\"\r\n | summarize by Computer);\r\n // Look for ADFS servers where Named Pipes event are present\r\n Event\r\n | where TimeGenerated > ago(timeframe)\r\n | where Source == \"Microsoft-Windows-Sysmon\"\r\n | where Computer in~ (ADFS_Servers)\r\n | extend RenderedDescription = tostring(split(RenderedDescription, \":\")[0])\r\n | extend EventData = parse_xml(EventData).DataItem.EventData.Data\r\n | mv-expand bagexpansion=array EventData\r\n | evaluate bag_unpack(EventData)\r\n | extend Key=tostring(['@Name']), Value=['#text']\r\n | evaluate pivot(Key, any(Value), TimeGenerated, Source, EventLog, Computer, EventLevel, EventLevelName, EventID, UserName, RenderedDescription, MG, ManagementGroupName, Type, _ResourceId)\r\n | extend RuleName = column_ifexists(\"RuleName\", \"\"), TechniqueId = column_ifexists(\"TechniqueId\", \"\"), TechniqueName = column_ifexists(\"TechniqueName\", \"\")\r\n | parse RuleName with * 'technique_id=' TechniqueId ',' * 'technique_name=' TechniqueName\r\n | where EventID in (17,18)\r\n // Look for Pipe related to querying the WID\r\n | where PipeName == \"\\\\MICROSOFT##WID\\\\tsql\\\\query\"\r\n | extend process = split(Image, '\\\\', -1)[-1]\r\n // Exclude expected processes\r\n | where process !in (\"Microsoft.IdentityServer.ServiceHost.exe\", \"Microsoft.Identity.Health.Adfs.PshSurrogate.exe\", \"AzureADConnect.exe\", \"Microsoft.Tri.Sensor.exe\", \"wsmprovhost.exe\",\"mmc.exe\", \"sqlservr.exe\")\r\n | extend Operation = RenderedDescription\r\n | project-reorder TimeGenerated, EventType, Operation, process, Image, Computer, UserName\r\n | extend HostCustomEntity = Computer, AccountCustomEntity = UserName ",
|
||
"size": 0,
|
||
"title": "Potential ADFS Key Export Activity (Sysmon)",
|
||
"queryType": 0,
|
||
"resourceType": "microsoft.operationalinsights/workspaces"
|
||
},
|
||
"customWidth": "50",
|
||
"name": "query - 9"
|
||
},
|
||
{
|
||
"type": 3,
|
||
"content": {
|
||
"version": "KqlItem/1.0",
|
||
"query": "(union isfuzzy=true (SecurityEvent \r\n | where EventID == 4662 // You need to create a SACL on the ADFS Policy Store DKM group for this event to be created. \r\n | where ObjectServer == 'DS'\r\n | where OperationType == 'Object Access'\r\n //| where ObjectName contains '<GUID of ADFS Policy Store DKM Group object' This is unique to the domain. Check description for more details.\r\n | where ObjectType contains '5cb41ed0-0e4c-11d0-a286-00aa003049e2' // Contact Class\r\n | where Properties contains '8d3bca50-1d7e-11d0-a081-00aa006c33ed' // Picture Attribute - Ldap-Display-Name: thumbnailPhoto\r\n | extend timestamp = TimeGenerated, HostCustomEntity = Computer, AccountCustomEntity = SubjectAccount),\r\n (DeviceEvents\r\n | where ActionType =~ \"LdapSearch\"\r\n | where AdditionalFields.AttributeList contains \"thumbnailPhoto\"\r\n | where AdditionalFields.DistinguishedName contains \"CN=ADFS,CN=Microsoft,CN=Program Data\" // Filter results to show only hits related to the ADFS AD container\r\n | extend timestamp = TimeGenerated, HostCustomEntity = DeviceName, AccountCustomEntity = InitiatingProcessAccountName)\r\n )",
|
||
"size": 0,
|
||
"title": "Potential ADFS Key Export activity (Event Logs & MDE)",
|
||
"timeContext": {
|
||
"durationMs": 2592000000
|
||
},
|
||
"timeContextFromParameter": "timeframe",
|
||
"queryType": 0,
|
||
"resourceType": "microsoft.operationalinsights/workspaces"
|
||
},
|
||
"customWidth": "50",
|
||
"name": "query - 10"
|
||
},
|
||
{
|
||
"type": 1,
|
||
"content": {
|
||
"json": "The query `Potential ADFS Key Export Activity (Sysmon)` will display the error `extend operator: Failed to resolve scalar expression named '[\"@Name\"]'` if you do not have Sysmon data onboarded to your workspace.",
|
||
"style": "info"
|
||
},
|
||
"name": "text - 11"
|
||
},
|
||
{
|
||
"type": 3,
|
||
"content": {
|
||
"version": "KqlItem/1.0",
|
||
"query": "(union isfuzzy=true\r\n(\r\nDeviceProcessEvents\r\n| where FileName =~ \"rundll32.exe\"\r\n| where InitiatingProcessIntegrityLevel in (\"High\", \"System\")\r\n| where ProcessCommandLine matches regex \"rundll32\\\\s+c\\\\:\\\\\\\\windows\\\\\\\\[a-zA-Z0-9\\\\.]*\\\\\\\\[a-zA-Z0-9\\\\\\\\\\\\.]*\\\\.dll\\\\s+[a-zA-Z_]{3,}\" or ProcessCommandLine matches regex @\"(?i)rundll32\\s+c\\:\\\\windows(\\\\[^\\\\]+)+\\.dll\\s+[a-zA-Z0-9_]{3,}\"),\r\n(\r\nSecurityEvent\r\n| where EventID == 4688\r\n| where Process =~ \"rundll32.exe\"\r\n| where MandatoryLabel in (\"S-1-16-12288\",\"S-1-16-16384\")\r\n| where CommandLine matches regex \"rundll32\\\\s+c\\\\:\\\\\\\\windows\\\\\\\\[a-zA-Z0-9\\\\.]*\\\\\\\\[a-zA-Z0-9\\\\\\\\\\\\.]*\\\\.dll\\\\s+[a-zA-Z_]{3,}\" or CommandLine matches regex @\"(?i)rundll32\\s+c\\:\\\\windows(\\\\[^\\\\]+)+\\.dll\\s+[a-zA-Z0-9_]{3,}\"))",
|
||
"size": 0,
|
||
"title": "Presence of custom Cobalt Strike",
|
||
"timeContext": {
|
||
"durationMs": 0
|
||
},
|
||
"timeContextFromParameter": "timeframe",
|
||
"queryType": 0,
|
||
"resourceType": "microsoft.operationalinsights/workspaces"
|
||
},
|
||
"customWidth": "50",
|
||
"name": "query - 14"
|
||
},
|
||
{
|
||
"type": 3,
|
||
"content": {
|
||
"version": "KqlItem/1.0",
|
||
"query": "(union isfuzzy=true\r\n(\r\nDeviceProcessEvents\r\n| where InitiatingProcessFileName in~(\"rundll32.exe\", \"dllhost.exe\") and InitiatingProcessCommandLine != \"\" and InitiatingProcessCommandLine !contains \" \"\r\n| extend RundllTime = Timestamp\r\n| join DeviceProcessEvents on $left.DeviceId == $right.DeviceId\r\n| where InitiatingProcessFileName hasprefix \"7z\" or InitiatingProcessCommandLine has \"-mx9\"\r\n| extend DateDiff = datetime_diff(\"day\", Timestamp, RundllTime)\r\n| where DateDiff < 2),\r\n(\r\nSecurityEvent\r\n| where EventID == 4688\r\n| where Process in~(\"rundll32.exe\", \"dllhost.exe\") and CommandLine != \"\" and CommandLine !contains \" |\"\r\n| extend RundllTime = TimeGenerated\r\n| join (SecurityEvent\r\n| where EventID == 4688) on $left.Computer == $right.Computer\r\n| where Process hasprefix \"7z\" or CommandLine has \"-mx9\"\r\n| extend DateDiff = datetime_diff(\"day\", TimeGenerated, RundllTime)\r\n| where DateDiff < 2))",
|
||
"size": 0,
|
||
"title": "Anomalous usage of 7zip",
|
||
"timeContext": {
|
||
"durationMs": 0
|
||
},
|
||
"timeContextFromParameter": "timeframe",
|
||
"queryType": 0,
|
||
"resourceType": "microsoft.operationalinsights/workspaces"
|
||
},
|
||
"customWidth": "50",
|
||
"name": "query - 15"
|
||
},
|
||
{
|
||
"type": 3,
|
||
"content": {
|
||
"version": "KqlItem/1.0",
|
||
"query": "DeviceNetworkEvents\r\n| where InitiatingProcessParentFileName =~ \"rundll32.exe\"\r\n| where InitiatingProcessFileName =~ \"dllhost.exe\" and InitiatingProcessCommandLine != \"\" and InitiatingProcessCommandLine !contains \" \"",
|
||
"size": 0,
|
||
"title": "Potential C2 Activity",
|
||
"timeContext": {
|
||
"durationMs": 86400000
|
||
},
|
||
"queryType": 0,
|
||
"resourceType": "microsoft.operationalinsights/workspaces"
|
||
},
|
||
"customWidth": "50",
|
||
"name": "query - 16"
|
||
},
|
||
{
|
||
"type": 1,
|
||
"content": {
|
||
"json": "Several of these queries look for activity assocaited with later stage host activity as detailed in this report: https://www.microsoft.com/security/blog/2021/01/20/deep-dive-into-the-solorigate-second-stage-activation-from-sunburst-to-teardrop-and-raindrop/",
|
||
"style": "info"
|
||
},
|
||
"customWidth": "50",
|
||
"name": "text - 17"
|
||
}
|
||
]
|
||
},
|
||
"conditionalVisibility": {
|
||
"parameterName": "nav_val",
|
||
"comparison": "isEqualTo",
|
||
"value": "4"
|
||
},
|
||
"name": "group - 5"
|
||
},
|
||
{
|
||
"type": 12,
|
||
"content": {
|
||
"version": "NotebookGroup/1.0",
|
||
"groupType": "editable",
|
||
"title": "Suspicious Network Activity",
|
||
"items": [
|
||
{
|
||
"type": 1,
|
||
"content": {
|
||
"json": "## Suspicious Network Activity\r\nThe Microsoft and FireEye blogs provide the network-based indicator of \"avsvmcloud[.]com\" that can be used for hunting. The queries below provide details of network events at both the host and network device level (using the CommonSecurityLog) that can be used for hunting."
|
||
},
|
||
"name": "text - 0"
|
||
},
|
||
{
|
||
"type": 9,
|
||
"content": {
|
||
"version": "KqlParameterItem/1.0",
|
||
"parameters": [
|
||
{
|
||
"id": "570e71c6-67b4-487b-874a-b357913a7860",
|
||
"version": "KqlParameterItem/1.0",
|
||
"name": "timeframe",
|
||
"label": "Hunting Time Frame",
|
||
"type": 4,
|
||
"isRequired": true,
|
||
"value": {
|
||
"durationMs": 2592000000
|
||
},
|
||
"typeSettings": {
|
||
"selectableValues": [
|
||
{
|
||
"durationMs": 86400000
|
||
},
|
||
{
|
||
"durationMs": 172800000
|
||
},
|
||
{
|
||
"durationMs": 259200000
|
||
},
|
||
{
|
||
"durationMs": 604800000
|
||
},
|
||
{
|
||
"durationMs": 1209600000
|
||
},
|
||
{
|
||
"durationMs": 2419200000
|
||
},
|
||
{
|
||
"durationMs": 2592000000
|
||
},
|
||
{
|
||
"durationMs": 5184000000
|
||
},
|
||
{
|
||
"durationMs": 7776000000
|
||
}
|
||
],
|
||
"allowCustom": true
|
||
},
|
||
"timeContext": {
|
||
"durationMs": 86400000
|
||
}
|
||
}
|
||
],
|
||
"style": "pills",
|
||
"queryType": 0,
|
||
"resourceType": "microsoft.operationalinsights/workspaces"
|
||
},
|
||
"customWidth": "30",
|
||
"name": "parameters - 2"
|
||
},
|
||
{
|
||
"type": 1,
|
||
"content": {
|
||
"json": ""
|
||
},
|
||
"customWidth": "70",
|
||
"name": "text - 4"
|
||
},
|
||
{
|
||
"type": 3,
|
||
"content": {
|
||
"version": "KqlItem/1.0",
|
||
"query": "let solorigate_domains = dynamic([\"avsvmcloud.com\",\"aimsecurity.net\",\"datazr.com\",\"ervsystem.com\",\"financialmarket.org\",\"gallerycenter.org\",\"infinitysoftwares.com\",\"mobilnweb.com\",\"olapdatabase.com\",\"swipeservice.com\",\"techiefly.com\"]);\r\n(union isfuzzy=true\r\n(\r\nCommonSecurityLog\r\n| where DestinationHostName has_any (solorigate_domains) or RequestURL has_any (solorigate_domains)),\r\n(\r\nDeviceNetworkEvents\r\n| where RemoteUrl has_any (solorigate_domains)),\r\n(\r\nDnsEvents\r\n| where Name has_any (solorigate_domains)))\r\n",
|
||
"size": 0,
|
||
"title": "Network Connections to Solorigate Domains",
|
||
"timeContext": {
|
||
"durationMs": 0
|
||
},
|
||
"timeContextFromParameter": "timeframe",
|
||
"queryType": 0,
|
||
"resourceType": "microsoft.operationalinsights/workspaces"
|
||
},
|
||
"customWidth": "70",
|
||
"name": "query - 1"
|
||
},
|
||
{
|
||
"type": 1,
|
||
"content": {
|
||
"json": "\r\n\r\n\r\nThis query looks across multiple data sources for network activity relating to domains known to be associated with Solorigate activity. This can be a large amount of data. If the query fails to load select a smaller Hunting Time Frame to run the workbook in. If you do not have DnsEvent logging in your workspace you may see an error relating to an unknown table or column, despite this warning the query has run sucessfully.",
|
||
"style": "info"
|
||
},
|
||
"customWidth": "30",
|
||
"name": "text - 5",
|
||
"styleSettings": {
|
||
"padding": "5"
|
||
}
|
||
},
|
||
{
|
||
"type": 3,
|
||
"content": {
|
||
"version": "KqlItem/1.0",
|
||
"query": " let cloudApiTerms = dynamic([\"api\", \"east\", \"west\"]);\r\n DnsEvents\r\n | where IPAddresses != \"\" and IPAddresses != \"127.0.0.1\"\r\n | where Name endswith \".com\" or Name endswith \".org\" or Name endswith \".net\"\r\n | extend domain_split = split(Name, \".\")\r\n | where tostring(domain_split[-5]) != \"\" and tostring(domain_split[-6]) == \"\"\r\n | extend sub_domain = tostring(domain_split[0])\r\n | where sub_domain !contains \"-\"\r\n | extend sub_directories = strcat(domain_split[-3], \" \", domain_split[-4])\r\n | where sub_directories has_any(cloudApiTerms)\r\n //Based on sample communications the subdomain is always between 20 and 30 bytes\r\n | where strlen(sub_domain) < 32 or strlen(sub_domain) > 20\r\n | extend domain = strcat(tostring(domain_split[-2]), \".\", tostring(domain_split[-1])) \r\n | extend subdomain_no = countof(sub_domain, @\"(\\d)\", \"regex\")\r\n | extend subdomain_ch = countof(sub_domain, @\"([a-z])\", \"regex\")\r\n | where subdomain_no > 1\r\n | extend percentage_numerical = toreal(subdomain_no) / toreal(strlen(sub_domain)) * 100\r\n | where percentage_numerical < 50 and percentage_numerical > 5\r\n | summarize count(), make_set(Name), FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated) by Name\r\n | order by count_ asc",
|
||
"size": 0,
|
||
"title": "Solorigate DNS Pattern",
|
||
"timeContext": {
|
||
"durationMs": 2592000000
|
||
},
|
||
"timeContextFromParameter": "timeframe",
|
||
"queryType": 0,
|
||
"resourceType": "microsoft.operationalinsights/workspaces"
|
||
},
|
||
"customWidth": "70",
|
||
"name": "query - 6"
|
||
},
|
||
{
|
||
"type": 1,
|
||
"content": {
|
||
"json": "This query looks for domains that exhibit a similar pattern of activity to avsvmcloud.com. \r\nWhilst domains detected by this query could be legitimate in nature the intention is to surface other potential C2 domains with a similar profile to avsvmcloud.com",
|
||
"style": "info"
|
||
},
|
||
"customWidth": "30",
|
||
"name": "text - 7",
|
||
"styleSettings": {
|
||
"padding": "5"
|
||
}
|
||
}
|
||
]
|
||
},
|
||
"conditionalVisibility": {
|
||
"parameterName": "nav_val",
|
||
"comparison": "isEqualTo",
|
||
"value": "5"
|
||
},
|
||
"name": "group - 6"
|
||
}
|
||
],
|
||
"fallbackResourceIds": [
|
||
],
|
||
"fromTemplateId": "sentinel-SolorWindsWorkbook",
|
||
"$schema": "https://github.com/Microsoft/Application-Insights-Workbooks/blob/master/schema/workbook.json"
|
||
} |