1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
|
local comm = require "comm"
local nmap = require "nmap"
local stdnse = require "stdnse"
local table = require "table"
local U = require "lpeg-utility"
description = [[
A simple banner grabber which connects to an open TCP port and prints out anything sent by the listening service within five seconds.
The banner will be truncated to fit into a single line, but an extra line may be printed for every
increase in the level of verbosity requested on the command line.
]]
---
-- @output
-- 21/tcp open ftp
-- |_ banner: 220 FTP version 1.0\x0D\x0A
-- @arg banner.ports Which ports to grab. Same syntax as -p option. Use
-- "common" to only grab common text-protocol banners.
-- Default: all ports.
-- @arg banner.timeout How long to wait for a banner. Default: 5s
author = "jah"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"discovery", "safe"}
local portarg = stdnse.get_script_args(SCRIPT_NAME .. ".ports")
if portarg == "common" then
portarg = "13,17,21-23,25,129,194,587,990,992,994,6667,6697"
end
---
-- Script is executed for any TCP port.
portrule = function( host, port )
if port.protocol == "tcp" then
if portarg then
return stdnse.in_port_range(port, portarg)
end
return true
end
return false
end
---
-- Grabs a banner and outputs it nicely formatted.
action = function( host, port )
local out = grab_banner(host, port)
return output( out )
end
---
-- Connects to the target on the given port and returns any data issued by a listening service.
-- @param host Host Table.
-- @param port Port Table.
-- @return String or nil if data was not received.
function grab_banner(host, port)
-- Did the service engine already do the hard work?
if port.version and port.version.service_fp then
local response = U.get_response(port.version.service_fp, "NULL")
if response then
return response:match("^%s*(.-)%s*$");
end
end
local opts = {}
opts.timeout = stdnse.parse_timespec(stdnse.get_script_args(SCRIPT_NAME .. ".timeout"))
opts.timeout = (opts.timeout or 5) * 1000
local status, response = comm.get_banner(host, port, opts)
if not status then
local errlvl = { ["EOF"]=3,["TIMEOUT"]=3,["ERROR"]=2 }
stdnse.debug(errlvl[response] or 1, "failed for %s on %s port %s. Message: %s", host.ip, port.protocol, port.number, response or "No Message.")
return nil
end
return response:match("^%s*(.-)%s*$");
end
---
-- Formats the banner for printing to the port script result.
--
-- Non-printable characters are hex encoded and the banner is
-- then truncated to fit into the number of lines of output desired.
-- @param out String banner issued by a listening service.
-- @return String formatted for output.
function output( out )
if type(out) ~= "string" or out == "" then return nil end
local filename = SCRIPT_NAME
local line_len = 75 -- The character width of command/shell prompt window.
local fline_offset = 5 -- number of chars excluding script id not available to the script on the first line
-- number of chars available on the first line of output
-- we'll skip the first line of output if the filename is looong
local fline_len
if filename:len() < (line_len-fline_offset) then
fline_len = line_len -1 -filename:len() -fline_offset
else
fline_len = 0
end
-- number of chars allowed on subsequent lines
local sline_len = line_len -1 -(fline_offset-2)
-- total number of chars allowed for output (based on verbosity)
local total_out_chars
if fline_len > 0 then
total_out_chars = fline_len + (extra_output()*sline_len)
else
-- skipped the first line so we'll have an extra lines worth of chars
total_out_chars = (1+extra_output())*sline_len
end
-- replace non-printable ascii chars - no need to do the whole string
out = replace_nonprint(out, 1+total_out_chars) -- 1 extra char so we can truncate below.
-- truncate banner to total_out_chars ensuring we remove whole hex encoded chars
if out:len() > total_out_chars then
while out:len() > total_out_chars do
if (out:sub(-4,-1)):match("\\x%x%x") then
out = out:sub(1,-1-4)
else
out = out:sub(1,-1-1)
end
end
out = ("%s..."):format(out:sub(1,total_out_chars-3)) -- -3 for ellipsis
end
-- break into lines - this will look awful if line_len is more than the actual space available on a line...
local ptr = fline_len
local t = {}
while true do
if out:len() >= ptr then
t[#t+1] = (ptr > 0 and out:sub(1,ptr)) or " " -- single space if we skipped the first line
out = out:sub(ptr+1,-1)
ptr = sline_len
else
t[#t+1] = out
break
end
end
return table.concat(t,"\n")
end
---
-- Replaces characters with ASCII values outside of the range of standard printable
-- characters (decimal 32 to 126 inclusive) with hex encoded equivalents.
--
-- The second parameter dictates the number of characters to return, however, if the
-- last character before the number is reached is one that needs replacing then up to
-- three characters more than this number may be returned.
-- If the second parameter is nil, no limit is applied to the number of characters
-- that may be returned.
-- @param s String on which to perform substitutions.
-- @param len Number of characters to return.
-- @return String.
function replace_nonprint( s, len )
local t = {}
local count = 0
for c in s:gmatch(".") do
if c:byte() < 32 or c:byte() > 126 then
t[#t+1] = ("\\x%s"):format( ("0%s"):format( ( (stdnse.tohex( c:byte() )):upper() ) ):sub(-2,-1) ) -- capiche
count = count+4
else
t[#t+1] = c
count = count+1
end
if type(len) == "number" and count >= len then break end
end
return table.concat(t)
end
---
-- Returns a number for each level of verbosity specified on the command line.
--
-- Ignores level increases resulting from debugging level.
-- @return Number
function extra_output()
return (nmap.verbosity()-nmap.debugging()>0 and nmap.verbosity()-nmap.debugging()) or 0
end
|