File-transfer protocols remain a quiet exfiltration route because they are simple, scriptable and often open on the egress firewall. The key is scoping: define which systems are permitted to send files out over ports 21, 22 and 990, and treat everything else as an alert. That inverts a noisy hunt into a short exception list.
Signals at a Glance
| Signal | Where | ATT&CK |
|---|---|---|
| Outbound 21, 22 or 990 from a host not on the transfer allowlist | Firewall, Zeek conn.log | T1048.003 |
| STOR or PUT commands with large sizes to an external server | Zeek ftp.log | T1048.003 |
| WinSCP, psftp, sftp or scp launched with script or batch options | Process creation | T1048 |
| curl or PowerShell performing a file upload to ftp:// or sftp:// | Process creation, script block | T1048 |
| SSH-protocol session on port 22 moving bulk outbound bytes | Zeek conn.log | T1048 |
Network-Level Detection
Maintain an explicit list of hosts that may send files out, usually a managed-file-transfer gateway and a handful of integration servers. Alert on any other source:
// Sentinel (KQL) - outbound FTP/SFTP/FTPS from non-allowlisted sources
// tune/validate: populate the allowlist from your MFT inventory
let allowed = dynamic(["10.20.5.10","10.20.5.11"]);
CommonSecurityLog
| where TimeGenerated > ago(1d)
| where DeviceAction == "allow" and DestinationPort in (21, 22, 990)
| where ipv4_is_private(DestinationIP) == false
| where SourceIP !in (allowed)
| summarize sessions = count(), bytesOut = sum(SentBytes), dests = dcount(DestinationIP)
by SourceIP, DestinationPort
| where bytesOut > 10000000 or sessions > 20
| order by bytesOut desc
Plain FTP is easier to inspect than SFTP because Zeek parses its command channel. Uploads are STOR commands, and the reply code tells you whether they succeeded:
# Zeek ftp.log fields: ts uid id.orig_h id.resp_h user command arg mime_type file_size reply_code
# Zeek - extract external uploads from ftp.log (zeek-cut)
cat ftp.log | zeek-cut ts id.orig_h id.resp_h user command arg file_size reply_code \
| awk '$5=="STOR" || $5=="APPE" {print}' \
| awk '$3 !~ /^(10[.]|192[.]168[.]|172[.](1[6-9]|2[0-9]|3[01])[.])/'
# tune/validate: SFTP rides SSH and is opaque; use conn.log byte counts on port 22 instead
Endpoint Command Lines
- WinSCP with
/script=or/command, orpsftp -bwith a batch file, indicates unattended transfer. ftp.exe -s:file.txtis a classic scripted pattern; the script file often holds the credentials in clear text.curl -Torcurl --upload-fileto an ftp:// URL, and PowerShell usingSystem.Net.WebClient.UploadFileorFtpWebRequest.- Alert harder when the sending process is a scripting engine or a user-writable binary, and when the destination is a residential or hosting-provider address.
SigmaHQ rules for WinSCP, ftp.exe and curl upload patterns, Zeek ftp and conn log semantics, and MITRE CAR guidance on non-standard protocol use. Baseline your own environment — managed file transfer and vendor integrations are the legitimate users, and they should be named.
Build Detections That Fire
Mjolnir Security helps blue teams turn threat intelligence into high-fidelity detections across endpoint, network, identity and cloud telemetry.
- Detection Engineering We build and tune detections mapped to ATT&CK from your own telemetry.
- Threat Hunting as a Service Hypothesis-driven hunts for the behaviours on this page.
- 24/7 Incident Response Containment when a detection fires. Call +1 833 403 5875.
Stay ahead of emerging threats. Get notified when we publish new intelligence reports and advisories.
Subscribe to Alerts