107 lines
3.2 KiB
Lua
107 lines
3.2 KiB
Lua
local comm = require "comm"
|
|
local string = require "string"
|
|
local table = require "table"
|
|
local shortport = require "shortport"
|
|
local nmap = require "nmap"
|
|
local stdnse = require "stdnse"
|
|
local U = require "lpeg-utility"
|
|
|
|
description = [[
|
|
Uses the HTTP Server header for missing version info. This is currently
|
|
infeasible with version probes because of the need to match non-HTTP services
|
|
correctly.
|
|
]]
|
|
|
|
---
|
|
--@output
|
|
-- PORT STATE SERVICE VERSION
|
|
-- 80/tcp open http Unidentified Server 1.0
|
|
--
|
|
-- PORT STATE SERVICE VERSION
|
|
-- 80/tcp open http Unidentified Server 1.0
|
|
-- |_ http-server-header: Unidentified Server 1.0
|
|
--
|
|
--@xmloutput
|
|
--<table key="Server">
|
|
-- <elem>Unidentified Server 1.0</elem>
|
|
-- <elem>SomeOther Server</elem>
|
|
--</table>
|
|
|
|
author = "Daniel Miller"
|
|
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
|
|
categories = {"version"}
|
|
dependencies = {"https-redirect"}
|
|
|
|
portrule = function(host, port)
|
|
return (shortport.http(host,port) and nmap.version_intensity() >= 7)
|
|
end
|
|
|
|
action = function(host, port)
|
|
local responses = {}
|
|
-- Did the service engine already do the hard work?
|
|
if port.version and port.version.service_fp then
|
|
-- Probes sent, replies received, but no match.
|
|
-- Loop through the probes most likely to receive HTTP responses
|
|
for _, p in ipairs({"GetRequest", "GenericLines", "HTTPOptions",
|
|
"FourOhFourRequest", "NULL", "RTSPRequest", "Help", "SIPOptions"}) do
|
|
responses[#responses+1] = U.get_response(port.version.service_fp, p)
|
|
end
|
|
end
|
|
if #responses == 0 then
|
|
-- Have to send the probe ourselves.
|
|
local socket, result = comm.tryssl(host, port, "GET / HTTP/1.0\r\n\r\n")
|
|
|
|
if (not socket) then
|
|
return nil
|
|
end
|
|
socket:close()
|
|
responses[1] = result
|
|
end
|
|
|
|
-- Also send a probe with host header if we can. IIS reported to send
|
|
-- different Server headers depending on presence of Host header.
|
|
local socket, result = comm.tryssl(host, port,
|
|
("GET / HTTP/1.1\r\nHost: %s\r\n\r\n"):format(stdnse.get_hostname(host)))
|
|
if socket then
|
|
socket:close()
|
|
responses[#responses+1] = result
|
|
end
|
|
|
|
port.version = port.version or {}
|
|
|
|
local headers = {}
|
|
for _, result in ipairs(responses) do
|
|
if string.match(result, "^HTTP/1.[01] %d%d%d") then
|
|
|
|
local http_server = string.match(result, "\n[Ss][Ee][Rr][Vv][Ee][Rr]:[ \t]*(.-)\r?\n")
|
|
|
|
-- Avoid setting version info if -sV scan already got a match
|
|
if port.version.product == nil and (port.version.name_confidence or 0) <= 3 then
|
|
port.version.service = "http"
|
|
port.version.product = http_server
|
|
-- Setting "softmatched" allows the service fingerprint to be printed
|
|
nmap.set_port_version(host, port, "softmatched")
|
|
elseif port.version.product == http_server then
|
|
-- If we already detected exactly this, no need to report it
|
|
http_server = nil
|
|
end
|
|
|
|
if http_server then
|
|
headers[http_server] = true
|
|
end
|
|
end
|
|
end
|
|
|
|
local out = {}
|
|
local out_s = {}
|
|
for s, _ in pairs(headers) do
|
|
out[#out+1] = s
|
|
out_s[#out_s+1] = s == "" and "<empty>" or s
|
|
end
|
|
if next(out) then
|
|
table.sort(out)
|
|
table.sort(out_s)
|
|
return out, ((#out > 1) and "\n " or "") .. table.concat(out_s, "\n ")
|
|
end
|
|
end
|