[prev in list] [next in list] [prev in thread] [next in thread] 

List:       nmap-dev
Subject:    [NSE] http matching library
From:       Patrik Karlsson <patrik () cqure ! net>
Date:       2011-04-30 6:49:54
Message-ID: 8C48D434-DB81-496E-A48F-998EBE1218D7 () cqure ! net
[Download RAW message or body]

Given the recent increase in http/web scripts I thought I would put some work out on \
the list I did a while back. I started working on it right about the time Ron did his \
big overhaul of the http-enum script. [1] My idea was to implement what the http-enum \
script does today, but de-couple the probes and matches from each other. The response \
wasn't very positive at the time.

Anyway, I did some more work on it and ended up creating a http-match library which \
pretty much does regexp matching based on rules created on-the-fly or loaded from a \
file. There's a script called http-fp that implements the decoupled probe and match \
approach on something similar to http-enum. It does so by loading all the rules \
(probes and matches) from a file (nselib/data/urls.txt). Once the probes have run \
matchers are used to process the response. Each matcher don't necessarily have to run \
for each probe as they can be restricted by url or category. In addition to regexp's \
a match can contain Lua code that will be executed on the http response received from \
the server.

To be clear, I'm not suggesting we add this script, but seeing the increase of small \
scripts that do different types of matching lately, I think someone may find the \
library useful. Also, please consider the code for what it is (maybe something \
useful) as it is not as well documented as what I usually put out to the list, or as \
finished as I would like. In order to make some of the matches I needed I factored \
the cookie code out from http.lua into cookie.lua. I'm attaching this as well. To \
better understand how it all works, the http-fp script may be useful. In order to get \
it to run you need to drop the url.txt into nselib/data/urls.txt and copy both \
libraries (httpmatch.lua and cookie.lua) into nselib.

Here's a few sample matches in order to give you an idea of how it works:

-- Detect .NET applications
match { status="200", ['header.x-powered-by']="(ASP.NET)", \
['header.x-aspnet-version']="(.*)", type="framework", desc="#header.x-powered-by_1# \
#header.x-aspnet-version_1#" }

-- Output any cookies set by the application
match { status="200", ['header.set-cookie']="(.*)", type="cookie", \
desc="#header.set-cookie_1#" }

-- Detect WordPress
match { status="200", body="\<meta name=\"generator\" content=\"(WordPress.-)\"", \
type="app", desc="#body_1#" }

-- Output contents of robots file
match { path="^/robots.txt$", status="200", type="additional", desc=
function(r) 
	local tbl = stdnse.strsplit("\r?\n", r.body)
	tbl.name = "Robots content"
	return tbl
end
}

-- Check whether the session cookie is assigned as HttpOnly
match { status="200", type="debug", ['header.set-cookie']='.*', desc=
	function(r) 
		local cookies = cookie.parse(r.header['set-cookie'] )
		local result = {}
	
		for _, cookie in ipairs(cookies) do
			if ( cookie:isSessionCookie() and not( cookie:isHttpOnly() ) ) then
				table.insert( result, ("OWASP-SM-002: Cookie (%s) is not set as \
HttpOnly"):format( cookie:getName() ) )  end
		end
		return result
	end 
}

-- Check if the cookie was assigned with the secure attribute
match { status="200", type="debug", ['header.set-cookie']='.*', desc=
	function(r) 
		local cookies = cookie.parse(r.header['set-cookie'] )
		local result = {}

		for _, cookie in ipairs(cookies) do
			if ( options.ssl and not( cookie:isSecure() ) ) then
				table.insert( result, ("OWASP-SM-002: Cookie (%s) is not set as secure"):format( \
cookie:getName() ) )  end
		end
		return result
	end 
}


-- Calculate SHA1 hash
match { status="200", type="additional", desc = function(r) return "SHA1 hash: " .. \
select(2, bin.unpack("H20", openssl.sha1(r.body))) end}

//Patrik

[1] http://seclists.org/nmap-dev/2010/q4/112


["http-fp.nse" (http-fp.nse)]

description = [[
Fingerprint web applications
]]

author = "Patrik Karlsson"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"safe", "discovery"}

require 'shortport'
require 'http'
require 'url'
require 'httpmatch'

portrule = shortport.http

local function runProbesPL( host, port, probes )
	local all, status = nil, true
	for _, probe in ipairs( probes ) do
		all = http.pipeline_add(probe.path, nil, ( all or probe ), probe.verb )
	end
	local responses = http.pipeline_go( host, port, all, nil )
	if ( not(responses) ) then status = false end
	return status, responses
end

local function runProbes( host, port, probes )
	local responses = {}
	for _, probe in ipairs( probes ) do
		local retries = 3
		local response
		repeat
			response = http.get(host, port, probe.path)
			retries = retries - 1
		until( response or retries == 0 )
		if ( retries == 0 ) then return false, "Error" end
		table.insert( responses, response )	
	end
	return true, responses
end

local function matchResponses( responses, probe_items, match_items, match_types )
	local matcher = httpmatch.Matcher:new( match_items, match_types )
	for i=1, #responses do
		matcher:matchHttpResponse( responses[i], probe_items[i] )
	end
	return matcher:formatOutput()
end

-- check if all urls return 401 authorization required
local function checkAllStatus( host, port, http_status )
	local status = true
	for i=1, 3 do
		local rnd = select(2, bin.unpack("H20", openssl.sha1( openssl.rand_bytes(100) ) ) )
		local response = http.get( host, port, "/url_test_" ..	rnd )
		if ( not( tostring(response.status):match(http_status) ) ) then status = false end
	end
	return status
end

local function checkAllRedir( host, port )
	return checkAllStatus( host, port, "30.")
end

local function checkAllAuth( host, port )
	return checkAllStatus( host, port, "401" )
end

local function checkAll200( host, port )
	return checkAllStatus( host, port, "200")
end

action = function( host, port )
	
	local urlfile = "nselib/data/urls.txt"
	local category = stdnse.get_script_args('http-fp.category')
	
	-- This obviously needs to be handled differently in the future
	if ( checkAll200(host, port) ) then
		return "All urls report 200 OK aborting ..."
	end

	local options = {
		showredir = false,
		debug = false,
		reportauth = not( checkAllAuth( host, port ) ),
		ssl = ( port.service == "https" ) and true or false
	}

	local helper = httpmatch.Helper:new()
	helper:setOptions(options)
	helper:setCategory(category)
	local status, fp = helper:loadFpFile( urlfile )
	
	if ( not(status) ) then	return fp end
				
	local status, responses = runProbesPL( host, port, fp.probes )
	if ( not(status) ) then
		stdnse.print_debug("Pipelined requests failed")
		return
	end
	
	local results = matchResponses( responses, fp.probes, fp.matches, fp.matchtypes ) or {}

	return stdnse.format_output(true, results)
end

["cookie.lua" (cookie.lua)]

module(... or "cookie", package.seeall)

Cookie = {
		
	new = function( self, cookie )
		local o = {}
       	setmetatable(o, self)
		for k,v in pairs(cookie) do	self[k] = v	end
        self.__index = self
		return o
	end,
	
	isSessionCookie = function( self )
		return not(self['expires'])
	end,

	getName = function( self ) return self['name'] end,
	
	isHttpOnly = function( self )
		return self['HttpOnly'] and true or false
	end,
	
	isSecure = function( self )
		return self['secure'] and true or false
	end,
	
}


-- Parsing of specific headers. skip_space and the read_* functions return the
-- byte index following whatever they have just read, or nil on error.

-- Skip whitespace (that has already been folded from LWS). See RFC 2616,
-- section 2.2, definition of LWS.
local skip_space = function(s, pos)
  local _

  _, pos = string.find(s, "^[ \t]*", pos)

  return pos + 1
end

-- Get a token starting at offset. See RFC 2616, section 2.2.
-- @return the first index following the token, or nil if no token was found.
-- @return the token.
local function get_token(s, offset)
  -- All characters except CTL and separators.
  local _, i, token = s:find("^([^()<>@,;:\\\"/%[%]?={} %z\001-\031\127]+)", offset)
  if i then
    return i + 1, token
  else
    return nil
  end
end

-- See RFC 2616, section 2.2.
local read_token = function(s, pos)
  local _, token

  pos = skip_space(s, pos)
  -- 1*<any CHAR except CTLs or separators>. CHAR is only byte values 0-127.
  _, pos, token = string.find(s, "^([^%z\001-\031()<>@,;:\\\"/?={} \t%[%]\127-\255]+)", pos)

  if token then
    return pos + 1, token
  else
    return nil
  end
end

-- See RFC 2616, section 2.2. Here we relax the restriction that TEXT may not
-- contain CTLs.
local read_quoted_string = function(s, pos)
  local chars = {}

  if string.sub(s, pos, pos) ~= "\"" then
    return nil
  end
  pos = pos + 1
  pos = skip_space(s, pos)
  while pos <= string.len(s) and string.sub(s, pos, pos) ~= "\"" do
    local c

    c = string.sub(s, pos, pos)
    if c == "\\" then
      if pos < string.len(s) then
        pos = pos + 1
        c = string.sub(s, pos, pos)
      else
        return nil
      end
    end

    chars[#chars + 1] = c
    pos = pos + 1
  end
  if pos > string.len(s) or string.sub(s, pos, pos) ~= "\"" then
    return nil
  end

  return pos + 1, table.concat(chars)
end

local read_token_or_quoted_string = function(s, pos)
  pos = skip_space(s, pos)
  if string.sub(s, pos, pos) == "\"" then
    return read_quoted_string(s, pos)
  else
    return read_token(s, pos)
  end
end

parse = function( s )
	local cookies
	local name, value
	local _, pos

	cookies = {}

	pos = 1
	while true do
		local cookie = {}

		-- Get the NAME=VALUE part.
		pos = skip_space(s, pos)
		pos, cookie.name = get_token(s, pos)
		if not cookie.name then
			return nil, "Can't get cookie name."
		end
		pos = skip_space(s, pos)
		if pos > #s or string.sub(s, pos, pos) ~= "=" then
			return nil, string.format("Expected '=' after cookie name \"%s\".", cookie.name)
		end
		pos = pos + 1
		pos = skip_space(s, pos)
		if string.sub(s, pos, pos) == "\"" then
			pos, cookie.value = get_quoted_string(s, pos)
		else
			_, pos, cookie.value = string.find(s, "([^;]*)[ \t]*", pos)
			pos = pos + 1
		end
		if not cookie.value then
			return nil, string.format("Can't get value of cookie named \"%s\".", cookie.name)
		end
		pos = skip_space(s, pos)

		-- Loop over the attributes.
		while pos <= #s and string.sub(s, pos, pos) == ";" do
			pos = pos + 1
			pos = skip_space(s, pos)
			pos, name = get_token(s, pos)
			if not name then
				return nil, string.format("Can't get attribute name of cookie \"%s\".", cookie.name)
			end
			pos = skip_space(s, pos)
			if pos <= #s and string.sub(s, pos, pos) == "=" then
				pos = pos + 1
				pos = skip_space(s, pos)
				if string.sub(s, pos, pos) == "\"" then
					pos, value = get_quoted_string(s, pos)
				else
					if string.lower(name) == "expires" then
						-- For version 0 cookies we must allow one comma for "expires".
						_, pos, value = string.find(s, "([^,]*,[^;,]*)[ \t]*", pos)
					else
						_, pos, value = string.find(s, "([^;,]*)[ \t]*", pos)
					end
					pos = pos + 1
				end
				if not value then
					return nil, string.format("Can't get value of cookie attribute \"%s\".", name)
				end
			else
				value = true
			end
			cookie[name] = value
			pos = skip_space(s, pos)
		end

		cookies[#cookies + 1] = Cookie:new( cookie )

		if pos > #s then
			break
		end

		if string.sub(s, pos, pos) ~= "," then
			return nil, string.format("Syntax error after cookie named \"%s\".", cookie.name)
		end

		pos = pos + 1
		pos = skip_space(s, pos)
	end

	return cookies
end

["httpmatch.lua" (httpmatch.lua)]

module(... or "httpmatch", package.seeall)

require "cookie"

Fp = {
	
	new = function( self, probes, matches, matchtypes )
		local o = {}
       	setmetatable(o, self)
		o.probes, o.matches, o.matchtypes = probes, matches, matchtypes
        self.__index = self
		return o
	end,
	
}

Matcher = {
	
	new = function( self, match_items, match_types )
		local o = {}
       	setmetatable(o, self)
		o.match_items, o.match_types = match_items, match_types
		o.results = {}
        self.__index = self
		return o
	end,
	
	matchHttpResponse = function( self, response, probe )
		local probe = probe or { path="/" }
		local result =  self.results[probe.path]
		local mt_by_type = Util.matchtypesByType( self.match_types )

		-- Check responses against all matchlines
		for _, match in ipairs( self.match_items ) do
			local match_groups = {}
			local matched
			-- Iterate over all match keys and value
			for match_key, match_value in pairs( match ) do
				-- handle hiearchical match keys eg. header.server
				local x = stdnse.strsplit( "%.", match_key )
				local response_value = response
				for j=1, #x do response_value = response_value[x[j]] end

				-- desc and type are not supposed to be matched so don't check them
				-- first attempt to find the match key in the response
				-- if this fails fallback to the probe (ie. path, category, etc.)
				if ( match_key:upper() ~= "DESC" and match_key:upper() ~= "TYPE" ) then
					match_groups[match_key] = { tostring(response_value):match(match_value) }
					response_value = response_value and tostring(response_value)

					-- fallback to probe
					if ( not(response_value) ) then
						response_value = probe[match_key] and tostring(probe[match_key])
					end	

					if ( response_value and response_value:match(tostring(match_value)) ) then
						matched = true
					else
						-- if ( not(response_value_uc) ) then
						-- 	stdnse.print_debug("Failed to find match_key %s", match_key )
						-- end
						matched = false
						break
					end
				end
			end	
			if ( matched ) then
				local desc = match.desc

				-- if the description is a text, attempt to do some match group replacing
				if ( "string" == type(desc) ) then
					for w in desc:gmatch("#.-_%d-#") do
						local key, no = w:match("#(.*)_(%d)#")
						local repl_txt= match_groups[key][tonumber(no)]
						if ( repl_txt ) then
							local start, stop = desc:find( w, 1, true )
							desc = desc:sub( 1, start - 1 ) .. repl_txt .. desc:sub( stop + 1 )
						end
					end
				-- if the desc is a Lua function, run it and collect the response
				elseif ( "function" == type(desc) ) then
					desc = desc(response)
				end
				result = result or {}
				local multiple = ( mt_by_type[match.type or "unknown"] ) and \
mt_by_type[match.type or "unknown"].multiple or false

				if ( not( match.type ) ) then
					stdnse.print_debug("ERROR: match (%s) has no type", match.desc )
				elseif ( multiple or not( result[match.type] ) ) then
					result[match.type] = result[match.type] or {}
					table.insert( result[match.type], desc )
				end
			end
		end
		if ( result ) then 
			self.results[probe.path] = result
			return true
		end
		return false
	end,
	
	formatOutput = function( self )
		-- add all path related data
		local output = {}
		for path, match in pairs(self.results) do
			local result_part = {}
			for _, t in pairs( self.match_types ) do
				for k, v in pairs(match) do
					if ( k == t.type ) then
						if ( #v > 1 or "table" == type(v[1]) ) then
							table.insert(result_part, { name=t.desc or ("Unknown match type \
(%s)"):format(k), v } )  else
							table.insert(result_part, ("%s: %s"):format(t.desc, v[1] or ""))
						end
					end
				end
			end		
			result_part.name = ("path = %s"):format(path)
			table.insert( output, result_part )
		end
		return output
	end,

}

Util = {

	matchtypesByType = function( mt )
		local tbl = {}
		for _, v in ipairs(mt) do tbl[v.type] = v end
		return tbl
	end,

}

Helper = {

	new = function( self )
		local o = {}
       	setmetatable(o, self)
        self.__index = self
		return o
	end,

	--- Sets a category filter
	-- When set, only probes matching the name of the category will be loaded.
	--
	-- @category string containing the category name
	setCategory = function( self, category )
		self.category = category
	end,
	
	--- Sets any options that can be used by probes or matches
	--
	-- @param options table containing any number of options
	setOptions = function( self, options )
		self.options = options
	end,
	
	--- Loads a finger print file
	-- The file can contain "probe", "match" and "matchtype" definitions
	--
	-- @param filename the name of the file to load
	-- @return status true on success, false on failure
	-- @return Fp instance containing probes, matches and matchtypes
	--         err string containing error if status is false 
	loadFpFile = function( self, filename )
	
		local file, err = loadfile(filename)

		if ( not(file) ) then
			return false, ("ERROR: Failed to load fingerprints:\n%s"):format(err)
		end
		
		local probes, matches, matchtypes = {}, {}, {}
		setfenv(file, setmetatable({
			options = self.options or {};
			probe = function(p)	
				if ( not(self.category) or self.category == p.category ) then
					if ( p.suffix ) then
						local suffixes = stdnse.strsplit(",", p.suffix )
						local path = p.path
						for _, s in ipairs(suffixes) do
							local new_p = {}
							for k, v in pairs( p ) do new_p[k] = v end
							new_p.path = path .. "." .. s
							table.insert( probes, new_p )
						end
					else
						table.insert(probes, p)
					end
				end
			end;
			match = function(m)	table.insert(matches, m) end;
			matchtype = function(mt) table.insert(matchtypes, mt) end;
		}, {__index = _G}))
		
		file()
		
		return true, Fp:new( probes, matches, matchtypes )
	end,
	
}


["urls.txt" (urls.txt)]

-- This is a comment

-- The purpose of the matchtypes is to group information from matches
-- The desc attribute contains the topic under which the information is presented
-- The matcher stops matching if a match is made for a specific type, unless the \
multiple attribute is set to true. matchtype { type="dev", desc="Device" }
matchtype { type="app", desc="Application" }
matchtype { type="ver", desc="Version" }
matchtype { type="framework", desc="Framework" }
matchtype { type="cookie", desc="Cookie" }
matchtype { type="auth", desc="Authentication" }
matchtype { type="additional", desc="Additional information", multiple=true}
matchtype { type="debug", desc="Debug information", multiple=true}

-- A probe consists of a mandatory path attribute
-- The verb attribute is optional and specifies what HTTP verb is supposed to be used
-- The category attribute groups probes into categories which can be used in targeted \
scans

-- General probes
probe { category="admin", path="/admin/", verb="GET" }
probe { path="/", verb="GET" }

-- Directory guesses
probe { path="/demo/" }
probe { path="/services/" }
probe { path="/backup/" }
probe { path="/test/" }
probe { path="/old/" }
probe { path="/new/" }
probe { path="/pics/" }
probe { path="/images/" }
probe { path="/includes/" }
probe { path="/menu/" }

-- Java appserver related folders
probe { path="/WEB-INF/"}
probe { path="/WEB-INF/web.xml"}

-- File guesses
probe { path="/test", suffix="asp,aspx,cf,php,jsp,exe,com,do,html" }
probe { path="/index", suffix="asp,aspx,cf,php,jsp,exe,com,do,html" }
probe { path="/admin", suffix="asp,aspx,cf,php,jsp,exe,com,do,html" }
probe { path="/default", suffix="asp,aspx,cf,php,jsp,exe,com,do,html"}


-- Guess for users when public_html is enabled
probe { path="/~root/" }
probe { path="/~apache/" }
probe { path="/~httpd/" }

-- Mail orientedprobes
probe { category="mail", path="/mail/", verb="GET"}
probe { category="mail", path="/webmail/", verb="GET" }
probe { category="mail", path="/squirrelmail/", verb="GET" }

probe { category="wiki", path="/mediawiki/" }

-- Wordpress orientedprobes
probe{ category="blog", path="/wordpress/", verb="GET" }
probe{ category="blog", path="/wp/", verb="GET" }

-- Frontpage orientedprobes
probe { category="frontpage", path="/_vti_bin/", verb="GET" }
probe { category="frontpage", path="/_vti_cnf/", verb="GET" }
probe { category="frontpage", path="/_vti_log/", verb="GET" }
probe { category="frontpage", path="/_vti_pvt/", verb="GET" }
probe { category="frontpage", path="/_vti_txt/", verb="GET" }

-- Outlook Web Access
probe { category="mail", path="/CookieAuth.dll?GetLogon?curl=/&reason=0&formdir=1", \
verb="GET"} probe { category="mail", path="/owa/auth/logon.aspx", verb="GET"}

-- SquirrelMail
probe { category="mail", path="/squirrelmail/src/login.php", verb="GET"}

-- Cisco IronPort
probe { category="mail", path="/websafe/help", verb="GET" }

-- Oracle
probe { category="oracle", path="/OA_HTML/AppsLocalLogin.jsp", verb="GET"}

-- Domino
probe { category="domino", path="/help/help8_admin.nsf/Main?OpenFrameSet", \
verb="GET"} probe { category="domino", path="/852566C90012664F" }
probe { category="domino", path="/AgentRunner.nsf" }
probe { category="domino", path="/DEASAppDesign.nsf" }
probe { category="domino", path="/DEASLog.nsf" }
probe { category="domino", path="/DEASLog01.nsf" }
probe { category="domino", path="/DEASLog02.nsf" }
probe { category="domino", path="/DEASLog03.nsf" }
probe { category="domino", path="/DEASLog04.nsf" }
probe { category="domino", path="/DEASLog05.nsf" }
probe { category="domino", path="/DEESAdmin.nsf" }
probe { category="domino", path="/a_domlog.nsf" }
probe { category="domino", path="/account.nsf" }
probe { category="domino", path="/accounts.nsf" }
probe { category="domino", path="/activity.nsf" }
probe { category="domino", path="/adm-bin/acls.exe" }
probe { category="domino", path="/adm-bin/alerts.exe" }
probe { category="domino", path="/adm-bin/console.exe" }
probe { category="domino", path="/adm-bin/listdb.exe" }
probe { category="domino", path="/adm-bin/webstats.exe" }
probe { category="domino", path="/admin.nsf" }
probe { category="domino", path="/admin4.nsf" }
probe { category="domino", path="/admin5.nsf" }
probe { category="domino", path="/adminadm0disk.nsf" }
probe { category="domino", path="/adminadm0plog.nsf" }
probe { category="domino", path="/agentrunner.nsf" }
probe { category="domino", path="/alog.nsf" }
probe { category="domino", path="/alog4.nsf" }
probe { category="domino", path="/archive/a_domlog.nsf" }
probe { category="domino", path="/archive/l_domlog.nsf" }
probe { category="domino", path="/billing.nsf" }
probe { category="domino", path="/bookmark.nsf" }
probe { category="domino", path="/bookmarks.nsf" }
probe { category="domino", path="/books.nsf" }
probe { category="domino", path="/busytime.nsf" }
probe { category="domino", path="/calendar.nsf" }
probe { category="domino", path="/catalog.nsf" }
probe { category="domino", path="/cersvr.nsf" }
probe { category="domino", path="/certa.nsf" }
probe { category="domino", path="/certlog.nsf" }
probe { category="domino", path="/certsrv.nsf " }
probe { category="domino", path="/certsrv.nsf" }
probe { category="domino", path="/chatlog.nsf" }
probe { category="domino", path="/clbusy.nsf" }
probe { category="domino", path="/cldbdir.nsf" }
probe { category="domino", path="/clusta4.nsf" }
probe { category="domino", path="/collect4.nsf" }
probe { category="domino", path="/cpa.nsf" }
probe { category="domino", path="/customerdata" }
probe { category="domino", path="/da.nsf" }
probe { category="domino", path="/database.nsf" }
probe { category="domino", path="/db.nsf" }
probe { category="domino", path="/dba4.nsf" }
probe { category="domino", path="/dbdirman.nsf" }
probe { category="domino", path="/dclf.nsf" }
probe { category="domino", path="/decsadm.nsf" }
probe { category="domino", path="/decslog.nsf" }
probe { category="domino", path="/default.nsf" }
probe { category="domino", path="/deslog.nsf" }
probe { category="domino", path="/diiop_ior.txt" }
probe { category="domino", path="/dirassist.nsf" }
probe { category="domino", path="/doc/dspug.nsf" }
probe { category="domino", path="/doc/helpadmn.nsf" }
probe { category="domino", path="/doc/javapg.nsf" }
probe { category="domino", path="/doc/readmec.nsf" }
probe { category="domino", path="/doladmin.nsf" }
probe { category="domino", path="/domadmin.nsf" }
probe { category="domino", path="/domcfg.nsf" }
probe { category="domino", path="/domguide.nsf" }
probe { category="domino", path="/domlog.nsf" }
probe { category="domino", path="/dspug.nsf" }
probe { category="domino", path="/event.nsf" }
probe { category="domino", path="/events.nsf" }
probe { category="domino", path="/events4.nsf" }
probe { category="domino", path="/events5.nsf" }
probe { category="domino", path="/group.nsf" }
probe { category="domino", path="/groups.nsf" }
probe { category="domino", path="/help/decsdoc.nsf" }
probe { category="domino", path="/help/decsdoc6.nsf" }
probe { category="domino", path="/help/dols_help.nsf" }
probe { category="domino", path="/help/help5_admin.nsf" }
probe { category="domino", path="/help/help5_client.nsf" }
probe { category="domino", path="/help/help5_designer.nsf" }
probe { category="domino", path="/help/help65_admin.nsf" }
probe { category="domino", path="/help/help65_client.nsf" }
probe { category="domino", path="/help/help65_designer.nsf" }
probe { category="domino", path="/help/lccon.nsf" }
probe { category="domino", path="/help/lccon6.nsf" }
probe { category="domino", path="/help/lsxlc.nsf" }
probe { category="domino", path="/help/lsxlc6.nsf" }
probe { category="domino", path="/help/readme.nsf" }
probe { category="domino", path="/help4.nsf" }
probe { category="domino", path="/helplt4.nsf" }
probe { category="domino", path="/hidden.nsf" }
probe { category="domino", path="/homepage.nsf" }
probe { category="domino", path="/iNotes/Forms5.nsf" }
probe { category="domino", path="/iNotes/Forms5.nsf/$DefaultNav" }
probe { category="domino", path="/iNotes/Forms6.nsf" }
probe { category="domino", path="/iNotes/help65_iwa_en.nsf" }
probe { category="domino", path="/iNotesForms5.nsf" }
probe { category="domino", path="/jotter.nsf" }
probe { category="domino", path="/l_domlog.nsf" }
probe { category="domino", path="/lccon.nsf" }
probe { category="domino", path="/ldap.nsf" }
probe { category="domino", path="/leiadm.nsf" }
probe { category="domino", path="/leilog.nsf" }
probe { category="domino", path="/leivlt.nsf" }
probe { category="domino", path="/lndfr.nsf" }
probe { category="domino", path="/log.nsf" }
probe { category="domino", path="/log4a.nsf" }
probe { category="domino", path="/loga4.nsf" }
probe { category="domino", path="/lsxlc.nsf" }
probe { category="domino", path="/mab.nsf" }
probe { category="domino", path="/mail.box" }
probe { category="domino", path="/mail/NOMBRE_USUARIO.nsf" }
probe { category="domino", path="/mail/admin.nsf" }
probe { category="domino", path="/mail/pxp.nsf" }
probe { category="domino", path="/mail1.box" }
probe { category="domino", path="/mail10.box" }
probe { category="domino", path="/mail2.box" }
probe { category="domino", path="/mail3.box" }
probe { category="domino", path="/mail4.box" }
probe { category="domino", path="/mail5.box" }
probe { category="domino", path="/mail6.box" }
probe { category="domino", path="/mail7.box" }
probe { category="domino", path="/mail8.box" }
probe { category="domino", path="/mail9.box" }
probe { category="domino", path="/mailw46.nsf" }
probe { category="domino", path="/msdwda.nsf" }
probe { category="domino", path="/mtatbls.nsf" }
probe { category="domino", path="/mtstore.nsf" }
probe { category="domino", path="/names.nsf" }
probe { category="domino", path="/nntp/nd000000.nsf" }
probe { category="domino", path="/nntp/nd000001.nsf" }
probe { category="domino", path="/nntp/nd000002.nsf" }
probe { category="domino", path="/nntp/nd000003.nsf" }
probe { category="domino", path="/nntp/nd000004.nsf" }
probe { category="domino", path="/nntppost.nsf" }
probe { category="domino", path="/notes.nsf" }
probe { category="domino", path="/ntsync4.nsf" }
probe { category="domino", path="/ntsync45.nsf" }
probe { category="domino", path="/patrol41.nsf" }
probe { category="domino", path="/perweb.nsf" }
probe { category="domino", path="/private.nsf" }
probe { category="domino", path="/proghelp/KBCCV11.NSF" }
probe { category="domino", path="/proghelp/KBNV11.NSF" }
probe { category="domino", path="/proghelp/KBSSV11.NSF" }
probe { category="domino", path="/public.nsf" }
probe { category="domino", path="/puserinfo.nsf" }
probe { category="domino", path="/qpadmin.nsf" }
probe { category="domino", path="/qstart.nsf" }
probe { category="domino", path="/quickplace/quickplace/main.nsf" }
probe { category="domino", path="/quickplacequickplacemain.nsf" }
probe { category="domino", path="/quickstart/qstart50.nsf" }
probe { category="domino", path="/quickstart/wwsample.nsf" }
probe { category="domino", path="/readme.nsf" }
probe { category="domino", path="/reports.nsf" }
probe { category="domino", path="/resource.nsf" }
probe { category="domino", path="/sample/faqw46.nsf" }
probe { category="domino", path="/sample/framew46.nsf" }
probe { category="domino", path="/sample/pagesw46.nsf" }
probe { category="domino", path="/sample/siregw46.nsf" }
probe { category="domino", path="/sample/site1w46.nsf" }
probe { category="domino", path="/sample/site2w46.nsf" }
probe { category="domino", path="/sample/site3w46.nsf" }
probe { category="domino", path="/schema.nsf" }
probe { category="domino", path="/schema50.nsf" }
probe { category="domino", path="/secret.nsf" }
probe { category="domino", path="/setup.nsf" }
probe { category="domino", path="/setupweb.nsf" }
probe { category="domino", path="/smbcfg.nsf" }
probe { category="domino", path="/smconf.nsf" }
probe { category="domino", path="/smency.nsf" }
probe { category="domino", path="/smhelp.nsf" }
probe { category="domino", path="/smmsg.nsf" }
probe { category="domino", path="/smquar.nsf" }
probe { category="domino", path="/smsolar.nsf" }
probe { category="domino", path="/smtime.nsf" }
probe { category="domino", path="/smtp.box" }
probe { category="domino", path="/smtp.nsf" }
probe { category="domino", path="/smtpibwq.nsf" }
probe { category="domino", path="/smtpobwq.nsf" }
probe { category="domino", path="/smtptbls.nsf" }
probe { category="domino", path="/smvlog.nsf" }
probe { category="domino", path="/software.nsf" }
probe { category="domino", path="/srvnam.htm" }
probe { category="domino", path="/srvnam.nsf" }
probe { category="domino", path="/statauths.nsf" }
probe { category="domino", path="/statautht.nsf" }
probe { category="domino", path="/statmail.nsf" }
probe { category="domino", path="/statrep.nsf" }
probe { category="domino", path="/stauths.nsf" }
probe { category="domino", path="/stautht.nsf" }
probe { category="domino", path="/stconf.nsf" }
probe { category="domino", path="/stconfig.nsf" }
probe { category="domino", path="/stdnaset.nsf" }
probe { category="domino", path="/stdomino.nsf" }
probe { category="domino", path="/stlog.nsf" }
probe { category="domino", path="/streg.nsf" }
probe { category="domino", path="/stsrc.nsf" }
probe { category="domino", path="/test.nsf" }
probe { category="domino", path="/userreg.nsf" }
probe { category="domino", path="/users.nsf" }
probe { category="domino", path="/vpuserinfo.nsf" }
probe { category="domino", path="/web.nsf" }
probe { category="domino", path="/webadmin.nsf" }
probe { category="domino", path="/welcome.nsf" }

-- Java Application Servers
-- JBoss
probe { category="java", path="/jmx-console/", verb="GET" }

-- Robots txt
probe { category="misc", path="/robots.txt", verb="GET" }

-- SSL VPN
-- Juniper

-- fixa så att followredir funkar som auth check biten
probe { category="vpn", path="/dana-na/"}

-- Citrix Access Gateway
probe { category="vpn", path="/vpn/index.html"}
probe { category="vpn", path="/Citrix/AccessPlatform/auth/login.aspx"}
probe { category="vpn", path="/Citrix/XenApp/auth/login.aspx"}

probe { category="misc", path="/icons/"}
probe { category="misc", path="/api/"}

-- Matches start here
-- The names of the fields in a match correspond to the fields in the http response
-- The desc field contains the message to print if the match is made and can contain \
                a regexp match pattern from the other fields
-- * A regexp match pattern that is to be replaced in the desc field should contain \
                the name of the field followed by an underscore and a number
-- * Eg. #status_1# - is replaced by the first match in the status field
-- The desc field may also contain a function which is to be executed if a match is \
                done
-- * The function receives the response as the first parameter and should return a \
string as a result

-- Framework oriented matches

-- Detect .NET framework and version number
match { status="200", ['header.x-powered-by']="(ASP.NET)", \
['header.x-aspnet-version']="(.*)", type="framework", desc="#header.x-powered-by_1# \
#header.x-aspnet-version_1#" } match { status="200", ['header.x-powered-by']="(.*)", \
type="framework", desc="#header.x-powered-by_1#"} match { \
['header.set-cookie']="PHPSESSID", type="framework", desc="PHP framework" }

-- Application related matches
match { body="\<input.*type=.*password.*", type="auth", desc="Form based \
authentication"} match { status=(options.reportauth and "401" or -1), type="auth", \
desc="Authorization required (401)" }

-- 403 on folders should result in "Directory exists"
match { status="403", type="auth", desc="The request was forbidden (403)"}
match { status="403", type="additional", desc="Directory exists"}


-- Cookie match
match { status="200", ['header.set-cookie']="(.*)", type="cookie", \
desc="#header.set-cookie_1#" }

-- Horde related matches
match { ['header.set-cookie']="Horde\=(.*)\;", type="app", desc="Horde webmail" }
match { ['header.set-cookie']="Horde\=(.*)\;", type="app", desc="Horde Cookie: \
#header.set-cookie_1#" }

-- WordPress related matches
--match { path="/wp/", status="(.*)", desc="HTTP Status Code: #status_1#" }
match { status="200", body="WordPress", type="app", desc="WordPress" }
match { status="200", body="\<meta name=\"generator\" content=\"(WordPress.-)\"", \
type="app", desc="#body_1#" }

-- Joomla related matches
match { status="200", body="\<meta name=\"generator\" content=\"Joomla.*\"", \
type="app", desc="Joomla" }

-- Wiki related matches
match { status="200", body="\<meta name=\"generator\" content=\"(MediaWiki.*)\"", \
type="app", desc="#body_1#"}

-- Matchline for Bubba NAS
match { status="200", body="\<title\>Bubba\|2 %- Home %((.*)%)\<\/title\>", \
type="dev", desc="Excito Bubba|2 NAS" } match { status="200", body="\<title\>Bubba\|2 \
%- Home %((.*)%)\<\/title\>", type="additional", desc="NAS Name: #body_1#" }

-- Outlook Web Access
match { status="200", body="(Microsoft Exchange %- Outlook Web Access)", type="app", \
desc="#body_1#" }

-- Squirrelmail
match { status="200", body="squirrelmail_loginpage_onload", type="app", \
desc="Squirrelmail"} match { status="200", body="SquirrelMail version (.-)\<", \
type="ver", desc="#body_1#"}

-- Cisco IOS stuff
match { path="^/$", status="401", ['header.server']="cisco%-IOS", type="app", \
desc="Cisco IOS"} match { path="^/$", status="401", \
['header.www-authenticate']="level.*15.*access", type="app", desc="Cisco IOS"}

-- VPNs

-- Status could be 200 or 400 invalid path, so leave it empty
match { path="^/dana%-na/$", body="Copyright.*Juniper Networks", type="dev", \
desc="Juniper SSL VPN"}

-- Oracle
match { path="^/OA_HTML/AppsLocalLogin.jsp$", status="200", type="app", desc="Oracle \
E-Business Suite" }

-- IronPort
match { path="^/websafe/help$", status="200", body="IronPort WebSite", type="app", \
desc="IronPort Encryption Appliance"}

-- IBM Lotus Domino
match { category="domino", status="200", type="additional", desc="Found notes \
database" }

-- JBoss
match { category="java", status="401", ['header.www-authenticate']="JBoss JMX \
Console", type="app", desc="JBoss JMX Console"} match { category="java", \
status="401", body="(Apache Tomcat/[^%s]*)", type="ver", desc="#body_1#"} match { \
category="java", status="401", body="(JBoss Web/[^%s]*)", type="ver", \
desc="#body_1#"}

-- Match Citrix
match { status="200", \
body="\<[Tt][Ii][Tt][Ll][Ee]\>%s*(Citrix.*)\<\/[Tt][Ii][Tt][Ll][Ee]\>", type="app", \
desc="#body_1#"} match { status="200", body="\<[Tt][Ii][Tt][Ll][Ee]\>%s*(Citrix \
XenApp).*\<\/[Tt][Ii][Tt][Ll][Ee]\>", type="app", desc="#body_1#"}


match { status="200", body="[Ii]ndex of /.*", type="additional", desc="Directory \
Indexing"}

-- Calculate SHA1 hash
match { status="200", type="additional", desc = function(r) return "SHA1 hash: " .. \
select(2, bin.unpack("H20", openssl.sha1(r.body))) end}

-- robots contents
match { path="^/robots.txt$", status="200", type="additional", desc=
function(r) 
	local tbl = stdnse.strsplit("\r?\n", r.body)
	tbl.name = "Robots content"
	return tbl
end
}

-- Error related match lines
match { status="(5.*)", type="additional", desc="An error (#status_1#) occured"}
match { status=(options.showredir and "^30." or "-1"), ['header.location']="(.*)", \
type="additional", desc="Redirect to #header.location_1#"}

match { category="frontpage", status="200", type="additional", desc="FrontPage Server \
Extensions"}

-- debugging
match { status=(options.debug and "([23].*)" or "-1"), ['header.server']="(.*)", \
type="debug", desc=  function(r) 
		local headers = {}
		for k, v in pairs(r.header) do
			table.insert(headers, ("%s: %s"):format( k, v ) )
		end
		headers.name = "HTTP Headers"
		return headers
	end 
}
match { status=(options.debug and "([23].*)" or "-1"), ['header.server']="(.*)", \
type="debug", desc="Server information: #header.server_1#" } match { \
status=(options.debug and "([23].*)" or "-1"), ['header.date']="(.*)", type="debug", \
desc="Server Date: #header.date_1#" }

match { status="200", type="debug", ['header.set-cookie']='.*', desc=
	function(r) 
		local cookies = cookie.parse(r.header['set-cookie'] )
		local result = {}
	
		for _, cookie in ipairs(cookies) do
			if ( cookie:isSessionCookie() and not( cookie:isHttpOnly() ) ) then
				table.insert( result, ("OWASP-SM-002: Cookie (%s) is not set as \
HttpOnly"):format( cookie:getName() ) )  end
		end
		return result
	end 
}

match { status="200", type="debug", ['header.set-cookie']='.*', desc=
	function(r) 
		local cookies = cookie.parse(r.header['set-cookie'] )
		local result = {}

		for _, cookie in ipairs(cookies) do
			if ( options.ssl and not( cookie:isSecure() ) ) then
				table.insert( result, ("OWASP-SM-002: Cookie (%s) is not set as secure"):format( \
cookie:getName() ) )  end
		end
		return result
	end 
}



--
Patrik Karlsson
http://www.cqure.net
http://www.twitter.com/nevdull77



_______________________________________________
Sent through the nmap-dev mailing list
http://cgi.insecure.org/mailman/listinfo/nmap-dev
Archived at http://seclists.org/nmap-dev/

[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic