Detection rules › Kusto
Rouge RDP: Suspicious File Creation
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
| Tactic | Techniques |
|---|---|
| Persistence | T1556 Modify Authentication Process |
| Defense Impairment | T1556 Modify Authentication Process |
| Credential Access | T1556 Modify Authentication Process |
References
Event coverage
| Provider | Event/ActionType | Title |
|---|---|---|
| Sysmon | Event ID 3 | Network connection |
| Security-Auditing | Event ID 5156 | The Windows Filtering Platform has permitted a connection. |
| Defender-DeviceNetworkEvents | ConnectionSuccess | Connection succeeded |
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.
| Field | Kind | Values |
|---|---|---|
ActionType | in |
|
FileName | match |
|
InitiatingProcessFileName | eq |
|
RemoteIPType | eq |
|
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.
| Field | Source |
|---|---|
DeviceId | project |
DeviceName | project |
InitiatingProcessFileName | project |
InitiatingProcessId | project |
NetworkTimestamp | project |
RemoteIP | project |
RemotePort | project |
RemoteUrl | project |
Timestamp | project |