description = [[ Extracts and outputs HTML and JavaScript comments from HTTP responses. ]] --- -- @usage nmap -p80 --script http-comments-displayer.nse -- -- This scripts uses patterns to extract HTML comments from HTTP -- responses and writes these to the command line. -- -- @args http-comments-displayer.singlepages Some single pages -- to check for comments. For example, {"/", "/wiki"}. -- Default: nil (crawler mode on) -- @args http-comments-displayer.context declares the number of chars -- to extend our final strings. This is useful when we need to -- to see the code that the comments are referring to. -- Default: 0, Maximum Value: 50 -- -- -- @output -- PORT STATE SERVICE REASON -- 80/tcp open http syn-ack -- | http-comments-displayer: -- | Path: / -- | Line number: 214 -- | Comment: -- | -- | -- | Path: /register.php -- | Line number: 15 -- | Comment: -- |_ /* We should avoid the hardcoding here */ -- --- categories = {"discovery", "safe"} author = "George Chatzisofroniou" license = "Same as Nmap--See https://nmap.org/book/man-legal.html" local http = require "http" local shortport = require "shortport" local stdnse = require "stdnse" local table = require "table" local string = require "string" local httpspider = require "httpspider" PATTERNS = { "", -- HTML comment "/%*.-%*/", -- Javascript multiline comment "[ ,\n]//.-\n" -- Javascript one-line comment. Could be better? } portrule = shortport.port_or_service( {80, 443}, {"http", "https"}, "tcp", "open") -- Returns comment's line number by counting the occurrences of the -- new line character ("\n") from the start of the HTML file until -- the related comment. local getLineNumber = function(body, comment) local partofresponse = body:find(comment, 1, true) partofresponse = body:sub(0, partofresponse) local _, count = string.gsub(partofresponse, "\n", "\n") return count + 1 end action = function(host, port) local context = stdnse.get_script_args("http-comments-displayer.context") local singlepages = stdnse.get_script_args("http-comments-displayer.singlepages") local comments = {} local crawler = httpspider.Crawler:new( host, port, '/', { scriptname = SCRIPT_NAME, withinhost = 1 } ) if (not(crawler)) then return end crawler:set_timeout(10000) if context then if (tonumber(context) > 100) then context = 100 end -- Lua's abbreviated patterns support doesn't have a fixed-number-of-repetitions syntax. for i, pattern in ipairs(PATTERNS) do PATTERNS[i] = string.rep(".", context) .. PATTERNS[i] .. string.rep(".", context) end end local index, k, target, response, path while (true) do if singlepages then k, target = next(singlepages, index) if (k == nil) then break end response = http.get(host, port, target) path = target else local status, r = crawler:crawl() -- if the crawler fails it can be due to a number of different reasons -- most of them are "legitimate" and should not be reason to abort if (not(status)) then if (r.err) then return stdnse.format_output(false, r.reason) else break end end response = r.response path = tostring(r.url) end if response.body then for i, pattern in ipairs(PATTERNS) do for c in string.gmatch(response.body, pattern) do local linenumber = getLineNumber(response.body, c) comments[c] = "\nPath: " .. path .. "\nLine number: " .. linenumber .. "\nComment: \n" end end if (index) then index = index + 1 else index = 1 end end end -- If the table is empty. if next(comments) == nil then return "Couldn't find any comments." end -- Create a nice output. local results = {} for c, _ in pairs(comments) do table.insert(results, {_, {{c}}}) end results.name = crawler:getLimitations() return stdnse.format_output(true, results) end