IP address of Windows host encoded in web request
Id | a4ce20ae-a2e4-4d50-b40d-d49f1353b6cc |
Rulename | IP address of Windows host encoded in web request |
Description | This detection will identify network requests in HTTP proxy data that contains Base64 encoded IP addresses. After identifying candidates the query joins with DeviceNetworkEvents to idnetify any machine within the network using that IP address. Alerts indicate that the IP address of a machine within your network was seen with it’s IP address base64 encoded in an outbound web request. This method of egressing the IP was seen used in POLONIUM’s RunningRAT tool, however the detection is generic. |
Severity | Medium |
Tactics | Exfiltration CommandAndControl |
Techniques | T1041 T1071.001 |
Required data connectors | CheckPoint Fortinet MicrosoftThreatProtection PaloAltoNetworks Zscaler |
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/MultipleDataSources/B64IPInURLFromMDE.yaml |
Version | 1.0.2 |
Arm template | a4ce20ae-a2e4-4d50-b40d-d49f1353b6cc.json |
// Extracts plaintext IPv4 addresses
let ipv4_plaintext_extraction_regex = @"((?:(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(?:\.)){3}(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]){1,3})";
// Identified base64 encoded IPv4 addresses
let ipv4_encoded_identification_regex = @"\=([a-zA-Z0-9\/\+]*(?:(?:MC|Au|wL|MS|Eu|xL|Mi|Iu|yL|My|Mu|zL|NC|Qu|0L|NS|Uu|1L|Ni|Yu|2L|Ny|cu|3L|OC|gu|4L|OS|ku|5L){1}[a-zA-Z0-9\/\+]{2,4}){3}[a-zA-Z0-9\/\+\=]*)";
// Extractes IPv4 addresses as hex values
let ipv4_decoded_hex_extract = @"((?:(?:61|62|63|64|65|66|67|68|69|6a|6b|6c|6d|6e|6f|70|71|72|73|74|75|76|77|78|79|7a|41|42|43|44|45|46|47|48|49|4a|4b|4c|4d|4e|4f|50|51|52|53|54|55|56|57|58|59|5a|2f|2b|3d),){7,15})";
CommonSecurityLog
| where isnotempty(RequestURL)
// Identify requests with encoded IPv4 addresses
| where RequestURL matches regex ipv4_encoded_identification_regex
| project TimeGenerated, RequestURL
// Extract IP candidates in their base64 encoded format, significantly reducing the dataset
| extend extracted_encoded_ip_candidate = extract_all(ipv4_encoded_identification_regex, RequestURL)
// We could have more than one candidate, expand them out
| mv-expand extracted_encoded_ip_candidate to typeof(string)
| summarize Start=min(TimeGenerated), End=max(TimeGenerated), make_set(RequestURL) by extracted_encoded_ip_candidate
// Pad if we need to
| extend extracted_encoded_ip_candidate = iff(strlen(extracted_encoded_ip_candidate) % 2 == 0, extracted_encoded_ip_candidate, strcat(extracted_encoded_ip_candidate, "="))
// Now decode the candidate to a long array, we cannot go straight to string as it cannot handle non-UTF8, we need to strip that first
| extend extracted_encoded_ip_candidate = tostring(base64_decode_toarray(extracted_encoded_ip_candidate))
// Extract the IP candidates from the array
| extend hex_extracted = extract_all(ipv4_decoded_hex_extract, extracted_encoded_ip_candidate)
// Expand, it's still possible that we might have more than 1 IP
| mv-expand hex_extracted
// Now we should have a clean string. We need to put it back into a dynamic array to convert back to a string.
| extend hex_extracted = trim_end(",", tostring(hex_extracted))
| extend hex_extracted = strcat("[",hex_extracted,"]")
| extend hex_extracted = todynamic(hex_extracted)
| extend extracted_encoded_ip_candidate = todynamic(extracted_encoded_ip_candidate)
// Convert the array back into a string
| extend decoded_ip_candidate = make_string(hex_extracted)
| summarize by decoded_ip_candidate, tostring(set_RequestURL), Start, End
// Now the IP candidates will be in plaintext, extract the IPs using a regex
| extend ipmatch = extract_all(ipv4_plaintext_extraction_regex, decoded_ip_candidate)
// If it's not an IP, throw it out
| where isnotnull(ipmatch)
| mv-expand ipmatch to typeof(string)
// Join with DeviceNetworkEvents to find instances where an IP of a machine in our MDE estate sent it's IP in a base64 encoded string
| join (
DeviceNetworkEvents
| summarize make_set(DeviceId), make_set(DeviceName) by RemoteIP
) on $left.ipmatch == $right.RemoteIP
| project Start, End, IPmatch=ipmatch, RequestURL=set_RequestURL, DeviceNames=set_DeviceName, DeviceIds=set_DeviceId, RemoteIP
relevantTechniques:
- T1041
- T1071.001
name: IP address of Windows host encoded in web request
requiredDataConnectors:
- dataTypes:
- CommonSecurityLog
connectorId: Zscaler
- dataTypes:
- CommonSecurityLog
connectorId: Fortinet
- dataTypes:
- CommonSecurityLog
connectorId: CheckPoint
- dataTypes:
- CommonSecurityLog
connectorId: PaloAltoNetworks
- dataTypes:
- DeviceNetworkEvents
connectorId: MicrosoftThreatProtection
entityMappings:
- fieldMappings:
- identifier: Address
columnName: IPmatch
entityType: IP
- fieldMappings:
- identifier: HostName
columnName: DeviceNames
entityType: Host
- fieldMappings:
- identifier: Url
columnName: RequestURL
entityType: URL
triggerThreshold: 0
id: a4ce20ae-a2e4-4d50-b40d-d49f1353b6cc
tactics:
- Exfiltration
- CommandAndControl
version: 1.0.2
OriginalUri: https://github.com/Azure/Azure-Sentinel/blob/master/Detections/MultipleDataSources/B64IPInURLFromMDE.yaml
queryPeriod: 1d
kind: Scheduled
tags:
- POLONIUM
metadata:
categories:
domains:
- Security - Others
- Networking
author:
name: Thomas McElroy
support:
tier: Community
source:
kind: Community
queryFrequency: 1d
severity: Medium
description: |
'This detection will identify network requests in HTTP proxy data that contains Base64 encoded IP addresses. After identifying candidates the query joins with DeviceNetworkEvents to idnetify any machine within the network using that IP address. Alerts indicate that the IP address of a machine within your network was seen with it's IP address base64 encoded in an outbound web request. This method of egressing the IP was seen used in POLONIUM's RunningRAT tool, however the detection is generic.'
query: |
// Extracts plaintext IPv4 addresses
let ipv4_plaintext_extraction_regex = @"((?:(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(?:\.)){3}(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]){1,3})";
// Identified base64 encoded IPv4 addresses
let ipv4_encoded_identification_regex = @"\=([a-zA-Z0-9\/\+]*(?:(?:MC|Au|wL|MS|Eu|xL|Mi|Iu|yL|My|Mu|zL|NC|Qu|0L|NS|Uu|1L|Ni|Yu|2L|Ny|cu|3L|OC|gu|4L|OS|ku|5L){1}[a-zA-Z0-9\/\+]{2,4}){3}[a-zA-Z0-9\/\+\=]*)";
// Extractes IPv4 addresses as hex values
let ipv4_decoded_hex_extract = @"((?:(?:61|62|63|64|65|66|67|68|69|6a|6b|6c|6d|6e|6f|70|71|72|73|74|75|76|77|78|79|7a|41|42|43|44|45|46|47|48|49|4a|4b|4c|4d|4e|4f|50|51|52|53|54|55|56|57|58|59|5a|2f|2b|3d),){7,15})";
CommonSecurityLog
| where isnotempty(RequestURL)
// Identify requests with encoded IPv4 addresses
| where RequestURL matches regex ipv4_encoded_identification_regex
| project TimeGenerated, RequestURL
// Extract IP candidates in their base64 encoded format, significantly reducing the dataset
| extend extracted_encoded_ip_candidate = extract_all(ipv4_encoded_identification_regex, RequestURL)
// We could have more than one candidate, expand them out
| mv-expand extracted_encoded_ip_candidate to typeof(string)
| summarize Start=min(TimeGenerated), End=max(TimeGenerated), make_set(RequestURL) by extracted_encoded_ip_candidate
// Pad if we need to
| extend extracted_encoded_ip_candidate = iff(strlen(extracted_encoded_ip_candidate) % 2 == 0, extracted_encoded_ip_candidate, strcat(extracted_encoded_ip_candidate, "="))
// Now decode the candidate to a long array, we cannot go straight to string as it cannot handle non-UTF8, we need to strip that first
| extend extracted_encoded_ip_candidate = tostring(base64_decode_toarray(extracted_encoded_ip_candidate))
// Extract the IP candidates from the array
| extend hex_extracted = extract_all(ipv4_decoded_hex_extract, extracted_encoded_ip_candidate)
// Expand, it's still possible that we might have more than 1 IP
| mv-expand hex_extracted
// Now we should have a clean string. We need to put it back into a dynamic array to convert back to a string.
| extend hex_extracted = trim_end(",", tostring(hex_extracted))
| extend hex_extracted = strcat("[",hex_extracted,"]")
| extend hex_extracted = todynamic(hex_extracted)
| extend extracted_encoded_ip_candidate = todynamic(extracted_encoded_ip_candidate)
// Convert the array back into a string
| extend decoded_ip_candidate = make_string(hex_extracted)
| summarize by decoded_ip_candidate, tostring(set_RequestURL), Start, End
// Now the IP candidates will be in plaintext, extract the IPs using a regex
| extend ipmatch = extract_all(ipv4_plaintext_extraction_regex, decoded_ip_candidate)
// If it's not an IP, throw it out
| where isnotnull(ipmatch)
| mv-expand ipmatch to typeof(string)
// Join with DeviceNetworkEvents to find instances where an IP of a machine in our MDE estate sent it's IP in a base64 encoded string
| join (
DeviceNetworkEvents
| summarize make_set(DeviceId), make_set(DeviceName) by RemoteIP
) on $left.ipmatch == $right.RemoteIP
| project Start, End, IPmatch=ipmatch, RequestURL=set_RequestURL, DeviceNames=set_DeviceName, DeviceIds=set_DeviceId, RemoteIP
triggerOperator: gt
{
"$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/a4ce20ae-a2e4-4d50-b40d-d49f1353b6cc')]",
"kind": "Scheduled",
"name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/a4ce20ae-a2e4-4d50-b40d-d49f1353b6cc')]",
"properties": {
"alertRuleTemplateName": "a4ce20ae-a2e4-4d50-b40d-d49f1353b6cc",
"customDetails": null,
"description": "'This detection will identify network requests in HTTP proxy data that contains Base64 encoded IP addresses. After identifying candidates the query joins with DeviceNetworkEvents to idnetify any machine within the network using that IP address. Alerts indicate that the IP address of a machine within your network was seen with it's IP address base64 encoded in an outbound web request. This method of egressing the IP was seen used in POLONIUM's RunningRAT tool, however the detection is generic.'\n",
"displayName": "IP address of Windows host encoded in web request",
"enabled": true,
"entityMappings": [
{
"entityType": "IP",
"fieldMappings": [
{
"columnName": "IPmatch",
"identifier": "Address"
}
]
},
{
"entityType": "Host",
"fieldMappings": [
{
"columnName": "DeviceNames",
"identifier": "HostName"
}
]
},
{
"entityType": "URL",
"fieldMappings": [
{
"columnName": "RequestURL",
"identifier": "Url"
}
]
}
],
"OriginalUri": "https://github.com/Azure/Azure-Sentinel/blob/master/Detections/MultipleDataSources/B64IPInURLFromMDE.yaml",
"query": "// Extracts plaintext IPv4 addresses\nlet ipv4_plaintext_extraction_regex = @\"((?:(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(?:\\.)){3}(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]){1,3})\";\n// Identified base64 encoded IPv4 addresses\nlet ipv4_encoded_identification_regex = @\"\\=([a-zA-Z0-9\\/\\+]*(?:(?:MC|Au|wL|MS|Eu|xL|Mi|Iu|yL|My|Mu|zL|NC|Qu|0L|NS|Uu|1L|Ni|Yu|2L|Ny|cu|3L|OC|gu|4L|OS|ku|5L){1}[a-zA-Z0-9\\/\\+]{2,4}){3}[a-zA-Z0-9\\/\\+\\=]*)\";\n// Extractes IPv4 addresses as hex values\nlet ipv4_decoded_hex_extract = @\"((?:(?:61|62|63|64|65|66|67|68|69|6a|6b|6c|6d|6e|6f|70|71|72|73|74|75|76|77|78|79|7a|41|42|43|44|45|46|47|48|49|4a|4b|4c|4d|4e|4f|50|51|52|53|54|55|56|57|58|59|5a|2f|2b|3d),){7,15})\";\nCommonSecurityLog\n| where isnotempty(RequestURL)\n// Identify requests with encoded IPv4 addresses\n| where RequestURL matches regex ipv4_encoded_identification_regex\n| project TimeGenerated, RequestURL\n// Extract IP candidates in their base64 encoded format, significantly reducing the dataset\n| extend extracted_encoded_ip_candidate = extract_all(ipv4_encoded_identification_regex, RequestURL)\n// We could have more than one candidate, expand them out\n| mv-expand extracted_encoded_ip_candidate to typeof(string)\n| summarize Start=min(TimeGenerated), End=max(TimeGenerated), make_set(RequestURL) by extracted_encoded_ip_candidate\n// Pad if we need to\n| extend extracted_encoded_ip_candidate = iff(strlen(extracted_encoded_ip_candidate) % 2 == 0, extracted_encoded_ip_candidate, strcat(extracted_encoded_ip_candidate, \"=\"))\n// Now decode the candidate to a long array, we cannot go straight to string as it cannot handle non-UTF8, we need to strip that first\n| extend extracted_encoded_ip_candidate = tostring(base64_decode_toarray(extracted_encoded_ip_candidate))\n// Extract the IP candidates from the array\n| extend hex_extracted = extract_all(ipv4_decoded_hex_extract, extracted_encoded_ip_candidate)\n// Expand, it's still possible that we might have more than 1 IP\n| mv-expand hex_extracted\n// Now we should have a clean string. We need to put it back into a dynamic array to convert back to a string.\n| extend hex_extracted = trim_end(\",\", tostring(hex_extracted))\n| extend hex_extracted = strcat(\"[\",hex_extracted,\"]\")\n| extend hex_extracted = todynamic(hex_extracted)\n| extend extracted_encoded_ip_candidate = todynamic(extracted_encoded_ip_candidate)\n// Convert the array back into a string\n| extend decoded_ip_candidate = make_string(hex_extracted)\n| summarize by decoded_ip_candidate, tostring(set_RequestURL), Start, End\n// Now the IP candidates will be in plaintext, extract the IPs using a regex\n| extend ipmatch = extract_all(ipv4_plaintext_extraction_regex, decoded_ip_candidate)\n// If it's not an IP, throw it out\n| where isnotnull(ipmatch)\n| mv-expand ipmatch to typeof(string)\n// Join with DeviceNetworkEvents to find instances where an IP of a machine in our MDE estate sent it's IP in a base64 encoded string\n| join (\n DeviceNetworkEvents\n | summarize make_set(DeviceId), make_set(DeviceName) by RemoteIP\n) on $left.ipmatch == $right.RemoteIP\n| project Start, End, IPmatch=ipmatch, RequestURL=set_RequestURL, DeviceNames=set_DeviceName, DeviceIds=set_DeviceId, RemoteIP\n",
"queryFrequency": "P1D",
"queryPeriod": "P1D",
"severity": "Medium",
"subTechniques": [
"T1071.001"
],
"suppressionDuration": "PT1H",
"suppressionEnabled": false,
"tactics": [
"CommandAndControl",
"Exfiltration"
],
"tags": [
"POLONIUM"
],
"techniques": [
"T1041",
"T1071"
],
"templateVersion": "1.0.2",
"triggerOperator": "GreaterThan",
"triggerThreshold": 0
},
"type": "Microsoft.OperationalInsights/workspaces/providers/alertRules"
}
]
}