amazon-ec2ftpvsftpd

vsftpd returns 0,0,0,0 in response to PASV


I set up an FTP server on AWS EC2 (Ubuntu16.04) with passive mode (PASV), but it doesn't work. However, it works with EPSV, don't know why. I searched around but find no answers, any body can help me with this?

1. vsftpd config

anonymous_enable=NO
local_enable=YES
write_enable=YES
chroot_local_user=YES
pasv_enable=YES
pasv_min_port=13000
pasv_max_port=13100
port_enable=YES
pasv_address=[public ip address of AWS EC2 instance]
allow_writeable_chroot=YES
seccomp_sandbox=NO

2. AWS EC2 Firewall

security groups (Inbound) setting

3. Test through FireFTP

Without IPV6 selected

With PASV mode, I cannot connect to FTP server, the log is:

220 (vsFTPd 3.0.3)
USER sensor
331 Please specify the password.
PASS (password not shown)
230 Login successful.
CWD /
250 Directory successfully changed.
TYPE A
200 Switching to ASCII mode.
PASV
QUIT

However, with it works with EPSV (with IPV6 checkbox selected), the log as below:

220 (vsFTPd 3.0.3)
USER sensor
331 Please specify the password.
PASS (password not shown)
230 Login successful.
PWD
257 "/" is the current directory
TYPE A
200 Switching to ASCII mode.
EPSV
229 Entering Extended Passive Mode (|||13082|)
LIST
150 Here comes the directory listing.
226 Directory send OK.

4. Test through Python ftplib

from ftplib import FTP
contents = []
ftp = FTP(host=xxx, timeout=3000)
ftp.login(user=xxx, passwd=xxx)
ftp.set_debuglevel(2)
ftp.retrlines("NLST", contents.append)
ftp.quit()

The log as below:

*cmd* 'TYPE A'
*put* 'TYPE A\r\n'
*get* '200 Switching to ASCII mode.\n'
*resp* '200 Switching to ASCII mode.'
*cmd* 'PASV'
*put* 'PASV\r\n'
*get* '227 Entering Passive Mode (0,0,0,0,50,245).\n'
*resp* '227 Entering Passive Mode (0,0,0,0,50,245).'
ConnectionRefusedError: [Errno 111] Connection refused

Solution

  • It looks like a bug in vsftpd to me.

    From the code it looks like, it will always send the 0,0,0,0, if the public pasv_address is set, but the server has a (local) IPv6 address.

    To fix this, make sure the server does not listen on IPv6 address (what is the default behavior, which you are overriding by setting listen_ipv6=YES):

    listen_ipv6=NO
    listen=YES
    

    The only other solution is removing a private IPv6 address, if it is possible in EC2.

    Or use another FTP server, e.g. ProFTPD.

    Or make ftplib ignore the IP address returned by the server.
    See Cannot list FTP directory using ftplib – but FTP client works


    To prove that this is indeed a bug, check this code of the latest vsftpd release (3.0.3):

    handle_pasv in postlogin.c:

    int is_ipv6 = vsf_sysutil_sockaddr_is_ipv6(p_sess->p_local_addr);
    
    ...
    
    if (tunable_pasv_address != 0)
    {
      vsf_sysutil_sockaddr_alloc_ipv4(&s_p_sockaddr);
      /* Report passive address as specified in configuration */
      if (vsf_sysutil_inet_aton(tunable_pasv_address, s_p_sockaddr) == 0)
      {
        die("invalid pasv_address");
      }
    }
    else
    {
      vsf_sysutil_sockaddr_clone(&s_p_sockaddr, p_sess->p_local_addr);
    }
    str_alloc_text(&s_pasv_res_str, "Entering Passive Mode (");
    if (!is_ipv6)
    {
      str_append_text(&s_pasv_res_str, vsf_sysutil_inet_ntop(s_p_sockaddr));
    }
    else
    {
      const void* p_v4addr = vsf_sysutil_sockaddr_ipv6_v4(s_p_sockaddr);
      if (p_v4addr)
      {
        str_append_text(&s_pasv_res_str, vsf_sysutil_inet_ntoa(p_v4addr));
      }
      else
      {
        str_append_text(&s_pasv_res_str, "0,0,0,0");
      }
    }
    

    where the vsf_sysutil_sockaddr_ipv6_v4 returns 0, if the s_p_sockaddr is not IPv6, what it never is, when the pasv_address is set.

    sysutil.c:

    const void*
    vsf_sysutil_sockaddr_ipv6_v4(const struct vsf_sysutil_sockaddr* p_addr)
    {
      static unsigned char pattern[12] =
          { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF };
      const unsigned char* p_addr_start;
      if (p_addr->u.u_sockaddr.sa_family != AF_INET6)
      {
        return 0;
      }
      if (vsf_sysutil_memcmp(pattern, &p_addr->u.u_sockaddr_in6.sin6_addr, 12))
      {
        return 0;
      }
      p_addr_start = (const unsigned char*)&p_addr->u.u_sockaddr_in6.sin6_addr;
      return &p_addr_start[12];
    }
    

    Imho, the code is wrong. It works (and makes sense), when the IP address is "autodetected" from p_sess->p_local_addr, but fails, when the pasv_address address is used.

    Consider reporting this to the author of vsftpd.


    Keeping an original explanation of the PASV vs. EPSV:

    Just to explain the difference between the PASV and the EPSV: The PASV returns an IP address in the response. That information is in 99.9% redundant. And it commonly causes problems, when the server is not aware of its external IP address.

    The EPSV was introduced later than the PASV, when it was clear that the IP address presence in the response is problematic. So with the EPSV, only a port number is included. And the client connects to the FTP server IP address implicitly.

    If the server really returns 0,0,0,0 in a response to the PASV command, it's clear why the client cannot connect to the server, when the PASV is used.