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
|
local datafiles = require "datafiles"
local http = require "http"
local nmap = require "nmap"
local shortport = require "shortport"
local stdnse = require "stdnse"
local string = require "string"
local table = require "table"
description = [[
Attempts to enumerate valid usernames on web servers running with the mod_userdir
module or similar enabled.
The Apache mod_userdir module allows user-specific directories to be accessed
using the http://example.com/~user/ syntax. This script makes http requests in
order to discover valid user-specific directories and infer valid usernames. By
default, the script will use Nmap's
<code>nselib/data/usernames.lst</code>. An HTTP response
status of 200 or 403 means the username is likely a valid one and the username
will be output in the script results along with the status code (in parentheses).
This script makes an attempt to avoid false positives by requesting a directory
which is unlikely to exist. If the server responds with 200 or 403 then the
script will not continue testing it.
CVE-2001-1013: http://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2001-1013.
]]
---
-- @args http-userdir-enum.users The filename of a username list.
-- @args http-userdir-enum.limit The maximum number of users to check.
--
-- @output
-- 80/tcp open http syn-ack Apache httpd 2.2.9
-- |_ http-userdir-enum: Potential Users: root (403), user (200), test (200)
author = "jah"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"auth", "intrusive"}
portrule = shortport.http
local function fail (err) return stdnse.format_output(false, err) end
action = function(host, port)
local limit = stdnse.get_script_args(SCRIPT_NAME .. '.limit')
if(not nmap.registry.userdir) then
init()
end
local usernames = nmap.registry.userdir
-- speedy exit if no usernames
if(#usernames == 0) then
return fail("Didn't find any users to test (should be in nselib/data/usernames.lst)")
end
-- Identify servers that answer 200 to invalid HTTP requests and exit as these would invalidate the tests
local status_404, result_404, known_404 = http.identify_404(host,port)
if ( status_404 and result_404 == 200 ) then
stdnse.debug1("Exiting due to ambiguous response from web server on %s:%s. All URIs return status 200.", host.ip, port.number)
return nil
end
-- Check if we can use HEAD requests
local use_head = http.can_use_head(host, port, result_404)
-- Queue up the checks
local all = {}
local i
for i = 1, #usernames, 1 do
if(nmap.registry.args.limit and i > tonumber(nmap.registry.args.limit)) then
stdnse.debug1("Reached the limit (%d), stopping", nmap.registry.args.limit)
break;
end
if(use_head) then
all = http.pipeline_add("/~" .. usernames[i], nil, all, 'HEAD')
else
all = http.pipeline_add("/~" .. usernames[i], nil, all, 'GET')
end
end
local results = http.pipeline_go(host, port, all)
-- Check for http.pipeline error
if(results == nil) then
stdnse.debug1("http.pipeline returned nil")
return fail("http.pipeline returned nil")
end
local found = {}
for i, data in pairs(results) do
if(http.page_exists(data, result_404, known_404, "/~" .. usernames[i], true)) then
stdnse.debug1("Found a valid user: %s", usernames[i])
table.insert(found, usernames[i])
end
end
if(#found > 0) then
return string.format("Potential Users: %s", table.concat(found, ", "))
elseif(nmap.debugging() > 0) then
return "Didn't find any users!"
end
return nil
end
---
-- Parses a file containing usernames (1 per line), defaulting to
-- "nselib/data/usernames.lst" and stores the resulting array of usernames in
-- the registry for use by all threads of this script. This means file access
-- is done only once per Nmap invocation. init() also adds a random string to
-- the array (in the first position) to attempt to catch false positives.
-- @return nil
function init()
local customlist = stdnse.get_script_args(SCRIPT_NAME .. '.users')
local read, usernames = datafiles.parse_file(customlist or "nselib/data/usernames.lst", {})
if not read then
stdnse.debug1("%s", usernames or "Unknown Error reading usernames list.")
nmap.registry.userdir = {}
return nil
end
-- random dummy username to catch false positives (not necessary)
-- if #usernames > 0 then table.insert(usernames, 1, randomstring()) end
nmap.registry.userdir = usernames
stdnse.debug1("Testing %d usernames.", #usernames)
return nil
end
|