Detection rules › Kusto

Rouge RDP: Suspicious File Creation

Author
Cyb3rMonk
Source
github.com/Cyb3r-Monk/Threat-Hunting-and-Detection

Below query detects file creations of mstsc.exe where it also makes a network connection to a public IP address. This behavior is an indication of Rogue RDP.
False Positives: Copying files to the local machine over RDP may cause false positives.

MITRE ATT&CK coverage

References

Event coverage

Rule body kusto

// Author: Cyb3rMonk(https://twitter.com/Cyb3rMonk, https://mergene.medium.com)
// Description: Detect file creations of mstsc.exe where it also makes a network connection to a public IP address. This behavior is an indication of Rogue RDP. 
//
// Query parameters:
// there might be more file types that can be leveraged
// adjust file types to monitor
// consider DLL sideloading opportunities if you want to filter based on folders.
let file_types = dynamic([".dll", ".exe", ".cpl", ".pif", ".com", ".js", ".vbs", ".wsh", ".vbe", ".jse", ".bat", ".cmd", ".lnk", ".url", ".png", ".hta", ".svg"]);
let _query_period = 7d;
DeviceNetworkEvents
| where Timestamp > ago(_query_period)
| where ActionType in ("ConnectionSuccess")
| where InitiatingProcessFileName =~ "mstsc.exe"
| where RemoteIPType == "Public"
| project Timestamp, DeviceId, DeviceName, InitiatingProcessFileName=tolower(InitiatingProcessFileName), InitiatingProcessId, RemoteIP, RemoteUrl, RemotePort, NetworkTimestamp = Timestamp
| join kind=inner (
    DeviceFileEvents
    | where Timestamp > ago(_query_period)
    | where ActionType in ("FileCreated", "FileModified")
    | where InitiatingProcessFileName =~ "mstsc.exe" 
    | where FileName has_any (file_types)
    | extend InitiatingProcessFileName=tolower(InitiatingProcessFileName), FileTimestamp = Timestamp
    ) on InitiatingProcessFileName, DeviceId // InitiatingProcessId or InitiatingProcessUniqueId can be used as well but be mindful of multiple connections and telemetry sampling.
    // adjust time between RDP connection and file creation
    | where datetime_diff('minute', FileTimestamp, NetworkTimestamp) < 60

Stages and Predicates

Parameters

let file_types = dynamic([".dll", ".exe", ".cpl", ".pif", ".com", ".js", ".vbs", ".wsh", ".vbe", ".jse", ".bat", ".cmd", ".lnk", ".url", ".png", ".hta", ".svg"]);
let _query_period = 7d;

Stage 1: source

DeviceNetworkEvents

Stage 2: where

| where Timestamp > ago(_query_period)

Stage 3: where

| where ActionType in ("ConnectionSuccess")

Stage 4: where

| where InitiatingProcessFileName =~ "mstsc.exe"

Stage 5: where

| where RemoteIPType == "Public"

Stage 6: project

| project Timestamp, DeviceId, DeviceName, InitiatingProcessFileName=tolower(InitiatingProcessFileName), InitiatingProcessId, RemoteIP, RemoteUrl, RemotePort, NetworkTimestamp = Timestamp

Stage 7: join

| join kind=inner (
    DeviceFileEvents
    | where Timestamp > ago(_query_period)
    | where ActionType in ("FileCreated", "FileModified")
    | where InitiatingProcessFileName =~ "mstsc.exe" 
    | where FileName has_any (file_types)
    | extend InitiatingProcessFileName=tolower(InitiatingProcessFileName), FileTimestamp = Timestamp
    ) on InitiatingProcessFileName, DeviceId

Stage 8: where where FileTimestamp - NetworkTimestamp < 1h

| where datetime_diff('minute', FileTimestamp, NetworkTimestamp) < 60

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
ActionTypein
  • ConnectionSuccess transforms: cased corpus 9 (kusto 9)
  • FileCreated transforms: cased corpus 8 (kusto 8)
  • FileModified transforms: cased
FileNamematch
  • .bat corpus 2 (kusto 2)
  • .cmd corpus 2 (kusto 2)
  • .com corpus 2 (kusto 2)
  • .cpl corpus 2 (kusto 2)
  • .dll corpus 2 (kusto 2)
  • .exe corpus 2 (kusto 2)
  • .hta
  • .js
  • .jse
  • .lnk
  • .pif
  • .png
  • .svg
  • .url
  • .vbe
  • .vbs corpus 2 (kusto 2)
  • .wsh
InitiatingProcessFileNameeq
  • mstsc.exe
RemoteIPTypeeq
  • Public transforms: cased corpus 5 (kusto 5)

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
DeviceIdproject
DeviceNameproject
InitiatingProcessFileNameproject
InitiatingProcessIdproject
NetworkTimestampproject
RemoteIPproject
RemotePortproject
RemoteUrlproject
Timestampproject