Skip to content

Commit 07694b9

Browse files
committed
Land rapid7#7874: A login scanner for Advantech WebAccess
2 parents c59b5ea + b989675 commit 07694b9

File tree

4 files changed

+329
-0
lines changed

4 files changed

+329
-0
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
## Description
2+
3+
This module allows you to authenticate to Advantech WebAccess.
4+
5+
## Vulnerable Application
6+
7+
This module was specifically tested on versions 8.0, 8.1, and 8.2:
8+
9+
**8.2 Download**
10+
11+
http://advcloudfiles.advantech.com/web/Download/webaccess/8.0/AdvantechWebAccessUSANode8.0_20141103_3.4.3.exe
12+
13+
**8.1 Download**
14+
15+
http://advcloudfiles.advantech.com/web/Download/webaccess/8.1/AdvantechWebAccessUSANode8.1_20151230.exe
16+
17+
**8.0 Download**
18+
19+
http://advcloudfiles.advantech.com/web/Download/webaccess/8.0/AdvantechWebAccessUSANode8.0_20141103_3.4.3.exe
20+
21+
Note:
22+
23+
By default, Advantech WebAccess comes with a built-in account named ```admin```, with a blank
24+
password.
25+
26+
27+
## Verification Steps
28+
29+
1. Make sure Advantech WebAccess is up and running
30+
2. Start ```msfconsole```
31+
3. ```use auxiliary/scanner/http/advantech_webaccess_login```
32+
4. ```set RHOSTS [IP]```
33+
5. Set credentials
34+
6. ```run```
35+
7. You should see that the module is attempting to log in.
36+
37+
## Demo
38+
39+
![webaccess_login_demo](https://cloud.githubusercontent.com/assets/1170914/22352301/26549236-e3e1-11e6-9710-506166a8bee3.gif)
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
require 'metasploit/framework/login_scanner/http'
2+
3+
module Metasploit
4+
module Framework
5+
module LoginScanner
6+
7+
class AdvantechWebAccess < HTTP
8+
9+
DEFAULT_PORT = 80
10+
PRIVATE_TYPES = [ :password ]
11+
LOGIN_STATUS = Metasploit::Model::Login::Status # Shorter name
12+
13+
def check_setup
14+
uri = normalize_uri("#{uri}broadWeb/bwRoot.asp")
15+
16+
res = send_request({
17+
'method' => 'GET',
18+
'uri' => uri
19+
})
20+
21+
if res && res.body =~ /Welcome to Advantech WebAccess/i
22+
return true
23+
end
24+
25+
false
26+
end
27+
28+
def do_login(user, pass)
29+
uri = normalize_uri("#{uri}broadweb/user/signin.asp")
30+
31+
res = send_request({
32+
'method' => 'POST',
33+
'uri' => uri,
34+
'vars_post' =>
35+
{
36+
'page' => '/',
37+
'pos' => '',
38+
'remMe' => '',
39+
'submit1' => 'Login',
40+
'username' => user,
41+
'password' => pass
42+
}
43+
})
44+
45+
unless res
46+
return {status: LOGIN_STATUS::UNABLE_TO_CONNECT, proof: 'Connection timed out for signin.asp'}
47+
end
48+
49+
if res.headers['Location'] && res.headers['Location'] == '/broadweb/bwproj.asp'
50+
return {status: LOGIN_STATUS::SUCCESSFUL, proof: res.body}
51+
end
52+
53+
{status: LOGIN_STATUS::INCORRECT, proof: res.body}
54+
end
55+
56+
# Attempts to login to Advantech WebAccess.
57+
#
58+
# @param credential [Metasploit::Framework::Credential] The credential object
59+
# @return [Result] A Result object indicating success or failure
60+
def attempt_login(credential)
61+
result_opts = {
62+
credential: credential,
63+
status: Metasploit::Model::Login::Status::INCORRECT,
64+
proof: nil,
65+
host: host,
66+
port: port,
67+
protocol: 'tcp'
68+
}
69+
70+
begin
71+
result_opts.merge!(do_login(credential.public, credential.private))
72+
rescue ::Rex::ConnectionError => e
73+
# Something went wrong during login. 'e' knows what's up.
74+
result_opts.merge!(status: LOGIN_STATUS::UNABLE_TO_CONNECT, proof: e.message)
75+
end
76+
77+
Result.new(result_opts)
78+
end
79+
80+
end
81+
end
82+
end
83+
end
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
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/advantech_webaccess'
8+
require 'metasploit/framework/credential_collection'
9+
10+
class MetasploitModule < 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' => 'Advantech WebAccess Login',
20+
'Description' => %q{
21+
This module will attempt to authenticate to Advantech WebAccess.
22+
},
23+
'Author' => [ 'sinn3r' ],
24+
'License' => MSF_LICENSE,
25+
'DefaultOptions' =>
26+
{
27+
'RPORT' => 80
28+
}
29+
))
30+
31+
register_options(
32+
[
33+
OptString.new('TARGETURI', [true, 'The base path to Advantech WebAccess', '/']),
34+
OptBool.new('TRYDEFAULT', [false, 'Try the default credential admin:[empty]', false])
35+
], self.class)
36+
end
37+
38+
39+
def scanner(ip)
40+
@scanner ||= lambda {
41+
cred_collection = Metasploit::Framework::CredentialCollection.new(
42+
blank_passwords: datastore['BLANK_PASSWORDS'],
43+
pass_file: datastore['PASS_FILE'],
44+
password: datastore['PASSWORD'],
45+
user_file: datastore['USER_FILE'],
46+
userpass_file: datastore['USERPASS_FILE'],
47+
username: datastore['USERNAME'],
48+
user_as_pass: datastore['USER_AS_PASS']
49+
)
50+
51+
if datastore['TRYDEFAULT']
52+
print_status("Default credential admin:[empty] added to the credential queue for testing.")
53+
cred_collection.add_public('admin')
54+
cred_collection.add_private('')
55+
end
56+
57+
return Metasploit::Framework::LoginScanner::AdvantechWebAccess.new(
58+
configure_http_login_scanner(
59+
host: ip,
60+
port: datastore['RPORT'],
61+
cred_details: cred_collection,
62+
stop_on_success: datastore['STOP_ON_SUCCESS'],
63+
bruteforce_speed: datastore['BRUTEFORCE_SPEED'],
64+
connection_timeout: 5,
65+
http_username: datastore['HttpUsername'],
66+
http_password: datastore['HttpPassword'],
67+
uri: target_uri.path
68+
))
69+
}.call
70+
end
71+
72+
73+
def report_good_cred(ip, port, result)
74+
service_data = {
75+
address: ip,
76+
port: port,
77+
service_name: 'http',
78+
protocol: 'tcp',
79+
workspace_id: myworkspace_id
80+
}
81+
82+
credential_data = {
83+
module_fullname: self.fullname,
84+
origin_type: :service,
85+
private_data: result.credential.private,
86+
private_type: :password,
87+
username: result.credential.public,
88+
}.merge(service_data)
89+
90+
login_data = {
91+
core: create_credential(credential_data),
92+
last_attempted_at: DateTime.now,
93+
status: result.status,
94+
proof: result.proof
95+
}.merge(service_data)
96+
97+
create_credential_login(login_data)
98+
end
99+
100+
101+
def report_bad_cred(ip, rport, result)
102+
invalidate_login(
103+
address: ip,
104+
port: rport,
105+
protocol: 'tcp',
106+
public: result.credential.public,
107+
private: result.credential.private,
108+
realm_key: result.credential.realm_key,
109+
realm_value: result.credential.realm,
110+
status: result.status,
111+
proof: result.proof
112+
)
113+
end
114+
115+
def bruteforce(ip)
116+
scanner(ip).scan! do |result|
117+
case result.status
118+
when Metasploit::Model::Login::Status::SUCCESSFUL
119+
print_brute(:level => :good, :ip => ip, :msg => "Success: '#{result.credential}'")
120+
report_good_cred(ip, rport, result)
121+
when Metasploit::Model::Login::Status::UNABLE_TO_CONNECT
122+
vprint_brute(:level => :verror, :ip => ip, :msg => result.proof)
123+
report_bad_cred(ip, rport, result)
124+
when Metasploit::Model::Login::Status::INCORRECT
125+
vprint_brute(:level => :verror, :ip => ip, :msg => "Failed: '#{result.credential}'")
126+
report_bad_cred(ip, rport, result)
127+
end
128+
end
129+
end
130+
131+
def run_host(ip)
132+
unless scanner(ip).check_setup
133+
print_brute(:level => :error, :ip => ip, :msg => 'Target is not Advantech WebAccess')
134+
return
135+
end
136+
137+
bruteforce(ip)
138+
end
139+
140+
end
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
require 'spec_helper'
2+
require 'metasploit/framework/login_scanner/advantech_webaccess'
3+
4+
RSpec.describe Metasploit::Framework::LoginScanner::AdvantechWebAccess do
5+
6+
it_behaves_like 'Metasploit::Framework::LoginScanner::Base', has_realm_key: true, has_default_realm: false
7+
it_behaves_like 'Metasploit::Framework::LoginScanner::RexSocket'
8+
9+
subject do
10+
described_class.new
11+
end
12+
13+
let(:successful_auth_response) do
14+
res = Rex::Proto::Http::Response.new(302, 'Found')
15+
res.headers['Location'] = '/broadweb/bwproj.asp'
16+
res
17+
end
18+
19+
let(:fail_auth_response) do
20+
Rex::Proto::Http::Response.new(200, 'OK')
21+
end
22+
23+
describe '#attempt_login' do
24+
25+
context 'when the credential is valid' do
26+
let(:username) { 'user' }
27+
let(:password) { 'goddpass' }
28+
29+
before do
30+
allow_any_instance_of(Rex::Proto::Http::Client).to receive(:request_cgi).with(any_args)
31+
allow_any_instance_of(Rex::Proto::Http::Client).to receive(:send_recv).with(any_args).and_return(successful_auth_response)
32+
allow_any_instance_of(Rex::Proto::Http::Client).to receive(:set_config).with(any_args)
33+
allow_any_instance_of(Rex::Proto::Http::Client).to receive(:close)
34+
allow_any_instance_of(Rex::Proto::Http::Client).to receive(:connect)
35+
end
36+
37+
it 'returns a Result object indicating a successful login' do
38+
cred = Metasploit::Framework::Credential.new(public: username, private: password)
39+
result = subject.attempt_login(cred)
40+
expect(result).to be_kind_of(Metasploit::Framework::LoginScanner::Result)
41+
expect(result.status).to eq(Metasploit::Model::Login::Status::SUCCESSFUL)
42+
end
43+
end
44+
45+
context 'when the credential is invalid' do
46+
let(:username) { 'admin' }
47+
let(:password) { 'badpass' }
48+
49+
before(:example) do
50+
allow_any_instance_of(Rex::Proto::Http::Client).to receive(:request_cgi).with(any_args)
51+
allow_any_instance_of(Rex::Proto::Http::Client).to receive(:send_recv).with(any_args).and_return(fail_auth_response)
52+
allow_any_instance_of(Rex::Proto::Http::Client).to receive(:set_config).with(any_args)
53+
allow_any_instance_of(Rex::Proto::Http::Client).to receive(:close)
54+
allow_any_instance_of(Rex::Proto::Http::Client).to receive(:connect)
55+
end
56+
57+
it 'returns a Result object indicating a failed login' do
58+
cred = Metasploit::Framework::Credential.new(public: username, private: password)
59+
result = subject.attempt_login(cred)
60+
expect(result).to be_kind_of(Metasploit::Framework::LoginScanner::Result)
61+
expect(result.status).to eq(Metasploit::Model::Login::Status::INCORRECT)
62+
end
63+
end
64+
end
65+
66+
67+
end

0 commit comments

Comments
 (0)