Post

Origins

In this Sherlock, I will be performing network traffic analysis involving a FTP brute-force attack, post-authentication reconnaissance, and sensitive file exfiltration.

Origins

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.

SherlockOrigins
CategoryDFIR
DifficultyVery Easy
CreatorCyberJunkie
Released20th 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

CommandDirectionWhat it does
USERClient→ServerSends the username to log in
PASSClient→ServerSends the password — cleartext in plain FTP
QUITClient→ServerGraceful disconnect; server responds 221
SYSTClient→ServerAsks the server OS type — leaks server info (e.g. UNIX Type: L8)
FEATClient→ServerLists supported extensions — fingerprints server capabilities
AUTH TLSClient→ServerUpgrades connection to TLS (FTPS). Absence = plaintext session
   

Data channel setup

CommandDirectionWhat it does
PORTClient→ServerActive mode — client tells server which IP:port to connect back to
PASVClient→ServerPassive mode — client asks server for an IP:port to connect to
EPSVClient→ServerExtended passive mode (IPv4/IPv6) — response is 229 (\|PORT\|)
EPRTClient→ServerExtended active mode — carries IP version, IP, and port explicitly
   

File & directory operations

CommandDirectionWhat it does
LISTClient→ServerLong directory listing (like ls -l). Sent over data channel
NLSTClient→ServerName-only listing — just filenames, no metadata
RETRClient→ServerDownload a file from the server
STORClient→ServerUpload a file to the server — watch for exfil or backdoor drops
STOUClient→ServerUpload with unique auto-generated filename
APPEClient→ServerAppend data to an existing file
DELEClient→ServerDelete a file on the server
RENClient→ServerRename file — uses RNFR (from) then RNTO (to)
MKDClient→ServerCreate directory
RMDClient→ServerRemove directory
CWDClient→ServerChange working directory
PWDClient→ServerPrint working directory — reveals remote file path structure
CDUPClient→ServerMove up one directory level

Transfer control

CommandDirectionWhat it does
TYPEClient→ServerSet transfer type:  TYPE A = ASCII & TYPE I = Binary
SIZEClient→ServerGet file size in bytes before downloading — useful to track exfil volume
MDTMClient→ServerGet file last-modified timestamp in YYYYMMDDhhmmss format
RESTClient→ServerResume transfer from a byte offset — indicates partial/chunked downloads
ABORClient→ServerAbort current transfer
NOOPClient→ServerKeep-alive — server responds 200. Used to keep idle sessions alive
SITEClient→ServerServer-specific commands (e.g. SITE CHMOD) — high-value in IR

FTP Response Codes

1xx — Positive preliminary

CodeMeaningContext
150File status OK, opening data connectionSeen before LISTRETRSTOR transfers

2xx — Positive completion

CodeMeaningContext
200Command OKGeneric success (e.g. after TYPE I)
211System status / feature listResponse to FEAT — lists supported extensions
213File statusUsed by SIZE and MDTM to return size/timestamp
215OS nameResponse to SYST — e.g. UNIX Type: L8
220Service ready / bannerFirst packet from server — may reveal software and version
221Service closingGoodbye after QUIT
226Transfer completeData transfer finished successfully
227Entering passive modeContains IP:port in (h1,h2,h3,h4,p1,p2) format — extract port
229Entering extended passive modeContains (\|PORT\|) — port only, used with EPSV
230Login successfulUser authenticated
250Requested action OKAfter CWDDELERMD, etc.
257Path created / current directoryAfter MKD or PWD — reveals server path structure

3xx — Positive intermediate

CodeMeaningContext
331Username OK, need passwordAfter USER — normal auth flow
350Pending further infoAfter RNFR — waiting for RNTO to complete rename

4xx — Transient negative (retriable)

CodeMeaningContext
421Service unavailableServer closing connection — timeout or shutdown
425Can’t open data connectionFirewall/NAT issue with passive/active mode
426Connection closed, transfer abortedInterrupted mid-transfer
430Invalid username or passwordSeen in brute-force attempts (RFC 2228)

5xx — Permanent negative

CodeMeaningContext
500Syntax error, unrecognized commandTypo or unsupported command sent
501Syntax error in argumentsBad parameters for a valid command
502Command not implementedServer doesn’t support the requested command
530Not logged inAuth failed — key indicator in brute-force detection
550No such file / permission deniedAccess control — watch for repeated 550 on STOR (write attempts)
553File name not allowedFilename 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:

image

Thought Process:

172.31.45.144 (The Internal Server)

The 172.31.0.0/12 range 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, assigns 172.31.x.x to 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.x is 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. The 62 kB and 444 packets it 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:

image

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.

image

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

image

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
CodeMeaningContext
220Service ready / bannerFirst packet from server (may reveal software and version)

image

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")

image

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:

image

You will notice the username & password used to successfully log into the FTP server.

image

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:

CommandDirectionWhat it does
RETRClient→ServerDownload a file from the server

image

To download the files, click on File > Export Objects > FTP-DATA

image

Save All

image

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

image

**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

imaage

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.

image

`archivebackups@forela.co.uk

This post is licensed under CC BY 4.0 by the author.