↳ Source GitHubRègle analytiqueMedium

Detect presence of private IP addresses in URLs (ASIM Web Session)

Description

'This rule identifies requests made to atypical URLs, as malware can exploit IP addresses for communication with command-and-control (C2) servers. The detection identifies network requests that contain either plain text or Base64 encoded IP addresses. Alerts are triggered when a private IP address is observed as plain text or base64 encoded in an outbound web request. This method of concealing the IP address was observed in the utilization of the RunningRAT tool by POLONIUM.'
Type de règle
Scheduled
Version
1.0.1
Statut déclaré
Available
Fréquence
1h
Période analysée
1h
Déclenchement
gt 0

Couverture MITRE déclarée

Requête KQL

Requête originale, sans modification.

let lookback = 1h;
// 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),){6,14}(?: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))";
let ipV4_Private_FromPlainString = _Im_WebSession(starttime=ago(lookback), eventresult='Success')
    | where isnotempty(Url)
    | extend ip_inURL = extract(@"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b", 0, Url)
    | where ipv4_is_private(ip_inURL)
    | where not(ipv4_is_private(DstIpAddr)) // only take traffic going to internet
    | summarize
        EventCount=count(),
        EventEndTime = max(TimeGenerated),
        EventStartTime = min(TimeGenerated),
        Urls=tostring(make_set(Url, 100))
        by SrcIpAddr, SrcUsername, SrcHostname, ip_inURL;
let ipV4_Private_FromEncodedString = 
    _Im_WebSession(starttime=ago(lookback), eventresult='Success')
    | where isnotempty(Url)
    // Identify requests with encoded IPv4 addresses
    | where Url matches regex ipv4_encoded_identification_regex
    | where not(ipv4_is_private(DstIpAddr)) // only take traffic going to internet
    | project TimeGenerated, Url, SrcIpAddr, SrcUsername, SrcHostname
    // Extract IP candidates in their base64 encoded format, significantly reducing the dataset
    | extend extracted_encoded_ip_inURL = extract_all(ipv4_encoded_identification_regex, Url)
    // We could have more than one ip, expand them out
    | mv-expand extracted_encoded_ip_inURL to typeof(string)
    | summarize
        EventStartTime=min(TimeGenerated),
        EventEndTime=max(TimeGenerated),
        make_set(Url, 100),
        EventCount=count()
        by extracted_encoded_ip_inURL, SrcIpAddr, SrcUsername, SrcHostname
    // Pad if we need to
    | extend extracted_encoded_ip_inURL = iff(strlen(extracted_encoded_ip_inURL) % 2 == 0, extracted_encoded_ip_inURL, strcat(extracted_encoded_ip_inURL, "="))
    // 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_inURL = tostring(base64_decode_toarray(extracted_encoded_ip_inURL))
    // Extract the IP candidates from the array
    | extend hex_extracted = extract_all(ipv4_decoded_hex_extract, extracted_encoded_ip_inURL)
    // 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)
    // Convert the array back into a string
    | extend decoded_ip_inURL = unicode_codepoints_to_string(hex_extracted)
    | where ipv4_is_private(decoded_ip_inURL)
    | project
        ip_inURL=decoded_ip_inURL,
        Urls=tostring(set_Url),
        EventStartTime,
        EventEndTime,
        EventCount,
        SrcIpAddr,
        SrcUsername,
        SrcHostname;
union ipV4_Private_FromPlainString, ipV4_Private_FromEncodedString
| extend Name = iif(SrcUsername contains "@", tostring(split(SrcUsername,'@',0)[0]),SrcUsername), UPNSuffix = iif(SrcUsername contains "@",tostring(split(SrcUsername,'@',1)[0]),"")

Entités déclarées

IPAccountHost

Contenus associés

Liens établis à partir des identifiants déclarés et des manifests des solutions.

Traçabilité de la source

GitHub

Les valeurs affichées proviennent des fichiers du dépôt Azure/Azure-Sentinel. Elles décrivent le modèle publié, pas la configuration de votre workspace.

Identifiant source
e3a7722a-e099-45a9-9afb-6618e8f05405
Autres fichiers source 2Solutions/Web Session Essentials/Analytic Rules/PrivateIPInURL.yamlsource ↗Solutions/Web Session Essentials/Data/Solution_Web Session Essentials.jsonsolution-membership ↗
GSTEP / SUIVI DU CATALOGUE

Ajouté au catalogue : 16 sept. 2026 · 05:49 UTC
Dernier changement observé : 16 sept. 2026 · 05:49 UTC

Dates de synchronisation GSTEP, distinctes des dates de publication du contenu source.