mirror of
https://github.com/nmap/nmap.git
synced 2025-12-06 12:41:29 +00:00
o [NSE] Added the Apache JServer Protocol (AJP) library and the scripts
ajp-methods, ajp-headers and ajp-auth. [Patrik Karlsson]
This commit is contained in:
@@ -1,5 +1,8 @@
|
|||||||
# Nmap Changelog ($Id$); -*-text-*-
|
# Nmap Changelog ($Id$); -*-text-*-
|
||||||
|
|
||||||
|
o [NSE] Added the Apache JServer Protocol (AJP) library and the scripts
|
||||||
|
ajp-methods, ajp-headers and ajp-auth. [Patrik Karlsson]
|
||||||
|
|
||||||
o In XML output, <osclass> elements are now child elements of the
|
o In XML output, <osclass> elements are now child elements of the
|
||||||
<osmatch> they belong to. Old output was thus:
|
<osmatch> they belong to. Old output was thus:
|
||||||
<os><osclass/><osclass/>...<osmatch/><osmatch/>...</os>
|
<os><osclass/><osclass/>...<osmatch/><osmatch/>...</os>
|
||||||
|
|||||||
430
nselib/ajp.lua
Normal file
430
nselib/ajp.lua
Normal file
@@ -0,0 +1,430 @@
|
|||||||
|
---
|
||||||
|
-- A basic AJP 1.3 implementation based on documentation available from Apache
|
||||||
|
-- mod_proxy_ajp; http://httpd.apache.org/docs/2.2/mod/mod_proxy_ajp.html
|
||||||
|
--
|
||||||
|
-- @author "Patrik Karlsson <patrik@cqure.net>"
|
||||||
|
--
|
||||||
|
module(... or "ajp",package.seeall)
|
||||||
|
|
||||||
|
local bin = require('bin')
|
||||||
|
local match = require('match')
|
||||||
|
local url = require('url')
|
||||||
|
|
||||||
|
AJP = {
|
||||||
|
|
||||||
|
-- The magic prefix that has to be present in all requests
|
||||||
|
Magic = 0x1234,
|
||||||
|
|
||||||
|
-- Methods encoded as numeric values
|
||||||
|
Method = {
|
||||||
|
['OPTIONS'] = 1,
|
||||||
|
['GET'] = 2,
|
||||||
|
['HEAD'] = 3,
|
||||||
|
['POST'] = 4,
|
||||||
|
['PUT'] = 5,
|
||||||
|
['DELETE'] = 6,
|
||||||
|
['TRACE'] = 7,
|
||||||
|
['PROPFIND'] = 8,
|
||||||
|
['PROPPATCH'] = 9,
|
||||||
|
['MKCOL'] = 10,
|
||||||
|
['COPY'] = 11,
|
||||||
|
['MOVE'] = 12,
|
||||||
|
['LOCK'] = 13,
|
||||||
|
['UNLOCK'] = 14,
|
||||||
|
['ACL'] = 15,
|
||||||
|
['REPORT'] = 16,
|
||||||
|
['VERSION-CONTROL'] = 17,
|
||||||
|
['CHECKIN'] = 18,
|
||||||
|
['CHECKOUT'] = 19,
|
||||||
|
['UNCHECKOUT'] = 20,
|
||||||
|
['SEARCH'] = 21,
|
||||||
|
['MKWORKSPACE'] = 22,
|
||||||
|
['UPDATE'] = 23,
|
||||||
|
['LABEL'] = 24,
|
||||||
|
['MERGE'] = 25,
|
||||||
|
['BASELINE_CONTROL'] = 26,
|
||||||
|
['MKACTIVITY'] = 27,
|
||||||
|
},
|
||||||
|
|
||||||
|
-- Request codes
|
||||||
|
Code = {
|
||||||
|
FORWARD_REQUEST = 2,
|
||||||
|
SEND_BODY = 3,
|
||||||
|
SEND_HEADERS = 4,
|
||||||
|
END_RESPONSE = 5,
|
||||||
|
SHUTDOWN = 7,
|
||||||
|
PING = 8,
|
||||||
|
CPING = 10,
|
||||||
|
},
|
||||||
|
|
||||||
|
-- Request attributes
|
||||||
|
Attribute = {
|
||||||
|
CONTEXT = 0x01,
|
||||||
|
SERVLET_PATH = 0x02,
|
||||||
|
REMOTE_USER = 0x03,
|
||||||
|
AUTH_TYPE = 0x04,
|
||||||
|
QUERY_STRING = 0x05,
|
||||||
|
JVM_ROUTE = 0x06,
|
||||||
|
SSL_CERT = 0x07,
|
||||||
|
SSL_CIPHER = 0x08,
|
||||||
|
SSL_SESSION = 0x09,
|
||||||
|
REQ_ATTRIBUTE= 0x0A,
|
||||||
|
SSL_KEY_SIZE = 0x0B,
|
||||||
|
ARE_DONE = 0xFF,
|
||||||
|
},
|
||||||
|
|
||||||
|
ForwardRequest = {
|
||||||
|
|
||||||
|
-- Common headers encoded as numeric values
|
||||||
|
Header = {
|
||||||
|
['accept'] = 0xA001,
|
||||||
|
['accept-charset'] = 0xA002,
|
||||||
|
['accept-encoding'] = 0xA003,
|
||||||
|
['accept-language'] = 0xA004,
|
||||||
|
['authorization'] = 0xA005,
|
||||||
|
['connection'] = 0xA006,
|
||||||
|
['content-type'] = 0xA007,
|
||||||
|
['content-length'] = 0xA008,
|
||||||
|
['cookie'] = 0xA009,
|
||||||
|
['cookie2'] = 0xA00A,
|
||||||
|
['host'] = 0xA00B,
|
||||||
|
['pragma'] = 0xA00C,
|
||||||
|
['referer'] = 0xA00D,
|
||||||
|
['user-agent'] = 0xA00E,
|
||||||
|
},
|
||||||
|
|
||||||
|
new = function(self, host, port, method, uri, headers, attributes, options)
|
||||||
|
local o = {
|
||||||
|
host = host,
|
||||||
|
magic = 0x1234,
|
||||||
|
length = 0,
|
||||||
|
code = AJP.Code.FORWARD_REQUEST,
|
||||||
|
method = AJP.Method[method],
|
||||||
|
version = "HTTP/1.1",
|
||||||
|
uri = uri,
|
||||||
|
raddr = options.raddr or "127.0.0.1",
|
||||||
|
rhost = options.rhost or "",
|
||||||
|
srv = host.ip,
|
||||||
|
port = port.number,
|
||||||
|
is_ssl = (port.service == "https"),
|
||||||
|
headers = headers or {},
|
||||||
|
attributes = attributes or {},
|
||||||
|
}
|
||||||
|
setmetatable(o, self)
|
||||||
|
self.__index = self
|
||||||
|
return o
|
||||||
|
end,
|
||||||
|
|
||||||
|
__tostring = function(self)
|
||||||
|
|
||||||
|
-- encodes a string, prefixing it with a 2-byte length
|
||||||
|
-- and suffixing it with a zero. P-encoding can't be used
|
||||||
|
-- as the zero terminator should not be counted in the length
|
||||||
|
local function encstr(str)
|
||||||
|
if ( not(str) or #str == 0 ) then
|
||||||
|
return bin.pack(">S", 0xFFFF)
|
||||||
|
end
|
||||||
|
return bin.pack(">Sz", #str, str)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- count the number of headers
|
||||||
|
local function headerCount()
|
||||||
|
local i = 0
|
||||||
|
for _, _ in pairs(self.headers) do i = i + 1 end
|
||||||
|
return i
|
||||||
|
end
|
||||||
|
|
||||||
|
-- add host header if it's missing
|
||||||
|
if ( not(self.headers['host']) ) then
|
||||||
|
self.headers['host'] = self.host.ip
|
||||||
|
end
|
||||||
|
|
||||||
|
-- add keep-alive connection header if missing
|
||||||
|
if ( not(self.headers['connection']) ) then
|
||||||
|
self.headers['connection'] = "keep-alive"
|
||||||
|
end
|
||||||
|
|
||||||
|
local p_url = url.parse(self.uri)
|
||||||
|
|
||||||
|
-- save the magic and data for last
|
||||||
|
local data = bin.pack(">CCAAAAASCS", self.code, self.method,
|
||||||
|
encstr(self.version), encstr(p_url.path), encstr(self.raddr),
|
||||||
|
encstr(self.rhost), encstr(self.srv),
|
||||||
|
self.port, (self.is_ssl and 1 or 0),
|
||||||
|
headerCount())
|
||||||
|
|
||||||
|
-- encode headers
|
||||||
|
for k, v in pairs(self.headers) do
|
||||||
|
local header = AJP.ForwardRequest.Header[k:lower()] or k
|
||||||
|
if ( "string" == type(header) ) then
|
||||||
|
data = data .. bin.pack(">Sz", #header, header)
|
||||||
|
else
|
||||||
|
data = data .. bin.pack(">S", header)
|
||||||
|
end
|
||||||
|
|
||||||
|
data = data .. encstr(v)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- encode attributes
|
||||||
|
if ( p_url.query ) then
|
||||||
|
data = data .. bin.pack("C", AJP.Attribute.QUERY_STRING)
|
||||||
|
data = data .. encstr(p_url.query)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- terminate the attribute list
|
||||||
|
data = data .. bin.pack("C", AJP.Attribute.ARE_DONE)
|
||||||
|
|
||||||
|
-- returns the AJP request as a string
|
||||||
|
return bin.pack(">SSA", AJP.Magic, #data, data)
|
||||||
|
end,
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
Response = {
|
||||||
|
|
||||||
|
Header = {
|
||||||
|
['Content-Type'] = 0xA001,
|
||||||
|
['Content-Language'] = 0xA002,
|
||||||
|
['Content-Length'] = 0xA003,
|
||||||
|
['Date'] = 0xA004,
|
||||||
|
['Last-Modified'] = 0xA005,
|
||||||
|
['Location'] = 0xA006,
|
||||||
|
['Set-Cookie'] = 0xA007,
|
||||||
|
['Set-Cookie2'] = 0xA008,
|
||||||
|
['Servlet-Engine'] = 0xA009,
|
||||||
|
['Status'] = 0xA00A,
|
||||||
|
['WWW-Authenticate'] = 0xA00B,
|
||||||
|
},
|
||||||
|
|
||||||
|
SendHeaders = {
|
||||||
|
|
||||||
|
new = function(self)
|
||||||
|
local o = { headers = {}, rawheaders = {} }
|
||||||
|
setmetatable(o, self)
|
||||||
|
self.__index = self
|
||||||
|
return o
|
||||||
|
end,
|
||||||
|
|
||||||
|
parse = function(data)
|
||||||
|
local sh = AJP.Response.SendHeaders:new()
|
||||||
|
local pos = 6
|
||||||
|
local status_msg, hdr_count
|
||||||
|
|
||||||
|
pos, sh.status = bin.unpack(">S", data, pos)
|
||||||
|
pos, status_msg = bin.unpack(">P", data, pos)
|
||||||
|
pos = pos + 1
|
||||||
|
sh['status-line'] = ("AJP1.3 %d %s"):format(sh.status, status_msg)
|
||||||
|
|
||||||
|
pos, hdr_count = bin.unpack(">S", data, pos)
|
||||||
|
|
||||||
|
local function headerById(id)
|
||||||
|
for k, v in pairs(AJP.Response.Header) do
|
||||||
|
if ( v == id ) then return k end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
for i=1, hdr_count do
|
||||||
|
local key, val, len
|
||||||
|
pos, len = bin.unpack(">S", data, pos)
|
||||||
|
|
||||||
|
if ( len < 0xA000 ) then
|
||||||
|
pos, key = bin.unpack("A"..len, data, pos)
|
||||||
|
pos = pos + 1
|
||||||
|
else
|
||||||
|
key = headerById(len)
|
||||||
|
end
|
||||||
|
|
||||||
|
pos, val = bin.unpack(">P", data, pos)
|
||||||
|
pos = pos + 1
|
||||||
|
|
||||||
|
sh.headers[key:lower()] = val
|
||||||
|
|
||||||
|
-- to keep the order, in which the headers were received,
|
||||||
|
-- add them to the rawheader table as well. This is based
|
||||||
|
-- on the same principle as the http library, however the
|
||||||
|
-- difference being that we have to "construct" the "raw"
|
||||||
|
-- format of the header, as we're receiving kvp's.
|
||||||
|
table.insert(sh.rawheaders, ("%s: %s"):format(key,val))
|
||||||
|
end
|
||||||
|
return sh
|
||||||
|
end,
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
-- The Comm class handles sending and receiving AJP requests/responses
|
||||||
|
Comm = {
|
||||||
|
|
||||||
|
-- Creates a new Comm instance
|
||||||
|
new = function(self, host, port, options)
|
||||||
|
local o = { host = host, port = port, options = options or {}}
|
||||||
|
setmetatable(o, self)
|
||||||
|
self.__index = self
|
||||||
|
return o
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Connects to the AJP server
|
||||||
|
--
|
||||||
|
-- @return status true on success, false on failure
|
||||||
|
-- @return err string containing error message on failure
|
||||||
|
connect = function(self)
|
||||||
|
self.socket = nmap.new_socket()
|
||||||
|
self.socket:set_timeout(self.options.timeout or 5000)
|
||||||
|
return self.socket:connect(self.host, self.port)
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Sends a request to the server
|
||||||
|
--
|
||||||
|
-- @param req instance of object that can be serialized with tostring
|
||||||
|
-- @return status true on succes, false on failure
|
||||||
|
-- @return err string containing error message on failure
|
||||||
|
send = function(self, req)
|
||||||
|
return self.socket:send(tostring(req))
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Receives an AJP response from the server
|
||||||
|
--
|
||||||
|
-- @return status true on succes, false on failure
|
||||||
|
-- @return response table containing the following fields, or string
|
||||||
|
-- containing error message on failure
|
||||||
|
-- <code>status</code> - status of response (see HTTP status codes)
|
||||||
|
-- <code>status-line</code> - the complete status line (eg. 200 OK)
|
||||||
|
-- <code>body</code> - the response body as string
|
||||||
|
-- <code>headers</code> - table of response headers
|
||||||
|
--
|
||||||
|
receive = function(self)
|
||||||
|
local response = {}
|
||||||
|
while(true) do
|
||||||
|
local status, buf = self.socket:receive_buf(match.numbytes(4), true)
|
||||||
|
if ( not(status) ) then
|
||||||
|
return false, "Failed to receive response from server"
|
||||||
|
end
|
||||||
|
local pos, magic, length = bin.unpack(">A2S", buf)
|
||||||
|
if ( magic ~= "AB" ) then
|
||||||
|
return false, ("Invalid magic received from server (%s)"):format(magic)
|
||||||
|
end
|
||||||
|
local status, data = self.socket:receive_buf(match.numbytes(length), true)
|
||||||
|
if ( not(status) ) then
|
||||||
|
return false, "Failed to receive response from server"
|
||||||
|
end
|
||||||
|
|
||||||
|
local pos, code = bin.unpack("C", data)
|
||||||
|
if ( AJP.Code.SEND_HEADERS == code ) then
|
||||||
|
local sh = AJP.Response.SendHeaders.parse(buf .. data)
|
||||||
|
response = sh
|
||||||
|
elseif( AJP.Code.SEND_BODY == code ) then
|
||||||
|
response.body = select(2, bin.unpack(">P", data, pos))
|
||||||
|
elseif( AJP.Code.END_RESPONSE == code ) then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true, response
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- Closes the socket
|
||||||
|
close = function(self)
|
||||||
|
return self.socket:close()
|
||||||
|
end,
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Helper = {
|
||||||
|
|
||||||
|
--- Creates a new AJP Helper instance
|
||||||
|
--
|
||||||
|
-- @param host table
|
||||||
|
-- @param port table
|
||||||
|
-- @param opt
|
||||||
|
-- @return o new Helper instance
|
||||||
|
new = function(self, host, port, opt)
|
||||||
|
local o = { host = host, port = port, opt = opt }
|
||||||
|
setmetatable(o, self)
|
||||||
|
self.__index = self
|
||||||
|
return o
|
||||||
|
end,
|
||||||
|
|
||||||
|
--- Connects to the AJP server
|
||||||
|
--
|
||||||
|
-- @return status true on success, false on failure
|
||||||
|
-- @return err string containing error message on failure
|
||||||
|
connect = function(self)
|
||||||
|
self.comm = Comm:new(self.host, self.port, self.opt)
|
||||||
|
return self.comm:connect()
|
||||||
|
end,
|
||||||
|
|
||||||
|
--- Sends an AJP request to the server
|
||||||
|
--
|
||||||
|
-- @param url string containing the URL to query
|
||||||
|
-- @param headers table containing optional headers
|
||||||
|
-- @param attributes table containing optional attributes
|
||||||
|
-- @return status true on succes, false on failure
|
||||||
|
-- @return response table (@see Comm.receive), or string containing error
|
||||||
|
-- message on failure
|
||||||
|
request = function(self, method, url, headers, attributes)
|
||||||
|
local status, lhost, lport, rhost, rport = self.comm.socket:get_info()
|
||||||
|
local options = {}
|
||||||
|
|
||||||
|
if ( status ) then
|
||||||
|
options.raddr = rhost
|
||||||
|
end
|
||||||
|
|
||||||
|
local request = AJP.ForwardRequest:new(self.host, self.port, method, url, headers, attributes, options)
|
||||||
|
if ( not(self.comm:send(request)) ) then
|
||||||
|
return false, "Failed to send request to server"
|
||||||
|
end
|
||||||
|
return self.comm:receive()
|
||||||
|
end,
|
||||||
|
|
||||||
|
--- Sends an AJP GET request to the server
|
||||||
|
--
|
||||||
|
-- @param url string containing the URL to query
|
||||||
|
-- @param headers table containing optional headers
|
||||||
|
-- @param attributes table containing optional attributes
|
||||||
|
-- @return status true on succes, false on failure
|
||||||
|
-- @return response table (@see Comm.receive), or string containing error
|
||||||
|
-- message on failure
|
||||||
|
get = function(self, url, headers, attributes)
|
||||||
|
return self:request("GET", url, headers, attributes)
|
||||||
|
end,
|
||||||
|
|
||||||
|
--- Sends an AJP HEAD request to the server
|
||||||
|
--
|
||||||
|
-- @param url string containing the URL to query
|
||||||
|
-- @param headers table containing optional headers
|
||||||
|
-- @param attributes table containing optional attributes
|
||||||
|
-- @return status true on succes, false on failure
|
||||||
|
-- @return response table (@see Comm.receive), or string containing error
|
||||||
|
-- message on failure
|
||||||
|
head = function(self, url, headers, attributes)
|
||||||
|
return self:request("HEAD", url, headers, attributes)
|
||||||
|
end,
|
||||||
|
|
||||||
|
--- Sends an AJP OPTIONS request to the server
|
||||||
|
--
|
||||||
|
-- @param url string containing the URL to query
|
||||||
|
-- @param headers table containing optional headers
|
||||||
|
-- @param attributes table containing optional attributes
|
||||||
|
-- @return status true on succes, false on failure
|
||||||
|
-- @return response table (@see Comm.receive), or string containing error
|
||||||
|
-- message on failure
|
||||||
|
options = function(self, url, headers, attributes)
|
||||||
|
return self:request("OPTIONS", url, headers, attributes)
|
||||||
|
end,
|
||||||
|
|
||||||
|
-- should only work against 127.0.0.1
|
||||||
|
shutdownContainer = function(self)
|
||||||
|
self.comm:send(bin.pack("H", "1234000107"))
|
||||||
|
self.comm:receive()
|
||||||
|
end,
|
||||||
|
|
||||||
|
--- Disconnects from the server
|
||||||
|
close = function(self)
|
||||||
|
return self.comm:close()
|
||||||
|
end,
|
||||||
|
|
||||||
|
}
|
||||||
72
scripts/ajp-auth.nse
Normal file
72
scripts/ajp-auth.nse
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
description = [[
|
||||||
|
Retrieves the authentication scheme and realm of an AJP service that requires
|
||||||
|
authentication.
|
||||||
|
]]
|
||||||
|
|
||||||
|
---
|
||||||
|
-- @usage
|
||||||
|
-- nmap -p 8009 <ip> --script ajp-auth [--script-args ajp-auth.path=/login]
|
||||||
|
--
|
||||||
|
-- @output
|
||||||
|
-- PORT STATE SERVICE
|
||||||
|
-- 8009/tcp open ajp13
|
||||||
|
-- | ajp-auth:
|
||||||
|
-- |_ Digest opaque=GPui3SvCGBoHrRMMzSsgaYBV qop=auth nonce=1336063830612:935b5b389696b0f67b9193e19f47e037 realm=example.org
|
||||||
|
--
|
||||||
|
-- @args ajp-auth.path Define the request path
|
||||||
|
--
|
||||||
|
|
||||||
|
author = "Patrik Karlsson"
|
||||||
|
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
||||||
|
categories = {"default", "auth", "safe"}
|
||||||
|
|
||||||
|
local ajp = require('ajp')
|
||||||
|
local http = require('http')
|
||||||
|
local shortport = require('shortport')
|
||||||
|
|
||||||
|
portrule = shortport.port_or_service(8009, 'ajp13', 'tcp')
|
||||||
|
|
||||||
|
local arg_path = stdnse.get_script_args(SCRIPT_NAME .. ".path")
|
||||||
|
|
||||||
|
local function fail(err) return ("\n ERROR: %s"):format(err or "") end
|
||||||
|
|
||||||
|
action = function(host, port)
|
||||||
|
local helper = ajp.Helper:new(host, port)
|
||||||
|
|
||||||
|
if ( not(helper:connect()) ) then
|
||||||
|
return fail("Failed to connect to AJP server")
|
||||||
|
end
|
||||||
|
|
||||||
|
local status, answer = helper:get(arg_path or "/")
|
||||||
|
|
||||||
|
--- check for 401 response code
|
||||||
|
if ( not(status) or answer.status ~= 401 ) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local result = { name = answer["status-line"]:match("^(.*)\r?\n$") }
|
||||||
|
|
||||||
|
local www_authenticate = answer.headers["www-authenticate"]
|
||||||
|
if not www_authenticate then
|
||||||
|
table.insert( result, ("Server returned status %d but no WWW-Authenticate header."):format(answer.status) )
|
||||||
|
return stdnse.format_output(true, result)
|
||||||
|
end
|
||||||
|
|
||||||
|
local challenges = http.parse_www_authenticate(www_authenticate)
|
||||||
|
if ( not(challenges) ) then
|
||||||
|
table.insert( result, ("Server returned status %d but the WWW-Authenticate header could not be parsed."):format(answer.status) )
|
||||||
|
table.insert( result, ("WWW-Authenticate: %s"):format(www_authenticate) )
|
||||||
|
return stdnse.format_output(true, result)
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, challenge in ipairs(challenges) do
|
||||||
|
local line = challenge.scheme
|
||||||
|
if ( challenge.params ) then
|
||||||
|
for name, value in pairs(challenge.params) do
|
||||||
|
line = line .. (" %s=%s"):format(name, value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.insert(result, line)
|
||||||
|
end
|
||||||
|
return stdnse.format_output(true, result)
|
||||||
|
end
|
||||||
46
scripts/ajp-headers.nse
Normal file
46
scripts/ajp-headers.nse
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
description = [[
|
||||||
|
Performs a HEAD or GET request against either the root directory or any
|
||||||
|
optional directory and returns the server response headers.
|
||||||
|
]]
|
||||||
|
|
||||||
|
---
|
||||||
|
-- @usage
|
||||||
|
-- nmap -p 8009 <ip> --script ajp-headers
|
||||||
|
--
|
||||||
|
-- @output
|
||||||
|
-- PORT STATE SERVICE
|
||||||
|
-- 8009/tcp open ajp13
|
||||||
|
-- | ajp-headers:
|
||||||
|
-- | X-Powered-By: JSP/2.2
|
||||||
|
-- | Set-Cookie: JSESSIONID=goTHax+8ktEcZsBldANHBAuf.undefined; Path=/helloworld
|
||||||
|
-- | Content-Type: text/html;charset=ISO-8859-1
|
||||||
|
-- |_ Content-Length: 149
|
||||||
|
--
|
||||||
|
-- @args ajp-headers.path The path to request, such as <code>/index.php</code>. Default <code>/</code>.
|
||||||
|
|
||||||
|
local ajp = require('ajp')
|
||||||
|
local shortport = require('shortport')
|
||||||
|
|
||||||
|
portrule = shortport.port_or_service(8009, 'ajp13', 'tcp')
|
||||||
|
|
||||||
|
author = "Patrik Karlsson"
|
||||||
|
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
||||||
|
categories = {"discovery", "safe"}
|
||||||
|
|
||||||
|
local arg_path = stdnse.get_script_args(SCRIPT_NAME .. '.path') or "/"
|
||||||
|
|
||||||
|
local function fail(err) return ("\n ERROR: %s"):format(err or "") end
|
||||||
|
|
||||||
|
action = function(host, port)
|
||||||
|
local method
|
||||||
|
local helper = ajp.Helper:new(host, port)
|
||||||
|
helper:connect()
|
||||||
|
|
||||||
|
local status, response = helper:get(arg_path)
|
||||||
|
helper:close()
|
||||||
|
|
||||||
|
if ( not(status) ) then
|
||||||
|
return fail("Failed to retrieve server headers")
|
||||||
|
end
|
||||||
|
return stdnse.format_output(true, response.rawheaders)
|
||||||
|
end
|
||||||
80
scripts/ajp-methods.nse
Normal file
80
scripts/ajp-methods.nse
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
description = [[
|
||||||
|
Finds out what options are supported by the AJP server by sending an OPTIONS
|
||||||
|
request and lists potentially risky methods.
|
||||||
|
|
||||||
|
In this script, "potentially risky" methods are anything except GET,
|
||||||
|
HEAD, POST, and OPTIONS. If the script reports potentially risky
|
||||||
|
methods, they may not all be security risks, but you should check to
|
||||||
|
make sure. This page lists the dangers of some common methods:
|
||||||
|
|
||||||
|
http://www.owasp.org/index.php/Testing_for_HTTP_Methods_and_XST_%28OWASP-CM-008%29
|
||||||
|
]]
|
||||||
|
|
||||||
|
---
|
||||||
|
-- @usage
|
||||||
|
-- nmap -p 8009 <ip> --script ajp-methods
|
||||||
|
--
|
||||||
|
-- @output
|
||||||
|
-- PORT STATE SERVICE
|
||||||
|
-- 8009/tcp open ajp13
|
||||||
|
-- | ajp-methods:
|
||||||
|
-- | Supported methods: GET HEAD POST PUT DELETE TRACE OPTIONS
|
||||||
|
-- | Potentially risky methods: PUT DELETE TRACE
|
||||||
|
-- |_ See http://nmap.org/nsedoc/scripts/ajp-methods.html
|
||||||
|
--
|
||||||
|
-- @args ajp-methods.path the path to check or <code>/<code> if none was given
|
||||||
|
--
|
||||||
|
|
||||||
|
author = "Patrik Karlsson"
|
||||||
|
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
||||||
|
categories = {"default", "safe"}
|
||||||
|
|
||||||
|
local ajp = require('ajp')
|
||||||
|
local shortport = require('shortport')
|
||||||
|
|
||||||
|
portrule = shortport.port_or_service(8009, 'ajp13', 'tcp')
|
||||||
|
|
||||||
|
local arg_url = stdnse.get_script_args(SCRIPT_NAME .. ".path") or "/"
|
||||||
|
local UNINTERESTING_METHODS = { "GET", "HEAD", "POST", "OPTIONS" }
|
||||||
|
|
||||||
|
local function contains(t, elem)
|
||||||
|
for _, e in ipairs(t) do if e == elem then return true end end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
local function filter_out(t, filter)
|
||||||
|
local result = {}
|
||||||
|
for _, e in ipairs(t) do
|
||||||
|
if ( not(contains(filter, e)) ) then
|
||||||
|
result[#result + 1] = e
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
action = function(host, port)
|
||||||
|
|
||||||
|
local helper = ajp.Helper:new(host, port)
|
||||||
|
if ( not(helper:connect()) ) then
|
||||||
|
return fail("Failed to connect to server")
|
||||||
|
end
|
||||||
|
|
||||||
|
local status, response = helper:options(arg_url)
|
||||||
|
helper:close()
|
||||||
|
if ( not(status) or response.status ~= 200 or
|
||||||
|
not(response.headers) or not(response.headers['allow']) ) then
|
||||||
|
return "Failed to get a valid response for the OPTION request"
|
||||||
|
end
|
||||||
|
|
||||||
|
local methods = stdnse.strsplit(",%s", response.headers['allow'])
|
||||||
|
|
||||||
|
local output = {}
|
||||||
|
table.insert(output, ("Supported methods: %s"):format(stdnse.strjoin(" ", methods)))
|
||||||
|
|
||||||
|
local interesting = filter_out(methods, UNINTERESTING_METHODS)
|
||||||
|
if ( #interesting > 0 ) then
|
||||||
|
table.insert(output, "Potentially risky methods: " .. stdnse.strjoin(" ", interesting))
|
||||||
|
table.insert(output, "See http://nmap.org/nsedoc/scripts/ajp-methods.html")
|
||||||
|
end
|
||||||
|
return stdnse.format_output(true, output)
|
||||||
|
end
|
||||||
@@ -5,6 +5,9 @@ Entry { filename = "afp-ls.nse", categories = { "discovery", "safe", } }
|
|||||||
Entry { filename = "afp-path-vuln.nse", categories = { "exploit", "intrusive", "vuln", } }
|
Entry { filename = "afp-path-vuln.nse", categories = { "exploit", "intrusive", "vuln", } }
|
||||||
Entry { filename = "afp-serverinfo.nse", categories = { "default", "discovery", "safe", } }
|
Entry { filename = "afp-serverinfo.nse", categories = { "default", "discovery", "safe", } }
|
||||||
Entry { filename = "afp-showmount.nse", categories = { "discovery", "safe", } }
|
Entry { filename = "afp-showmount.nse", categories = { "discovery", "safe", } }
|
||||||
|
Entry { filename = "ajp-auth.nse", categories = { "auth", "default", "safe", } }
|
||||||
|
Entry { filename = "ajp-headers.nse", categories = { "discovery", "safe", } }
|
||||||
|
Entry { filename = "ajp-methods.nse", categories = { "default", "safe", } }
|
||||||
Entry { filename = "amqp-info.nse", categories = { "default", "discovery", "safe", "version", } }
|
Entry { filename = "amqp-info.nse", categories = { "default", "discovery", "safe", "version", } }
|
||||||
Entry { filename = "asn-query.nse", categories = { "discovery", "external", "safe", } }
|
Entry { filename = "asn-query.nse", categories = { "discovery", "external", "safe", } }
|
||||||
Entry { filename = "auth-owners.nse", categories = { "default", "safe", } }
|
Entry { filename = "auth-owners.nse", categories = { "default", "safe", } }
|
||||||
|
|||||||
Reference in New Issue
Block a user