diff --git a/scripts/ftp-bounce.nse b/scripts/ftp-bounce.nse
index 9dbbbcf21..cb5476f98 100644
--- a/scripts/ftp-bounce.nse
+++ b/scripts/ftp-bounce.nse
@@ -1,8 +1,8 @@
-local coroutine = require "coroutine"
local nmap = require "nmap"
local shortport = require "shortport"
local stdnse = require "stdnse"
local string = require "string"
+local ftp = require "ftp"
description=[[
Checks to see if an FTP server allows port scanning using the FTP bounce method.
@@ -12,9 +12,9 @@ license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
---
-- @args ftp-bounce.username Username to log in with. Default
--- "anonymous".
+-- anonymous.
-- @args ftp-bounce.password Password to log in with. Default
--- "IEUser@".
+-- IEUser@.
-- @args ftp-bounce.checkhost Host to try connecting to with the PORT command.
-- Default: scanme.nmap.org
--
@@ -26,217 +26,88 @@ license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
-- PORT STATE SERVICE
-- 21/tcp open ftp
-- |_ftp-bounce: server forbids bouncing to low ports <1025
---
--- PORT STATE SERVICE
--- 21/tcp open ftp
--- |_ftp-bounce: no banner
categories = {"default", "safe"}
+portrule = shortport.port_or_service({21, 990}, {"ftp", "ftps"})
-portrule = shortport.service("ftp")
-
-line_iterate = function(s)
- local line
- for line in string.gmatch(s, "([^\n$]*)") do
- if #line > 0 then
- coroutine.yield(line)
- end
- end
-end
-
--- returns last ftp code read, or 000 on timeout
-get_ftp_code = function(socket)
- local fcode = 000
- local code = 0
- local status
- local result
- local co
- local line
- local err
-
- while true do
- status, result = socket:receive()
- if not status then
- break
- end
- -- read okay!
- co = coroutine.create(line_iterate)
- while coroutine.status(co) ~= 'dead' do
- err, line = coroutine.resume(co, result)
- if line then
- code = string.match(line, "^(%d%d%d) ")
- if not code then
- code = "-1"
- end
- -- io.write(">" .. code .. ":".. line .. "<\n")
- if tonumber(code) > 0 then
- fcode = tonumber(code)
- end
- end
- end
- -- not received good ftp code, try again
- if fcode ~= 0 then
- break
- end
- end
- -- io.write("## " .. fcode .. "\n");
- return fcode
-end
-
-local get_login = function()
- local user, pass
- local k
-
- for _, k in ipairs({"ftp-bounce.username", "username"}) do
- if nmap.registry.args[k] then
- user = nmap.registry.args[k]
- break
- end
- end
-
- for _, k in ipairs({"ftp-bounce.password", "password"}) do
- if nmap.registry.args[k] then
- pass = nmap.registry.args[k]
- break
- end
- end
-
- return user or "anonymous", pass or "IEUser@"
-end
-
-local portfmt_cached
local function get_portfmt()
- if portfmt_cached then return portfmt_cached end
local arghost = stdnse.get_script_args(SCRIPT_NAME .. ".checkhost") or "scanme.nmap.org"
- local status, addrs = nmap.resolve(arghost, "inet")
- if not status or #addrs < 1 then
- stdnse.verbose1("Couldn't resolve %s, scanning 10.0.0.1 instead.", arghost)
- addrs = {"10.0.0.1"}
+ local reg = nmap.registry[SCRIPT_NAME] or {}
+ local addr = reg[arghost]
+ if not addr then
+ local status, addrs = nmap.resolve(arghost, "inet")
+ if not status or #addrs < 1 then
+ stdnse.verbose1("Couldn't resolve %s, scanning 10.0.0.1 instead.", arghost)
+ addr = "10.0.0.1"
+ else
+ addr = addrs[1]
+ end
+ reg[arghost] = addr
end
- portfmt_cached = string.format("PORT %s,%%s\r\n", (string.gsub(addrs[1], "%.", ",")))
- return portfmt_cached
+ nmap.registry[SCRIPT_NAME] = reg
+ return string.format("PORT %s,%%s\r\n", (string.gsub(addr, "%.", ",")))
end
action = function(host, port)
- local socket = nmap.new_socket()
- local result;
- local status = true
- local isAnon = false
- local isOk = false
- local sendPass = true
- local user, pass = get_login()
- local fc
-
- socket:set_timeout(10000)
- socket:connect(host, port)
+ local user = stdnse.get_script_args(SCRIPT_NAME .. ".username") or "anonymous"
+ local pass = stdnse.get_script_args(SCRIPT_NAME .. ".password") or "IEUser@"
-- BANNER
- fc = get_ftp_code(socket)
- if fc == 0 then
- socket:close()
- -- no banner
- return "no banner"
+ local socket, code, message, buffer = ftp.connect(host, port, {request_timeout=10000})
+ if not socket then
+ return nil
end
- if fc == 421 or (fc >= 500 and fc <= 599) then
+ if code < 200 or code > 299 then
socket:close()
- -- return "server says you are not allowed to create connection"
- return
- end
- if fc < 200 or fc > 299 then
- socket:close()
- -- bad code
- -- return "bad banner (code " .. fc .. ")"
- return
+ return nil
end
socket:set_timeout(5000)
-- USER
- socket:send("USER " .. user .. "\r\n")
- fc = get_ftp_code(socket)
- if (fc >= 400 and fc <= 499) or (fc >= 500 and fc <= 599) then
- socket:close()
- -- bad code
- --return "anonymous user not allowed"
- return
- end
- if fc == 0 then
- socket:close()
- -- return "anonymous user timeouted"
- return
- end
- if fc ~= 230 and fc ~= 331 then
- socket:close()
- -- bad code
- -- return "bad response for anonymous user (code " .. fc .. ")"
- return
- end
- if fc == 230 then
- sendPass = false
+ local status, code, message = ftp.auth(socket, buffer, user, pass)
+ if not status then
+ stdnse.debug1("Authentication rejected: %s %s", code or "socket", message)
+ ftp.close(socket)
+ return nil
end
- -- PASS
- if sendPass then
- socket:send("PASS " .. pass .. "\r\n")
- fc = get_ftp_code(socket)
- if (fc >= 500 and fc <= 599) or (fc >= 400 and fc <= 499) then
- socket:close()
- -- bad code
- -- return "anonymous user/pass rejected"
- return
- end
- if fc == 0 then
- socket:close()
- -- return "anonymous pass timeouted"
- return
- end
- if fc ~= 230 and fc ~= 200 then
- socket:close()
- -- return "answer to PASS not understood (code " .. fc .. ")"
- return
- end
- end
-
- -- PORT scanme.nmap.com:highport
+ -- PORT highport
local portfmt = get_portfmt()
-- This is actually port 256*80 + 80 = 20560
- socket:send(string.format(portfmt, "80,80"))
- fc = get_ftp_code(socket)
- if (fc >= 500 and fc <= 599) then
- socket:close()
+ if not socket:send(string.format(portfmt, "80,80")) then
+ stdnse.debug1("Can't send PORT")
+ return nil
+ end
+ code, message = ftp.read_reply(buffer)
+ if not code then
+ stdnse.debug1("Error after PORT: %s", message)
+ return nil
+ end
+ if code < 200 or code > 299 then
+ stdnse.verbose1("PORT response: %d %s", code, message)
+ ftp.close(socket)
-- return "server forbids bouncing"
- return
- end
- if fc == 0 then
- socket:close()
- -- return "PORT command timeouted"
- return
- end
- if not (fc >= 200 and fc<=299) then
- socket:close()
- -- return "PORT response not understood (code " .. fc .. ")"
- return
+ return nil
end
- -- PORT scanme.nmap.com:lowport
- socket:send(string.format(portfmt, "0,80"))
- fc = get_ftp_code(socket)
- if (fc >= 500 and fc <= 599) then
- socket:close()
+ -- PORT lowport
+ if not socket:send(string.format(portfmt, "0,80")) then
+ stdnse.debug1("Can't send PORT")
+ return nil
+ end
+ code, message = ftp.read_reply(buffer)
+ if not code then
+ stdnse.debug1("Error after PORT: %s", message)
+ return nil
+ end
+ if code < 200 or code > 299 then
+ stdnse.verbose1("PORT (low port) response: %d %s", code, message)
+ ftp.close(socket)
return "server forbids bouncing to low ports <1025"
end
- if fc == 0 then
- socket:close()
- -- return "PORT command timeouted for low port"
- return
- end
- if not (fc >= 200 and fc<=299) then
- socket:close()
- -- return "PORT response not understood for low port (code " .. fc .. ")"
- return
- end
- socket:close()
+ ftp.close(socket)
return "bounce working!"
end