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 outbounf 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.1
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
queryFrequency: 1d
metadata:
  author:
    name: Thomas McElroy
  support:
    tier: Community
  categories:
    domains:
    - Security - Others
    - Networking
  source:
    kind: Community
triggerThreshold: 0
name: IP address of Windows host encoded in web request
version: 1.0.1
tags:
- POLONIUM
id: a4ce20ae-a2e4-4d50-b40d-d49f1353b6cc
tactics:
- Exfiltration
- CommandAndControl
entityMappings:
- entityType: IP
  fieldMappings:
  - columnName: IPmatch
    identifier: Address
- entityType: Host
  fieldMappings:
  - columnName: DeviceNames
    identifier: HostName
- entityType: URL
  fieldMappings:
  - columnName: RequestURL
    identifier: Url
queryPeriod: 1d
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 outbounf web request. This method of egressing the IP was seen used in POLONIUM's
  RunningRAT tool, however the detection is generic.'  
requiredDataConnectors:
- connectorId: Zscaler
  dataTypes:
  - CommonSecurityLog
- connectorId: Fortinet
  dataTypes:
  - CommonSecurityLog
- connectorId: CheckPoint
  dataTypes:
  - CommonSecurityLog
- connectorId: PaloAltoNetworks
  dataTypes:
  - CommonSecurityLog
- connectorId: MicrosoftThreatProtection
  dataTypes:
  - DeviceNetworkEvents
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  
kind: Scheduled
OriginalUri: https://github.com/Azure/Azure-Sentinel/blob/master/Detections/MultipleDataSources/B64IPInURLFromMDE.yaml
triggerOperator: gt
relevantTechniques:
- T1041
- T1071.001
severity: Medium
{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "workspace": {
      "type": "String"
    }
  },
  "resources": [
    {
      "apiVersion": "2023-02-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\njoins with DeviceNetworkEvents to idnetify any machine within the network using that IP address. Alerts indicate that the IP address of a machine\nwithin your network was seen with it's IP address base64 encoded in an outbounf web request. This method of egressing the IP was seen used in POLONIUM's\nRunningRAT 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",
        "suppressionDuration": "PT1H",
        "suppressionEnabled": false,
        "tactics": [
          "CommandAndControl",
          "Exfiltration"
        ],
        "tags": [
          "POLONIUM"
        ],
        "techniques": [
          "T1041",
          "T1071"
        ],
        "templateVersion": "1.0.1",
        "triggerOperator": "GreaterThan",
        "triggerThreshold": 0
      },
      "type": "Microsoft.OperationalInsights/workspaces/providers/alertRules"
    }
  ]
}