local http = require "http" local nmap = require "nmap" local shortport = require "shortport" local strbuf = require "strbuf" local table = require "table" local stdnse = require "stdnse" description = [[ Checks for disallowed entries in /robots.txt on a web server. The higher the verbosity or debug level, the more disallowed entries are shown. ]] --- --@output -- 80/tcp open http syn-ack -- | http-robots.txt: -- | entry_count: 241 -- | display_count: 15 -- | entries: -- | /search /sdch /groups /images /catalogs -- | /news /nwshp /setnewsprefs? /index.html? /? -- |_/addurl/image? /pagead/ /relpage/ -- --@xmloutput -- author = "Eddie Bell" license = "Same as Nmap--See http://nmap.org/book/man-legal.html" categories = {"default", "discovery", "safe"} portrule = shortport.http local last_len = 0 -- split the output in 50 character length lines local function wordwrap(output) local line = strbuf.new() local out = strbuf.new("") local line_len = 0 for i,v in ipairs(output) do if line_len + v:len() + 1 > 50 then stdnse.print_debug(1, "i=%d, eol", i) out = out .. strbuf.dump(line, ' ') line_len = 0 line = strbuf.new(v) else stdnse.print_debug(1, "i=%d, line_len=%d", i, line_len) line_len = line_len + v:len() + 1 line = line .. v end end if line_len > 0 then stdnse.print_debug(1, "line_len=%d", line_len) out = out .. strbuf.dump(line, ' ') end return tostring(out) end local function buildOutput(output, w) if w:len() == 0 then return nil end -- check for duplicates for i,v in ipairs(output) do if w == v or w == v:sub(2) then return nil end end output[#output+1] = w end -- parse all disallowed entries in body and add them to a strbuf local function parse_robots(body, output) for line in body:gmatch("[^\r\n]+") do for w in line:gmatch('[Dd]isallow:%s*(.*)') do w = w:gsub("%s*#.*", "") buildOutput(output, w) end end return #output end action = function(host, port) local dis_count local answer = http.get(host, port, "/robots.txt" ) if answer.status ~= 200 then return nil end local o = {} local v_level = nmap.verbosity() + (nmap.debugging()*2) local entries = {} local detail = 15 dis_count = parse_robots(answer.body, entries) if dis_count == 0 then return end -- verbose/debug mode, print 50 entries if v_level > 1 and v_level < 5 then detail = 40 -- double debug mode, print everything elseif v_level >= 5 then detail = dis_count end -- check we have enough entries if detail > dis_count then detail = dis_count end o.entry_count = dis_count local output_order = {"entry_count", "entries"} if (detail ~= 0 and detail ~= dis_count) then o.display_count = detail table.insert(entries, detail+1, nil) -- Hide the rest output_order = {"entry_count", "display_count", "entries"} end stdnse.set_tostring(entries, wordwrap) o.entries = entries stdnse.set_tostring(o, stdnse.format_generator({ key_order = output_order })) return o end