Detection rules › Kusto

Windows host username encoded in base64 web request

Severity
medium
Time window
1d
Group by
InitiatingProcessAccountName, base64_candidate, base64_user
Author
Thomas McElroy
Source
github.com/Azure/Azure-Sentinel

This detection will identify network requests in HTTP proxy data that contains Base64 encoded usernames from machines in the DeviceEvents table. This technique was seen usee by POLONIUM in their RunningRAT tool.

MITRE ATT&CK coverage

Event coverage

Rule body kusto

id: 6e715730-82c0-496c-983b-7a20c4590bd9
name: Windows host username encoded in base64 web request
description: |
  'This detection will identify network requests in HTTP proxy data that contains Base64 encoded usernames from machines in the DeviceEvents table.
  This technique was seen usee by POLONIUM in their RunningRAT tool.'
severity: Medium
requiredDataConnectors:
  - connectorId: Zscaler
    dataTypes:
      - CommonSecurityLog
  - connectorId: Fortinet
    dataTypes:
      - CommonSecurityLog
  - connectorId: CheckPoint
    dataTypes:
      - CommonSecurityLog
  - connectorId: PaloAltoNetworks
    dataTypes:
      - CommonSecurityLog
  - connectorId: MicrosoftThreatProtection
    dataTypes:
      - DeviceNetworkEvents
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
  - Exfiltration
  - CommandAndControl
relevantTechniques:
  - T1041
  - T1071.001
tags:
  - POLONIUM
query: |
  let accountLookback = 3d;
  let requestLookback = 3d;
  let extraction_regex = @"(?:\?|&)[a-zA-Z0-9\%]*=([a-zA-Z0-9\/\+\=]*)";
  // Collect account names and base64 encode them
  DeviceEvents
  | where TimeGenerated > ago(accountLookback)
  | summarize make_set(DeviceId), make_set(DeviceName) by InitiatingProcessAccountName
  | where isnotempty(InitiatingProcessAccountName)
  | extend base64_user = base64_encode_tostring(InitiatingProcessAccountName)
  | join (
      // Collect requests and extract base64 parameters
      CommonSecurityLog
      | where TimeGenerated > ago(requestLookback)
      | where isnotempty(RequestURL)
      // Summarize early on the RequestURL
      | summarize FirstRequest=min(TimeGenerated), LastRequest=max(TimeGenerated), NumberOfRequests=count() by RequestURL
      | extend base64_candidate = extract_all(extraction_regex, RequestURL)
      | mv-expand base64_candidate  to typeof(string)
  ) on $left.base64_user == $right.base64_candidate
  | project FirstRequest, LastRequest, NumberOfRequests, RequestURL, DeviceIds=set_DeviceId, DeviceNames=set_DeviceName, UserName=InitiatingProcessAccountName
entityMappings:
  - entityType: Host
    fieldMappings:
      - identifier: HostName
        columnName: DeviceNames
  - entityType: URL
    fieldMappings:
      - identifier: Url
        columnName: RequestURL
  - entityType: Account
    fieldMappings:
      - identifier: Name
        columnName: UserName
version: 1.0.1
kind: Scheduled
metadata:
    source:
        kind: Community
    author:
        name: Thomas McElroy
    support:
        tier: Community
    categories:
        domains: [ "Security - Others" ]

Stages and Predicates

Parameters

let accountLookback = 3d;
let requestLookback = 3d;

Let binding: extraction_regex

let extraction_regex = @"(?:\?|&)[a-zA-Z0-9\%]*=([a-zA-Z0-9\/\+\=]*)";

Stage 1: source

DeviceEvents

Stage 2: where

| where TimeGenerated > ago(accountLookback)

Stage 3: summarize

| summarize make_set(DeviceId), make_set(DeviceName) by InitiatingProcessAccountName

Stage 4: where

| where isnotempty(InitiatingProcessAccountName)

Stage 5: extend

| extend base64_user = base64_encode_tostring(InitiatingProcessAccountName)

Stage 6: join

| join (
    CommonSecurityLog
    | where TimeGenerated > ago(requestLookback)
    | where isnotempty(RequestURL)
    | summarize FirstRequest=min(TimeGenerated), LastRequest=max(TimeGenerated), NumberOfRequests=count() by RequestURL
    | extend base64_candidate = extract_all(extraction_regex, RequestURL)
    | mv-expand base64_candidate  to typeof(string)
) on $left.base64_user == $right.base64_candidate

Stage 7: project

| project FirstRequest, LastRequest, NumberOfRequests, RequestURL, DeviceIds=set_DeviceId, DeviceNames=set_DeviceName, UserName=InitiatingProcessAccountName

Indicators

Each row is a field, operator, and value that the rule matches. The corpus column counts how many other rules in the catalog look for the same combination: high numbers point to widely-used, community-vetted indicators. Blank or 1 shows that the indicator is specific to this rule.

FieldKindValues
InitiatingProcessAccountNameis_not_null
  • (no value, null check)
RequestURLis_not_null
  • (no value, null check)

Output fields

Fields the rule emits when it matches. Chronicle authors list these in the outcome block; they appear on the detection and $risk_score drives alerting. Sentinel / Defender XDR rules build them up through project / summarize / extend stages. Sentinel maps these into alert fields via entityMappings and customDetails; Defender XDR custom detections surface them as alert fields directly.

FieldSource
DeviceIdsproject
DeviceNamesproject
FirstRequestproject
LastRequestproject
NumberOfRequestsproject
RequestURLproject
UserNameproject