FTP
SFTP
WINSCP
STOR
DetectionBlue TeamSOCOctober 12, 2026•9 min read

Detecting FTP and SFTP Exfiltration

Old protocols still work. Very few hosts in a modern network have a reason to push files out over FTP or SFTP, so a short allowlist makes this a precise detection.

Scroll

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

SignalWhereATT&CK
Outbound 21, 22 or 990 from a host not on the transfer allowlistFirewall, Zeek conn.logT1048.003
STOR or PUT commands with large sizes to an external serverZeek ftp.logT1048.003
WinSCP, psftp, sftp or scp launched with script or batch optionsProcess creationT1048
curl or PowerShell performing a file upload to ftp:// or sftp://Process creation, script blockT1048
SSH-protocol session on port 22 moving bulk outbound bytesZeek conn.logT1048

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

Vetted sources

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 EngineeringManaged Detection & ResponseThreat HuntingIncident ResponsePurple Teaming
  • 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.
Written by: Mjolnir Threat Research (MTAC)  |  Published: October 12, 2026  |  Last reviewed: October 12, 2026