Adversaries may use Discord CDN to exfiltrate data or deploy malicious payloads by downloading risky file types, leveraging the covert communication channel for command and control. SOC teams should proactively hunt for this behavior in Azure Sentinel to identify potential exfiltration or compromise activities early.
KQL Query
let discord=dynamic(["cdn.discordapp.com", "media.discordapp.com"]);
_Im_WebSession(url_has_any=discord, eventresult='Success')
| where Url has "attachments"
| extend DiscordServerId = extract(@"\/attachments\/([0-9]+)\/", 1, Url)
| summarize dcount(Url), make_set(SrcUsername), make_set(SrcIpAddr), make_set(Url), min(TimeGenerated), max(TimeGenerated), make_set(EventResult) by DiscordServerId
| mv-expand set_SrcUsername to typeof(string), set_Url to typeof(string), set_EventResult to typeof(string), set_SrcIpAddr to typeof(string)
| summarize by DiscordServerId, dcount_Url, set_SrcUsername, min_TimeGenerated, max_TimeGenerated, set_EventResult, set_SrcIpAddr, set_Url
| project StartTime=min_TimeGenerated, EndTime=max_TimeGenerated, Result=set_EventResult, SourceUser=set_SrcUsername, SourceIP=set_SrcIpAddr, RequestURL=set_Url
| where RequestURL has_any (".bin",".exe",".dll",".bin",".msi")
| extend AccountName = tostring(split(SourceUser, "@")[0]), AccountUPNSuffix = tostring(split(SourceUser, "@")[1])
id: 01e8ffff-dc0c-43fe-aa22-d459c4204553
name: Discord CDN Risky File Download (ASIM Web Session Schema)
description: |
'Identifies callouts to Discord CDN addresses for risky file extensions. This detection will trigger when a callout for a risky file is made to a discord server that has only been seen once in your environment.
Unique discord servers are identified using the server ID that is included in the request URL (DiscordServerId in query). Discord CDN has been used in multiple campaigns to download additional payloads.
This analytic rule uses [ASIM](https://aka.ms/AboutASIM) and supports any built-in or custom source that supports the ASIM WebSession schema (ASIM WebSession Schema)'
severity: Medium
requiredDataConnectors:
- connectorId: SquidProxy
dataTypes:
- SquidProxy_CL
- connectorId: Zscaler
dataTypes:
- CommonSecurityLog
queryFrequency: 1d
queryPeriod: 1d
triggerOperator: gt
triggerThreshold: 0
tactics:
- CommandAndControl
relevantTechniques:
- T1071.001
tags:
- Discord
query: |
let discord=dynamic(["cdn.discordapp.com", "media.discordapp.com"]);
_Im_WebSession(url_has_any=discord, eventresult='Success')
| where Url has "attachments"
| extend DiscordServerId = extract(@"\/attachments\/([0-9]+)\/", 1, Url)
| summarize dcount(Url), make_set(SrcUsername), make_set(SrcIpAddr), make_set(Url), min(TimeGenerated), max(TimeGenerated), make_set(EventResult) by DiscordServerId
| mv-expand set_SrcUsername to typeof(string), set_Url to typeof(string), set_EventResult to typeof(string), set_SrcIpAddr to typeof(string)
| summarize by DiscordServerId, dcount_Url, set_SrcUsername, min_TimeGenerated, max_TimeGenerated, set_EventResult, set_SrcIpAddr, set_Url
| project StartTime=min_TimeGenerated, EndTime=max_TimeGenerated, Result=set_EventResult, SourceUser=set_SrcUsername, SourceIP=set_SrcIpAddr, RequestURL=set_Url
| where RequestURL has_any (".bin",".exe",".dll",".bin",".msi")
| extend AccountName = tostring(split(SourceUser, "@")[0]), AccountUPNSuffix = tostring(split(SourceUser, "@")[1])
entityMappings:
- entityType: Account
fieldMappings:
- identifier: FullName
columnName: SourceUser
- identifier: Name
columnName: AccountName
- identifier: UPNSuffix
columnName: AccountUPNSuffix
- entityType: IP
fieldMappings:
- identifier: Address
columnName: SourceIP
- entityType: URL
fieldMappings:
- identifier: Url
columnName: RequestURL
version: 1.1.4
kind: Scheduled
metadata:
source:
kind: Community
author:
name: Microsoft Security Research
support:
tier: Community
categories:
domains: [ "Security - Threat Protection" ]
Adversaries may communicate using application layer protocols associated with web traffic to avoid detection/network filtering by blending in with existing traffic. Commands to the remote system, and
Scenario: Legitimate file download by a developer using a CI/CD pipeline
Description: A developer uses a CI/CD tool like Jenkins or GitHub Actions to download a required dependency from a Discord CDN, which happens to have a risky file extension (e.g., .exe).
Filter/Exclusion: Check for known CI/CD tools (e.g., process.name = "jenkins.exe" or process.name = "github-runner"), and exclude file downloads from trusted development environments or specific IP ranges used by the CI/CD system.
Scenario: Scheduled system update or patching task using a script
Description: A scheduled task (e.g., via Task Scheduler or Ansible) runs a script that downloads a patch or update from a Discord CDN, which includes a file with a risky extension.
Filter/Exclusion: Exclude file downloads initiated by scheduled tasks (e.g., process.name = "schtasks.exe" or process.name = "ansible"), or filter by the specific script name or command line arguments used for patching.
Scenario: Internal tool or admin task using Discord CDN for asset retrieval
Description: An internal admin tool or script (e.g., a custom PowerShell script or a Python-based tool) uses Discord CDN to fetch a file (e.g., a configuration file or media asset) with a risky extension.
Filter/Exclusion: Exclude file downloads from known internal tools (e.g., process.name = "powershell.exe" with specific command-line arguments, or process.name = "python" with known internal scripts).
Scenario: User downloading a legitimate file with a risky extension via a trusted link
Description: A user clicks on a link (e.g., from a trusted internal portal) that points to a Discord CDN and downloads a file (e.g., a .zip containing a