summaryrefslogtreecommitdiffstats
path: root/scripts/http-svn-enum.nse
blob: 4a231137c6b2e989d0809746f65558a79a8ca766 (plain)
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
local http = require "http"
local shortport = require "shortport"
local slaxml = require "slaxml"
local stdnse = require "stdnse"
local tab = require "tab"

description = [[Enumerates users of a Subversion repository by examining logs of most recent commits.
]]

---
-- @usage nmap --script http-svn-enum <target>
--
-- @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
-- <table></table>
-- <table>
--   <elem>Author</elem>
--   <elem>Count</elem>
--   <elem>Revision</elem>
--   <elem>Date</elem>
-- </table>
-- <table>
--   <elem>gyani</elem>
--   <elem>183</elem>
--   <elem>34965</elem>
--   <elem>2015-07-24</elem>
-- </table>
-- <table>
--   <elem>robert</elem>
--   <elem>1</elem>
--   <elem>34566</elem>
--   <elem>2015-06-02</elem>
-- </table>
-- <table>
--   <elem>david</elem>
--   <elem>2</elem>
--   <elem>34785</elem>
--   <elem>2015-06-28</elem>
-- </table>

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 = '<?xml version="1.0"?> <S:log-report xmlns:S="svn:"> <S:start-revision>'.. start_revision .. '</S:start-revision> <S:discover-changed-paths/> </S:log-report>'

      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