Origins
In this Sherlock, I will be performing network traffic analysis involving a FTP brute-force attack, post-authentication reconnaissance, and sensitive file exfiltration.
Sherlock Scenario
A major incident has recently occurred at Forela. Approximately 20 GB of data were stolen from internal s3 buckets and the attackers are now extorting Forela. During the root cause analysis, an FTP server was suspected to be the source of the attack. It was found that this server was also compromised and some data was stolen, leading to further compromises throughout the environment. You are provided with a minimal PCAP file. Your goal is to find evidence of brute force and data exfiltration.
| Sherlock | Origins |
|---|---|
| Category | DFIR |
| Difficulty | Very Easy |
| Creator | CyberJunkie |
| Released | 20th March, 2025 |
Resources
FTP is one of the oldest file transfer protocols still running in production environments today, and from a security standpoint it’s a goldmine.
- No encryption by default.
- Credentials in cleartext.
- Every command and response visible in plain text on the wire.
- An attacker’s entire session, what they downloaded, what they tried to upload, what they were looking for.
I’ve created a comprehensive cheatsheet which serves as a practical reference for anyone analyzing FTP traffic in Wireshark, whether you’re working through a CTF challenge, investigating a suspected breach, or just trying to build your blue team fundamentals. It covers the commands you’ll encounter on the wire and what they actually mean, the response codes that tell you whether an operation succeeded or failed and a ready-to-use set of Wireshark display filters.
The session we’re working through in this post is a real-world example involving a brute-force attack, post-authentication reconnaissance, and sensitive file exfiltration. By the end, you’ll be able to open a pcap and read an FTP session.
Let’s get into it. 😎
FTP Commands Cheatsheet
Auth & session
| Command | Direction | What it does |
|---|---|---|
| USER | Client→Server | Sends the username to log in |
| PASS | Client→Server | Sends the password — cleartext in plain FTP |
| QUIT | Client→Server | Graceful disconnect; server responds 221 |
| SYST | Client→Server | Asks the server OS type — leaks server info (e.g. UNIX Type: L8) |
| FEAT | Client→Server | Lists supported extensions — fingerprints server capabilities |
| AUTH TLS | Client→Server | Upgrades connection to TLS (FTPS). Absence = plaintext session |
Data channel setup
| Command | Direction | What it does |
|---|---|---|
| PORT | Client→Server | Active mode — client tells server which IP:port to connect back to |
| PASV | Client→Server | Passive mode — client asks server for an IP:port to connect to |
| EPSV | Client→Server | Extended passive mode (IPv4/IPv6) — response is 229 (\|PORT\|) |
| EPRT | Client→Server | Extended active mode — carries IP version, IP, and port explicitly |
File & directory operations
| Command | Direction | What it does |
|---|---|---|
| LIST | Client→Server | Long directory listing (like ls -l). Sent over data channel |
| NLST | Client→Server | Name-only listing — just filenames, no metadata |
| RETR | Client→Server | Download a file from the server |
| STOR | Client→Server | Upload a file to the server — watch for exfil or backdoor drops |
| STOU | Client→Server | Upload with unique auto-generated filename |
| APPE | Client→Server | Append data to an existing file |
| DELE | Client→Server | Delete a file on the server |
| REN | Client→Server | Rename file — uses RNFR (from) then RNTO (to) |
| MKD | Client→Server | Create directory |
| RMD | Client→Server | Remove directory |
| CWD | Client→Server | Change working directory |
| PWD | Client→Server | Print working directory — reveals remote file path structure |
| CDUP | Client→Server | Move up one directory level |
Transfer control
| Command | Direction | What it does |
|---|---|---|
| TYPE | Client→Server | Set transfer type: TYPE A = ASCII & TYPE I = Binary |
| SIZE | Client→Server | Get file size in bytes before downloading — useful to track exfil volume |
| MDTM | Client→Server | Get file last-modified timestamp in YYYYMMDDhhmmss format |
| REST | Client→Server | Resume transfer from a byte offset — indicates partial/chunked downloads |
| ABOR | Client→Server | Abort current transfer |
| NOOP | Client→Server | Keep-alive — server responds 200. Used to keep idle sessions alive |
| SITE | Client→Server | Server-specific commands (e.g. SITE CHMOD) — high-value in IR |
FTP Response Codes
1xx — Positive preliminary
| Code | Meaning | Context |
|---|---|---|
| 150 | File status OK, opening data connection | Seen before LIST, RETR, STOR transfers |
2xx — Positive completion
| Code | Meaning | Context |
|---|---|---|
| 200 | Command OK | Generic success (e.g. after TYPE I) |
| 211 | System status / feature list | Response to FEAT — lists supported extensions |
| 213 | File status | Used by SIZE and MDTM to return size/timestamp |
| 215 | OS name | Response to SYST — e.g. UNIX Type: L8 |
| 220 | Service ready / banner | First packet from server — may reveal software and version |
| 221 | Service closing | Goodbye after QUIT |
| 226 | Transfer complete | Data transfer finished successfully |
| 227 | Entering passive mode | Contains IP:port in (h1,h2,h3,h4,p1,p2) format — extract port |
| 229 | Entering extended passive mode | Contains (\|PORT\|) — port only, used with EPSV |
| 230 | Login successful | User authenticated |
| 250 | Requested action OK | After CWD, DELE, RMD, etc. |
| 257 | Path created / current directory | After MKD or PWD — reveals server path structure |
3xx — Positive intermediate
| Code | Meaning | Context |
|---|---|---|
| 331 | Username OK, need password | After USER — normal auth flow |
| 350 | Pending further info | After RNFR — waiting for RNTO to complete rename |
4xx — Transient negative (retriable)
| Code | Meaning | Context |
|---|---|---|
| 421 | Service unavailable | Server closing connection — timeout or shutdown |
| 425 | Can’t open data connection | Firewall/NAT issue with passive/active mode |
| 426 | Connection closed, transfer aborted | Interrupted mid-transfer |
| 430 | Invalid username or password | Seen in brute-force attempts (RFC 2228) |
5xx — Permanent negative
| Code | Meaning | Context |
|---|---|---|
| 500 | Syntax error, unrecognized command | Typo or unsupported command sent |
| 501 | Syntax error in arguments | Bad parameters for a valid command |
| 502 | Command not implemented | Server doesn’t support the requested command |
| 530 | Not logged in | Auth failed — key indicator in brute-force detection |
| 550 | No such file / permission denied | Access control — watch for repeated 550 on STOR (write attempts) |
| 553 | File name not allowed | Filename rejected by server policy |
Wireshark Filters
Authentication events
1
2
3
4
5
6
7
8
9
10
11
# Successful logins
ftp.response.code == 230
# Failed logins (not logged in / auth failure)
ftp.response.code == 530
# Any login failure codes (530 or 430)
ftp.response.code == 530 || ftp.response.code == 430
# Capture credentials (USER and PASS commands)
ftp.request.command == "USER" || ftp.request.command == "PASS"
File transfer activity
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# All downloads
ftp.request.command == "RETR"
# All uploads (potential exfil or web shell drops)
ftp.request.command == "STOR" || ftp.request.command == "STOU" || ftp.request.command == "APPE"
# File size lookups before transfer
ftp.request.command == "SIZE"
# File deletion
ftp.request.command == "DELE"
# Directory listing (reconnaissance)
ftp.request.command == "LIST" || ftp.request.command == "NLST"
Server fingerprinting
1
2
3
4
5
6
7
8
# Banner capture (first server packet)
ftp.response.code == 220
# OS fingerprinting via SYST
ftp.request.command == "SYST"
# Feature enumeration via FEAT
ftp.request.command == "FEAT"
Data channel & session
1
2
3
4
5
6
7
8
9
10
11
# Passive mode negotiations (find ephemeral data ports)
ftp.response.code == 227 || ftp.response.code == 229
# All FTP control channel traffic (port 21)
ftp
# FTP data channel traffic (follow the data stream)
ftp-data
# Complete session for a specific IP
ip.addr == <target_ip> && (ftp || ftp-data)
Permission denials & errors
1
2
3
4
5
# Permission denied responses
ftp.response.code == 550
# All 5xx errors (permanent failures)
ftp.response.code >= 500 && ftp.response.code < 600
Directory traversal suspicion
1
2
3
4
5
# CWD commands (watch for ../ sequences in argument)
ftp.request.command == "CWD"
# Path disclosure via PWD
ftp.request.command == "PWD" || ftp.response.code == 257
Solution
Question 1
What is the attacker’s IP address?
The Endpoints view (IPv4 tab) gives you a bird’s-eye summary of who is talking to whom and how much data moved. Most entries have about 2 packets and 112 bytes which is more of background noise. Two entries highlighted however have a high packet count:
Thought Process:
172.31.45.144 (The Internal Server)
The
172.31.0.0/12range is an RFC 1918 private address space, meaning this IP is sitting inside a local/internal network such as a home lab, a corporate LAN, a cloud VPC private subnet (AWS, for instance, assigns172.31.x.xto EC2 instances inside a default VPC). This is the FTP server being attacked. It has the higher packet count because it’s responding to every single one of the attacker’s requests including all those 530 rejection messages.15.206.185.207 (The External Attacker)
15.206.x.xis an ARIN-allocated public IP block belonging to Amazon Web Services, but crucially it is a public-facing address, not a private one. This is the machine on the other end of the internet making inbound connections to the internal server. The62 kBand444 packetsit generated represent the flood of brute-force authentication attempts. The fact that it’s also an AWS IP is worth noting as attackers frequently spin up cloud VMs to conduct attacks because they’re cheap, disposable, and harder to attribute.
Switching to the TCP tab breaks conversations down by port, and one row is unique:
Port 21 is the FTP control channel . This is where all the commands and responses travel (USER, PASS, 230, 530, etc.). The fact that 172.31.45.144:21 has 398 packets , nearly the entire IPv4 conversation volume tells you almost all traffic in this capture is FTP control plane chatter. That’s consistent with a brute-force scenario. Thousands of USER/PASS attempts generate enormous numbers of small control packets while producing almost no data channel activity.
Applying the ftp.response.code == 530 filter , you get Login incorrect responses, all flowing from 172.31.45.144 to 15.206.185.207. In FTP, a 530 is the server telling the client “Wrong credentials” . So the server (172.31.45.144) is rejecting repeated login attempts coming from 15.206.185.207. That pattern (dozens of 530s in a matter of seconds across a two-to-three minute window), is a clear indicator of a FTP brute-force attack.
15.206.185.207
Question 2
It’s critical to get more knowledge about the attackers, even if it’s low fidelity. Using the geolocation data of the IP address used by the attackers, what city do they belong to?
https://ipinfo.io/15.206.185.207?lookup_source=search-bar
Mumbai
Question 3
Which FTP application was used by the backup server? Enter the full name and version. (Format: Name Version)
1
ftp.response.code == 220
Code Meaning Context 220 Service ready / banner First packet from server (may reveal software and version)
vsFTPd 3.0.5
Question 4
The attacker has started a brute force attack on the server. When did this attack start?
1
(ip.src == 15.206.185.207) && (ftp.request.command == "USER")
2024-05-03 04:12:54
Question 5
What are the correct credentials that gave the attacker access? (Format username:password)
1 2 3 4 5 # Success ftp.response.code == 230 # Login incorrect ftp.response.code == 530
Filter for Successful Logins and Follow TCP Stream as shown:
You will notice the username & password used to successfully log into the FTP server.
forela-ftp:ftprocks69$
Question 6
The attacker has exfiltrated files from the server. What is the FTP command used to download the remote files?
Apply the following filter to show potential exfiltration attempts:
1
ftp.request.command == "RETR"
Note:
Command Direction What it does RETR Client→Server Download a file from the server
To download the files, click on File > Export Objects > FTP-DATA
Save All
Command Used: RETR
Question 7
Attackers were able to compromise the credentials of a backup SSH server. What is the password for this SSH server?
Inspecting the PDF extracted reveals the SSH temporary password mentioned in the Contingency Plan
**B@ckup2024!**
Question 8
What is the s3 bucket URL for the data archive from 2023?
Inspecting the s3_buckets.txt file reveals the s3 bucket URL containing bulk data from 2023
https://2023-coldstorage.s3.amazonaws.com
Question 9
The scope of the incident is huge as Forela’s s3 buckets were also compromised and several GB of data were stolen and leaked. It was also discovered that the attackers used social engineering to gain access to sensitive data and extort it. What is the internal email address used by the attacker in the phishing email to gain access to sensitive data stored on s3 buckets?
Inspecting the s3_buckets.txt file reveals an internal email address likely used by the attacker in the phishing email.
`archivebackups@forela.co.uk
