From 297dd306fa733e9f0538e353fc3c6b36119d670c Mon Sep 17 00:00:00 2001 From: dmiller Date: Wed, 26 Jul 2017 23:11:01 +0000 Subject: [PATCH] Update ftp-bounce to use new ftp functions --- scripts/ftp-bounce.nse | 243 ++++++++++------------------------------- 1 file changed, 57 insertions(+), 186 deletions(-) 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