Microsoft Sentinel Analytic Rules
cloudbrothers.infoAzure Sentinel RepoToggle Dark/Light/Auto modeToggle Dark/Light/Auto modeToggle Dark/Light/Auto modeBack to homepage

IP address of Windows host encoded in web request

Back
Ida4ce20ae-a2e4-4d50-b40d-d49f1353b6cc
RulenameIP address of Windows host encoded in web request
DescriptionThis 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.
SeverityMedium
TacticsExfiltration
CommandAndControl
TechniquesT1041
T1071.001
Required data connectorsCheckPoint
Fortinet
MicrosoftThreatProtection
PaloAltoNetworks
Zscaler
KindScheduled
Query frequency1d
Query period1d
Trigger threshold0
Trigger operatorgt
Source Urihttps://github.com/Azure/Azure-Sentinel/blob/master/Detections/MultipleDataSources/B64IPInURLFromMDE.yaml
Version1.0.2
Arm templatea4ce20ae-a2e4-4d50-b40d-d49f1353b6cc.json
Deploy To Azure
// 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"
    }
  ]
}