summaryrefslogtreecommitdiffstats
path: root/scripts/rdp-vuln-ms12-020.nse
blob: bf560e441b428b98b05b3fda148661fa3ca3b216 (plain)
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
local nmap = require "nmap"
local shortport = require "shortport"
local stdnse = require "stdnse"
local string = require "string"
local vulns = require "vulns"

description = [[
Checks if a machine is vulnerable to MS12-020 RDP vulnerability.

The Microsoft bulletin MS12-020 patches two vulnerabilities: CVE-2012-0152
which addresses a denial of service vulnerability inside Terminal Server, and
CVE-2012-0002 which fixes a vulnerability in Remote Desktop Protocol. Both are
part of Remote Desktop Services.

The script works by checking for the CVE-2012-0152 vulnerability. If this
vulnerability is not patched, it is assumed that CVE-2012-0002 is not patched
either. This script can do its check without crashing the target.

The way this works follows:
* Send one user request. The server replies with a user id (call it A) and a channel for that user.
* Send another user request. The server replies with another user id (call it B) and another channel.
* Send a channel join request with requesting user set to A and requesting channel set to B. If the server replies with a success message, we conclude that the server is vulnerable.
* In case the server is vulnerable, send a channel join request with the requesting user set to B and requesting channel set to B to prevent the chance of a crash.

References:
* http://technet.microsoft.com/en-us/security/bulletin/ms12-020
* http://support.microsoft.com/kb/2621440
* http://zerodayinitiative.com/advisories/ZDI-12-044/
* http://aluigi.org/adv/termdd_1-adv.txt

Original check by by Worawit Wang (sleepya).
]]

---
-- @usage
-- nmap -sV --script=rdp-vuln-ms12-020 -p 3389 <target>
--
-- @output
-- PORT     STATE SERVICE        VERSION
-- 3389/tcp open  ms-wbt-server?
-- | rdp-vuln-ms12-020:
-- |   VULNERABLE:
-- |   MS12-020 Remote Desktop Protocol Denial Of Service Vulnerability
-- |     State: VULNERABLE
-- |     IDs:  CVE:CVE-2012-0152
-- |     Risk factor: Medium  CVSSv2: 4.3 (MEDIUM) (AV:N/AC:M/Au:N/C:N/I:N/A:P)
-- |     Description:
-- |               Remote Desktop Protocol vulnerability that could allow remote attackers to cause a denial of service.
-- |
-- |     Disclosure date: 2012-03-13
-- |     References:
-- |       http://technet.microsoft.com/en-us/security/bulletin/ms12-020
-- |       http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2012-0152
-- |
-- |   MS12-020 Remote Desktop Protocol Remote Code Execution Vulnerability
-- |     State: VULNERABLE
-- |     IDs:  CVE:CVE-2012-0002
-- |     Risk factor: High  CVSSv2: 9.3 (HIGH) (AV:N/AC:M/Au:N/C:C/I:C/A:C)
-- |     Description:
-- |               Remote Desktop Protocol vulnerability that could allow remote attackers to execute arbitrary code on the targeted system.
-- |
-- |     Disclosure date: 2012-03-13
-- |     References:
-- |       http://technet.microsoft.com/en-us/security/bulletin/ms12-020
-- |_      http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2012-0002

author = "Aleksandar Nikolic"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"intrusive", "vuln"}


portrule = shortport.port_or_service({3389},{"ms-wbt-server"})

-- see http://msdn.microsoft.com/en-us/library/cc240836%28v=prot.10%29.aspx for more info
local connectionRequest = "\x03\x00" -- TPKT Header version 03, reserved 0
.. "\x00\x0b" -- Length
.. "\x06"   -- X.224 Data TPDU length
.. "\xe0"    -- X.224 Type (Connection request)
.. "\x00\x00" -- dst reference
.. "\x00\x00" -- src reference
.. "\x00" -- class and options

-- see http://msdn.microsoft.com/en-us/library/cc240836%28v=prot.10%29.aspx
local connectInitial = "\x03\x00\x00\x65" -- TPKT Header
.. "\x02\xf0\x80" -- Data TPDU, EOT
.. "\x7f\x65\x5b" -- Connect-Initial
.. "\x04\x01\x01" -- callingDomainSelector
.. "\x04\x01\x01" -- calledDomainSelector
.. "\x01\x01\xff" -- upwardFlag
.. "\x30\x19" -- targetParams + size
..  "\x02\x01\x22" -- maxChannelIds
..  "\x02\x01\x20" -- maxUserIds
..  "\x02\x01\x00" -- maxTokenIds
..  "\x02\x01\x01" -- numPriorities
..  "\x02\x01\x00" -- minThroughput
..  "\x02\x01\x01" -- maxHeight
..  "\x02\x02\xff\xff" -- maxMCSPDUSize
..  "\x02\x01\x02" -- protocolVersion
.. "\x30\x18" -- minParams + size
.. "\x02\x01\x01" -- maxChannelIds
.. "\x02\x01\x01" -- maxUserIds
.. "\x02\x01\x01" -- maxTokenIds
.. "\x02\x01\x01" -- numPriorities
.. "\x02\x01\x00" -- minThroughput
.. "\x02\x01\x01" -- maxHeight
.. "\x02\x01\xff" -- maxMCSPDUSize
.. "\x02\x01\x02" -- protocolVersion
.. "\x30\x19" -- maxParams + size
.. "\x02\x01\xff" -- maxChannelIds
.. "\x02\x01\xff" -- maxUserIds
.. "\x02\x01\xff" -- maxTokenIds
.. "\x02\x01\x01" -- numPriorities
.. "\x02\x01\x00" -- minThroughput
.. "\x02\x01\x01" -- maxHeight
.. "\x02\x02\xff\xff" -- maxMCSPDUSize
.. "\x02\x01\x02" -- protocolVersion
.. "\x04\x00" -- userData

-- see http://msdn.microsoft.com/en-us/library/cc240835%28v=prot.10%29.aspx
local userRequest = "\x03\x00" -- header
.. "\x00\x08" -- length
.. "\x02\xf0\x80" -- X.224 Data TPDU (2 bytes: 0xf0 = Data TPDU, 0x80 = EOT, end of transmission)
.. "\x28" -- PER encoded PDU contents

local function do_check(host, port)
  local is_vuln = false
  local socket = nmap.new_socket()
  -- If any socket call fails, bail.
  local catch = function ()
    socket:close()
  end
  local try = nmap.new_try(catch)

  try(socket:connect(host, port))
  try(socket:send(connectionRequest))

  local rdp_banner = "\x03\x00\x00\x0b\x06\xd0\x00\x00\x12\x34\x00"
  local response = try(socket:receive_bytes(#rdp_banner))
  if response ~= rdp_banner then
    --probably not rdp at all
    stdnse.debug1("not RDP")
    return false
  end
  try(socket:send(connectInitial))
  try(socket:send(userRequest))  -- send attach user request
  response = try(socket:receive_bytes(12)) -- receive attach user confirm
  local user1 = string.unpack(">I2", response, 10) -- user_channel-1001 - see http://msdn.microsoft.com/en-us/library/cc240918%28v=prot.10%29.aspx

  try(socket:send(userRequest)) -- send another attach user request
  response = try(socket:receive_bytes(12)) -- receive another attach user confirm
  local user2 = string.unpack(">I2", response, 10) -- second user's channel - 1001
  user2 = user2+1001 -- second user's channel
  local data4 = string.pack(">I2I2", user1, user2)
  local data5 = "\x03\x00\x00\x0c\x02\xf0\x80\x38" -- channel join request TPDU
  local channelJoinRequest = data5 .. data4
  try(socket:send(channelJoinRequest)) -- bogus channel join request user1 requests channel of user2
  response = try(socket:receive_bytes(9))
  if response:sub(8,9) == "\x3e\x00" then
    -- 3e00 indicates a successful join
    -- see http://msdn.microsoft.com/en-us/library/cc240911%28v=prot.10%29.aspx
    -- service is vulnerable
    is_vuln = true
    -- send a valid request to prevent the BSoD
    data4 = string.pack(">I2I2", user2 - 1001, user2)
    channelJoinRequest = data5 .. data4 -- valid join request
    -- Don't bother checking these; we know it's vulnerable and are just cleaning up.
    socket:send(channelJoinRequest)
    local _, _ = socket:receive_bytes(0)
  end
  socket:close()
  return is_vuln
end

action = function(host, port)
  local rdp_vuln_0152  = {
    title = "MS12-020 Remote Desktop Protocol Denial Of Service Vulnerability",
    IDS = {CVE = 'CVE-2012-0152'},
    risk_factor = "Medium",
    scores = {
      CVSSv2 = "4.3 (MEDIUM) (AV:N/AC:M/Au:N/C:N/I:N/A:P)",
    },
    description = [[
    Remote Desktop Protocol vulnerability that could allow remote attackers to cause a denial of service.
    ]],
    references = {
      'http://technet.microsoft.com/en-us/security/bulletin/ms12-020',
    },
    dates = {
      disclosure = {year = '2012', month = '03', day = '13'},
    },
    exploit_results = {},
  }

  local rdp_vuln_0002 = {
    title = "MS12-020 Remote Desktop Protocol Remote Code Execution Vulnerability",
    IDS = {CVE = 'CVE-2012-0002'},
    risk_factor = "High",
    scores = {
      CVSSv2 = "9.3 (HIGH) (AV:N/AC:M/Au:N/C:C/I:C/A:C)",
    },
    description = [[
    Remote Desktop Protocol vulnerability that could allow remote attackers to execute arbitrary code on the targeted system.
    ]],
    references = {
      'http://technet.microsoft.com/en-us/security/bulletin/ms12-020',
    },
    dates = {
      disclosure = {year = '2012', month = '03', day = '13'},
    },
    exploit_results = {},
  }

  local report = vulns.Report:new(SCRIPT_NAME, host, port)
  rdp_vuln_0152.state = vulns.STATE.NOT_VULN
  rdp_vuln_0002.state = vulns.STATE.NOT_VULN

  -- Sleep for 0.2 seconds to make sure the script works even with SYN scan.
  -- Posible reason for this is that Windows resets the connection if we try to
  -- reconnect too fast to the same port after doing a SYN scan and not completing the
  -- handshake. In my tests, sleep values above 0.1s prevent the connection reset.
  stdnse.sleep(0.2)

  local status, is_vuln = pcall(do_check, host, port)
  if not status then
    -- A socket or data unpacking error means the POC didn't work as expected
    -- Report the error in case we actually need to fix something.
    -- Kinda wish we had a LIKELY_NOT_VULN
    local result = ("Server response not as expected: %s"):format(is_vuln)
    rdp_vuln_0152.check_results = result
    rdp_vuln_0002.check_results = result
  elseif is_vuln then
    rdp_vuln_0152.state = vulns.STATE.VULN
    rdp_vuln_0002.state = vulns.STATE.VULN
  end

  return report:make_output(rdp_vuln_0152,rdp_vuln_0002)
end