Paranoid Penguin - Secure Anonymous FTP with vsftpd
Can you believe that in nearly four years of Paranoid Penguin columns, I've never talked about how to configure FTP services? This month I fix that, using my new favorite FTP server, Chris Evans' excellent vsftpd (Very Secure FTP Dæmon). Because my space here is limited and the best use of FTP is anonymous FTP, we focus on anonymous FTP. The FTP protocol's use of clear-text authentication makes it a terrible choice for anything but anonymous file transfer. But anonymous FTP is still plenty useful.
vsftpd is increasingly popular and is included with recent versions of Debian, SuSE, Fedora, Red Hat and other Linux distributions. This inclusion probably is because vsftpd provides a unique combination of security and convenience. It is easy to get up and running in a hurry, without having to make ugly security-vs.-expedience trade-offs.
Chris Evans created vsftpd with security as a central design goal, and its track record so far is impressive. In the nearly four years it's been available, as of this writing, vsftpd has had zero significant security vulnerabilities. Regardless of whether that's still true by the time you read this article, it speaks to vsftpd's excellent design philosophy, which borrows from OpenBSD's “secure by default, extra features disabled by default, minimal complexity overall” motto.
How minimalist is vsftpd? Its entire source tree is just over 1MB in size, fully uncompressed. The vsftpd executable itself is 80K.
As I mentioned, vsftpd now is a standard package on many Linux distributions. The usual advantages of binary packages apply: convenience, easy patching and minimal impact on other system software. In Debian, SuSE, Fedora and Red Hat, the package you need predictably is named vsftpd. It has no particularly exotic dependencies. Most users probably will be perfectly happy with their distribution's stock vsftpd package.
If your distribution of choice doesn't provide a binary package for vsftpd, or if you need a later version than the one your distribution provides, compile vsftpd from its source code tarball, which is available at vsftpd.beasts.org. The build process is decidedly old school. If you aren't already, become root. Then, unpack the tarball and change your working directory to its root, like this:
# tar -xf vsftpd-1.2.1.tar.gz; cd vsftpd-1.2.1
Next, enter the command make without arguments. If it succeeds, there should be a vsftp executable in the current directory. Make sure the user nobody exists; if it doesn't, create it. vsftpd runs on this account.
Create the directory /usr/share/empty if it doesn't exist already. It should be owned by root and be neither group- nor world-writable—it will be used as the default vsftpd chroot jail.
Create a home directory for the anonymous FTP user. SuSE conventionally uses /srv/ftp, and other distributions use /var/ftp, but it can be whatever you like. Again, this directory should be owned by root and should not be writable by anyone else.
Create an anonymous FTP user account, such as ftp, and make sure its home directory is set to the one you created in the previous step. Your system already may have such an account. The anonymous ftp user should not be able to write in its home directory, and it should never own any files or directories.
Now you're ready to copy vsftpd and the vsftpd(8) and vsftpd.conf(5) man pages into more useful locations, so enter the command make install. Manually copy the sample vsftpd.conf file into /etc.
If you want to run vsftpd as a standalone dæmon, create a startup script for vsftpd in /etc/init.d. Otherwise, configure either inetd or xinetd to start it up as needed (see the Standalone Dæmon vs. inetd/xinetd section).
If you're running vsftpd as a standalone dæmon, enable the startup script with chkconfig if you use an RPM-based Linux distribution or with update-rc.d if you run Debian GNU/Linux. Alternatively, if you install vsftpd from an RPM or deb package, all these steps are executed automatically, with the probable exception of the last one. Did I mention that binary packages are much more convenient? Some distributions require manual intervention to enable newly installed packages. For example, on my SuSE 9.0 system, although the SuSE vsftpd RPM automatically installed /etc/init.d/vsftpd, I had to issue the commands chkconfig --add vsftpd and chkconfig --level 35 vsftpd on to enable the script.
Before I begin a discussion of vsftpd that focuses narrowly on running it as a standalone dæmon serving up only anonymous FTP, I should point out some valuable, much more complete, sources of vsftpd documentation. First, vsftpd comes with an EXAMPLE/ directory containing sample configurations for a variety of FTP scenarios, including running standalone, running with xinetd, serving anonymous users only and serving local users. If you installed vsftpd from source code, EXAMPLE is a subdirectory of your vsftpd source code tarball. If you installed vsftpd from a binary package, it's probably been copied to your system somewhere under /user/share/doc. It is /usr/share/doc/packages/vsftpd/EXAMPLE on SuSE systems.
As I mentioned in the previous section, vsftpd has man pages, vsftpd(8) and vsftpd.conf(5). Finally, the default (sample) vsftpd.conf file itself is well commented. It doesn't contain all vsftpd options, but it does illustrate the most commonly used ones. I've successfully gotten vstpd to work several times with only minimal tweaking to the sample vsftpd.conf file.
Before configuring vsftpd itself, you must decide whether to run it as a standalone dæmon or by way of a super-server, inetd or xinetd. In previous versions of vsftpd, its developer recommended using it with xinetd due to xinetd's logging and access-control features. However, vsftpd versions 1.2 and later have native support for most of those features. For this reason, Evans now recommends that vsftpd be run as a standalone dæmon. In addition, a performance cost is associated with using inetd or xinetd. The cost isn't warranted if your system is to be a dedicated FTP server or if you anticipate FTP comprising a significant percentage of your system's activity.
I'm going to take the liberty of using standalone dæmon examples for the remainder of this article. vsftpd's included documentation amply describes how to use vsftpd with inetd and xinetd; see the example configurations included in vsftpd's EXAMPLE directory.
Interestingly, the vsftpd package that comes with SuSE 9 is preconfigured to run from xinetd, while Debian 3.0's runs from inetd. This is especially logical in the latter case, because Debian 3.0 comes with an older version of vsftpd (1.0.0), but SuSE 9.0 uses vsftpd 1.2. The vsftpd RPMs that come with Fedora and Red Hat install vsftpd as a standalone dæmon. At any rate, there are two steps to converting vsftpd from inet/xinetd startup to standalone startup.
First, as I mentioned in the Getting and Installing vsftpd section, you must make sure you've got an enabled startup script for vsftpd in /etc/init.d. The Fedora Core 1 and SuSE 9.0 packages both provide and install one; in SuSE's case it's present but disabled by default, in favor of xinetd. If you used Debian 3.0's vsftpd package or installed vsftpd from source, however, you need to create your own startup script. You also must create the corresponding links in the directories for the runlevels at which you want vsftpd to run, such as rc3.d and rc5.d. The last step is easy to do automatically with chkconfig or update-rc.d.
Second, you need either to disable vsftpd's xinetd file, by setting disable = yes in the file /etc/xinetd.d/vsftpd or to comment out vsftpd's line in /etc/inetd.conf. Alternatively, you can disable inetd or xinetd altogether, if vsftpd was the only important thing it was starting.
Arguably, it's irresponsible of me to recommend that you enable an application's startup script before you've fine-tuned that application's security. In my opinion, enabling is one thing; you're fine so long as you follow through and lock down the service before actually starting it or rebooting your system.
Third, you need to make sure that in /etc/vsftpd.conf the parameter listen is set to YES. This brings us to vsftpd configuration proper.
Actually, you may not need to do anything more to configure vsftpd for secure anonymous FTP. Its default configuration settings permit only anonymous FTP. What's more, no write commands of any kind are enabled by default, and in recent versions of vsftpd, the dæmon chroots itself to the directory /usr/share/empty whenever possible. This is one of the things I love about vsftpd. It actually takes more work to loosen its security than it does to tighten it down.
Assuming your distribution hasn't altered this default behavior, all you need to do now is populate your anonymous FTP user account's home directory with FTP content for people to download. On Debian 3.0, SuSE 9.0 and Fedora Core 1, the anonymous FTP user is ftp by default, with a home directory of /srv/ftp for Debian and SuSE and /var/ftp in the case of Fedora. If you installed vsftpd from source, the anonymous FTP directory is whatever home directory you assigned to the anonymous FTP user account you created. Pay special attention to ownership and permissions when populating your FTP directories. Defaults may or may not be appropriate, but at least do a quick ls -al now and then to see for yourself.
Even though default settings suffice for many users, let's take a closer look at the vsftpd.conf parameters most relevant to anonymous FTP. By default, this file resides in /etc, but on Red Hat and Fedora systems it resides in /etc/vsftpd/. Listing 1 shows a sample vsftpd.conf file.
Listing 1. vsftpd.conf Settings for Anonymous FTP
listen=YES # listen_address= anonymous_enable=YES ftp_username=ftp # anon_root=[$ftp_username's home directory] write_enable=NO anon_upload_enable=NO anon_mkdir_write_enable=NO anon_other_write_enable=NO anon_world_readable_only=YES anon_max_rate=0 idle_session_timeout=300 ascii_download_enable=NO ascii_upload_enable=NO connect_from_port_20=NO port_enable=YES hide_ids=NO log_ftp_protocol=NO syslog_enable=NO max_per_ip=0 # cmds_allowed= local_root=/usr/share/empty nopriv_user=nobody ftpd_banner=(vsFTPd 1.2.0)
In practice, you'd never use a vsftpd.conf file exactly like Listing 1. All parameters in it are, in fact, set to their default values. Rather, this listing is meant as a quick reference. Let's discuss its parameters in turn.
listen: tells vsftpd to run as a dæmon rather than as a per-connection process invoked as needed by inetd or xinetd. Default value is NO.
listen_address: specifies on which local IP address vsftpd should listen for connections. The default is "" (null), signifying all local IP addresses. If you want to run multiple virtual FTP servers, you need to set this parameter in each virtual server's configuration file (see the next section, Virtual Servers).
anonymous_enable: this parameter, whose default is YES, determines whether vsftpd accepts anonymous logins. If set to YES or not set at all, vsftpd accepts connections from the users anonymous and ftp (the two are equivalent) without requiring a real password.
ftp_username: the name of the user account used for anonymous logins, that is, FTP logins as anonymous and ftp. This account must exist in /etc/passwd and should have a valid home directory that is not owned by the user account; the default is ftp.
anon_root: the directory vsftpd should chroot into for anonymous logins. This defaults to the home directory of the anonymous ftp user account (see ftp_username), but you can use this parameter to set a different anonymous FTP root. Either way, this directory should not be owned by the anonymous ftp user.
write_enable: unless this parameter is set to YES, no user may upload any files under any circumstances, regardless of other settings in vsftpd.conf. Its default value is NO.
anon_upload_enable: if this parameter and write_enable are both set to YES, anonymous users are permitted to upload files into directories on which the anonymous user account has write permission.
anon_mkdir_write_enable: if this parameter and write_enable are both set to YES, anonymous users are permitted to create new directories within directories on which the anonymous user account has write permission.
anon_other_write_enable: if this parameter and write_enable are both set to YES, anonymous users are permitted to delete and rename directories within directories on which the anonymous user account has write permission.
anon_world_readable_only: if set to YES, this parameter forbids anonymous users from downloading any non-world-readable file. Most useful if anonymous users are able to upload files you don't want other anonymous users to download.
anon_max_rate: specifies the maximum data transfer rate, in bytes per second, that anonymous users can use. The default value is 0, which means unlimited.
idle_session_timeout: the maximum amount of time, in seconds, allowed to transpire between FTP commands until a session is closed forcibly by the server. Default value is 300, but if you're worried about denial-of-service attacks, you may want to set this lower.
ascii_download_enable: if set to YES, this allows users to perform ASCII-mode downloads, as opposed to binary-mode. The default is NO because ASCII-mode is seldom if ever necessary, and it's much less efficient, so much so as to represent a potential vector for denial-of-service attacks.
ascii_upload_enable: ASCII-mode uploads, on the other hand, are sometimes necessary for such things as scripts. This parameter's default value is, nonetheless, NO.
connect_from_port_20: in active-mode FTP sessions, whenever a user downloads anything, including directory listings, the server initiates a new connection back to the client, conventionally originating from the server's TCP port 20. By default, however, vsftpd originates such connections from a higher, non-privileged port, in order to avoid having to run as root. To change this default behavior, in case your FTP users connect from behind proxies or firewalls that don't expect such behavior, set this parameter to YES.
port_enable: set this to NO to disable PORT commands, which effectively disables active-mode FTP altogether. Default is YES.
hide_ids: if set to YES, replaces the owner and group fields in all directory listing output to ftp and ftp, respectively. Personally, I think this can be a useful bit of obscurity when used on public FTP servers, but the default is NO.
log_ftp_protocol: if set to YES, turns on per-command logging, FTP protocol commands, that is, triggered by but distinct from FTP user-space commands. Invaluable for troubleshooting.
syslog_enable: normally vsftpd writes log messages to /var/log/vsftpd.log. Setting this parameter to YES (its default is NO) sends those messages instead to the system's syslog service, using the FTPD facility.
max_per_ip: specifies the maximum number of concurrent connections permitted from a single source IP address. Limiting this may seem like a good idea—the default is 0, which means unlimited—but doing so has a disproportionate effect on users connecting from behind NAT/SPAT firewalls, which cause multiple users to appear to originate from the same source IP address.
cmds_allowed: specifies a comma-separated list of allowed FTP commands; default value is "" (null), which means unlimited. Only FTP protocol-level commands may be specified, not the commands commonly accepted by FTP client software packages. For example, to allow clients only to list files, change working directories and download files, you'd use cmds_allowed=USER,LIST,NLST,CWD,RETR,PORT,QUIT. The Web site www.nsftools.com/tips/RawFTP.htm is a useful reference for these commands.
local_root: this specifies an empty, root-owned directory to which vsftpd chroots itself any time it doesn't need access to other parts of the filesystem. Default value is /usr/share/empty.
nopriv_user: specifies the non-privileged user vsftpd runs as whenever possible. vsftpd obviously needs to be root when doing things like binding to TCP port 21. It demotes itself as soon as it can, however, in order to lessen the chance of a buffer-overflow vulnerability or other process-hijacking event leading to root compromise.
ftpd_banner: banner message to display when FTP clients attempt to connect. Default message is hard-coded into vsftpd; in v1.2.0, it's simply (vsFTPd 1.2.0). Alternatively, you can use the parameter banner_file to specify a text file containing your banner message.
The vsftpd.conf(5) man page explains these and many other parameters you can use. Believe it or not, I've only scratched the surface here.
If you want to have multiple virtual FTP servers residing on the same physical host, one with multiple IP addresses, vsftpd can do this easily. All you need to do is run multiple instances of the vsftpd dæmon, each with its own vsftpd.conf file specifying on which IP address to listen and which directory to use as its anonymous root.
For example, suppose I've got two IP addresses assigned to my machine, 1.2.3.4 and 1.2.3.5, registered in DNS to the names knusper and rover, respectively. In that case, I could have two configuration files for vsftpd, say, /etc/vsftpd.knusper and /etc/vsftpd.rover. Listings 2 and 3 show these files.
Listing 2. Virtual FTP Server Configuration File /etc/vsftpd.knusper
listen=YES listen_on=1.2.3.4 connect_from_port_20=YES anonymous_enable=YES anon_root=/srv/ftp/knusper ftpd_banner=Welcome to FTP at knusper.wiremonkeys.org. Behave!
Listing 3. Virtual FTP Server Configuration File /etc/vsftpd.rover
listen=YES listen_on=1.2.3.5 connect_from_port_20=YES anonymous_enable=NO ftpd_banner=Private FTP at rover.wiremonkeys.org. Strangers-B-gone. # DANGER: don't use the following unless you know what you're doing! local_enable=YES
Notice my possibly foolish use of the local_enable parameter in Listing 3. It's dangerous to set this to YES, because FTP logon credentials are sent in clear text. You never want to expose real system credentials to eavesdropping, especially if your server is Internet-connected. The real reason I show it here is to illustrate that because each virtual server uses its own configuration file, you can specify completely different behaviors for each. One virtual server may have a public uploads directory that anonymous users write to, whereas another may be a strictly read-only FTP site. Conversely, you need to take care that settings you consider to be important in preserving overall system security are set consistently between different virtual servers running on the same machine.
Besides creating different configuration files for each virtual FTP server you want vsftpd to serve up, you also need to alter your startup script accordingly. The startup script on my sample server, represented by Listings 2 and 3, would need something equivalent to these two lines:
vsftpd /etc/vsftpd.knusper vsftpd /etc/vsftpd.rover
If you run Red Hat or Fedora, this already has been taken care of for you. The /etc/init.d/vsftpd script included with those distributions' vsftpd RPM packages automatically parses the directory /etc/vsftpd for as many configuration files as you care to put there, so long as the filename of each ends with .conf. This strikes me as an excellent bit of foresight on the part of the Red Hat team.
That's all you need to know about setting up a simple and secure anonymous FTP server with vsftpd. As I mentioned, I've only covered a subset of what vsftpd is capable of doing. Despite its minimalist design philosophy, this is a powerful FTP server indeed. Fortunately, it's also well documented, so it's really no cop-out for me to refer you to the vsftpd.conf(5) man page and the EXAMPLE/ directory for information on the many other uses of vsftpd.
Mick Bauer, CISSP, is Linux Journal's security editor and an IS security consultant in Minneapolis, Minnesota. He's the author of Building Secure Servers With Linux (O'Reilly & Associates, 2002).