local http = require "http" local shortport = require "shortport" local slaxml = require "slaxml" local stdnse = require "stdnse" local tab = require "tab" local table = require "table" description = [[Enumerates users of a Subversion repository by examining logs of most recent commits. ]] --- -- @usage nmap --script http-svn-enum -- -- @args http-svn-enum.count The number of logs to fetch. Defaults to the last 1000 commits. -- @args http-svn-enum.url This is a URL relative to the scanned host eg. /default.html (default: /). -- -- @output -- PORT STATE SERVICE REASON -- 443/tcp open https syn-ack -- | http-svn-enum: -- | Author Count Revision Date -- | gyani 183 34965 2015-07-24 -- | robert 1 34566 2015-06-02 -- | david 2 34785 2015-06-28 -- -- @xmloutput --
-- -- Author -- Count -- Revision -- Date --
-- -- gyani -- 183 -- 34965 -- 2015-07-24 --
-- -- robert -- 1 -- 34566 -- 2015-06-02 --
-- -- david -- 2 -- 34785 -- 2015-06-28 --
author = "Gyanendra Mishra" license = "Same as Nmap--See https://nmap.org/book/man-legal.html" categories = {"default", "discovery", "safe"} local ELEMENTS = { ["creator-displayname"] = "author", ["version-name"] = "version", ["date"] = "date", } local function get_callback(name, unames, temp) if ELEMENTS[name] then return function(content) if not content then content = "unknown" end --useful for "nil" authors temp[ELEMENTS[name]] = name == "date" and content:sub(1, 10) or content if temp.date and temp.version and temp.author then unames[temp.author] = {unames[temp.author] and unames[temp.author][1] + 1 or 1, temp.version, temp.date} end end end end portrule = shortport.http action = function(host, port) local count = tonumber(stdnse.get_script_args(SCRIPT_NAME .. ".count")) or 1000 local url = stdnse.get_script_args(SCRIPT_NAME .. ".url") or "/" local output, revision, unames = tab.new(), nil, {} local options = { header = { ["Depth"] = 0, }, } -- first we fetch the current revision number local response = http.generic_request(host, port, "PROPFIND", url, options) if response and response.status == 207 then local parser = slaxml.parser:new() parser._call = {startElement = function(name) parser._call.text = name == "version-name" and function(content) revision = tonumber(content) end end, closeElement = function(name) parser._call.text = function() return nil end end } parser:parseSAX(response.body, {stripWhitespace=true}) if revision then local start_revision = revision > count and revision - count or 1 local content = ' '.. start_revision .. ' ' options = { header = { ["Depth"] = 1, }, content = content, } local temp = {} response = http.generic_request(host, port, "REPORT", url, options) if response and response.status == 200 then parser._call.startElement = function(name) parser._call.text = get_callback(name, unames, temp) end parser._call.closeElement = function(name) if name == "log-item" then temp ={} end parser._call.text = function() return nil end end parser:parseSAX(response.body, {stripWhitespace=true}) tab.nextrow(output) tab.addrow(output, "Author", "Count", "Revision", "Date") for revision_author, data in pairs(unames) do tab.addrow(output, revision_author, data[1], data[2], data[3]) end if next(unames) then return output end end end end end