1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-07 21:21:31 +00:00
Files
nmap/scripts/ftp-bounce.nse
dmiller 26510fe8a7 Remove hardcoded IP from ftp-bounce
Instead of hard-coding the many-years-invalid IP address of
scanme.nmap.org, look it up via DNS. Even better, you can override the
host used for this purpose, in case you don't want to tip your hand.
2016-01-15 20:19:55 +00:00

243 lines
5.7 KiB
Lua

local coroutine = require "coroutine"
local nmap = require "nmap"
local shortport = require "shortport"
local stdnse = require "stdnse"
local string = require "string"
description=[[
Checks to see if an FTP server allows port scanning using the FTP bounce method.
]]
author = "Marek Majkowski"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
---
-- @args ftp-bounce.username Username to log in with. Default
-- <code>"anonymous"</code>.
-- @args ftp-bounce.password Password to log in with. Default
-- <code>"IEUser@"</code>.
-- @args ftp-bounce.checkhost Host to try connecting to with the PORT command.
-- Default: scanme.nmap.org
--
-- @output
-- PORT STATE SERVICE
-- 21/tcp open ftp
-- |_ftp-bounce: bounce working!
--
-- 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.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"}
end
portfmt_cached = string.format("PORT %s,%%s\r\n", (string.gsub(addrs[1], "%.", ",")))
return portfmt_cached
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)
-- BANNER
fc = get_ftp_code(socket)
if fc == 0 then
socket:close()
-- no banner
return "no banner"
end
if fc == 421 or (fc >= 500 and fc <= 599) 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
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
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
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()
-- 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
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()
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()
return "bounce working!"
end