mirror of
https://github.com/nmap/nmap.git
synced 2025-12-06 04:31:29 +00:00
Compare commits
23 Commits
f2548e68a8
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf24dab1c9 | ||
|
|
df4896eadb | ||
|
|
472b586767 | ||
|
|
306263da43 | ||
|
|
c0a01aa7e1 | ||
|
|
46fe5228f4 | ||
|
|
6dc02c9bcd | ||
|
|
3d205335b9 | ||
|
|
138c7b7467 | ||
|
|
5daccaed1d | ||
|
|
790deb7daf | ||
|
|
a04fc3389e | ||
|
|
a74125aef5 | ||
|
|
f5a3251e97 | ||
|
|
8d7fa538e3 | ||
|
|
8d06576dbb | ||
|
|
d2d591ce0c | ||
|
|
4f7c92fbac | ||
|
|
d2fbcc6cd6 | ||
|
|
b4b921c913 | ||
|
|
81b0568452 | ||
|
|
7a989ff957 | ||
|
|
9289bbccee |
11
CHANGELOG
11
CHANGELOG
@@ -1,5 +1,16 @@
|
||||
#Nmap Changelog ($Id$); -*-text-*-
|
||||
|
||||
o [GH#3191][GH#3218] Script http-internal-ip-disclosure has been enhanced,
|
||||
including added support for IPv6 and HTTPS and more accurate processing
|
||||
of target responses. [nnposter]
|
||||
|
||||
o [GH#3194] RPC-based scripts were sporadically failing due to privileged
|
||||
port conflicts. [nnposter]
|
||||
|
||||
o [GH#3196] Script rlogin-brute was sporadically failing due to using
|
||||
an off-by-one range for privileged ports and not handling potential
|
||||
port conflicts. [nnposter]
|
||||
|
||||
Nmap 7.98 [2025-08-21]
|
||||
|
||||
o [SECURITY] Rebuilt the Windows self-installer with NSIS 3.11, addressing
|
||||
|
||||
@@ -2480,9 +2480,20 @@ escapes a quote. A backslash is only used to escape quotation marks in this
|
||||
special case; in all other cases a backslash is interpreted literally. Values
|
||||
may also be tables enclosed in <literal>{}</literal>, just as in Lua. A table
|
||||
may contain simple string values or more name-value pairs, including nested
|
||||
tables. Many scripts qualify their arguments with the script name, as in <literal>xmpp-info.server_name</literal>. You may use that full qualified version to affect just the specified script, or you may pass the unqualified version (<literal>server_name</literal> in this case) to affect all scripts using that argument name. A script will first check for its fully qualified argument name (the name specified in its documentation) before it accepts an unqualified argument name. A complex example of script arguments is
|
||||
<option>--script-args 'user=foo,pass=",{}=bar",whois={whodb=nofollow+ripe},xmpp-info.server_name=localhost'</option>. The online NSE Documentation Portal at <ulink url="https://nmap.org/nsedoc/"/>
|
||||
lists the arguments that each script accepts.
|
||||
tables. A complex example of script arguments is
|
||||
<option>--script-args 'user=foo,pass=",{}=bar",whois={whodb=nofollow+ripe},xmpp-info.server_name=localhost'</option>.
|
||||
Many scripts qualify their arguments with the script name, as in
|
||||
<literal>xmpp-info.server_name</literal>. A script will first check for its
|
||||
fully qualified argument name (the name specified in its documentation) before
|
||||
it accepts an unqualified argument name (<literal>server_name</literal> in this
|
||||
case). Some arguments are not specific to one script. They typically effect
|
||||
behavior of a library and therefore potentially all the scripts that use the
|
||||
library. (One such example is <literal>http.useragent</literal>, which sets
|
||||
the default HTTP User-Agent header for every web request, regardless which
|
||||
script sends it.) It is not possible for the exact same argument to be given
|
||||
different values for diferent scripts. The online NSE Documentation Portal at
|
||||
<ulink url="https://nmap.org/nsedoc/"/> lists the arguments that each script
|
||||
accepts.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
@@ -276,7 +276,6 @@ local function get_quoted_string(s, offset, crlf)
|
||||
-- continuation." So there are really two definitions of quoted-string,
|
||||
-- depending on whether it's in a header field or not. This function does
|
||||
-- not allow CRLF.
|
||||
c = s:sub(i, i)
|
||||
if c ~= "\t" and c:match("^[\0\001-\031\127]$") then
|
||||
error(string.format("Unexpected control character in quoted-string: 0x%02X.", c:byte(1)))
|
||||
end
|
||||
@@ -292,10 +291,9 @@ local function skip_lws(s, pos)
|
||||
local _, e
|
||||
|
||||
while true do
|
||||
while string.match(s, "^[ \t]", pos) do
|
||||
pos = pos + 1
|
||||
end
|
||||
_, e = string.find(s, "^\r?\n[ \t]", pos)
|
||||
_, pos = string.find(s, "^[ \t]*", pos)
|
||||
pos = pos + 1
|
||||
_, e = string.find(s, "^\r?\n[ \t]+", pos)
|
||||
if not e then
|
||||
return pos
|
||||
end
|
||||
@@ -360,7 +358,19 @@ local function validate_options(options)
|
||||
stdnse.debug1("http: options.cookies[i].max-age should be a string")
|
||||
bad = true
|
||||
end
|
||||
elseif not (cookie_key == 'httponly' or cookie_key == 'secure') then
|
||||
elseif(cookie_key == 'domain') then
|
||||
if(type(cookie_value) ~= 'string') then
|
||||
stdnse.debug1("http: options.cookies[i].domain should be a string")
|
||||
bad = true
|
||||
end
|
||||
elseif(cookie_key == 'samesite') then
|
||||
if(type(cookie_value) ~= 'string') then
|
||||
stdnse.debug1("http: options.cookies[i].samesite should be a string")
|
||||
bad = true
|
||||
end
|
||||
elseif not (cookie_key == 'httponly'
|
||||
or cookie_key == 'secure'
|
||||
or cookie_key == 'partitioned') then
|
||||
stdnse.debug1("http: Unknown field in cookie table: %s", cookie_key)
|
||||
-- Ignore unrecognized attributes (per RFC 6265, Section 5.2)
|
||||
end
|
||||
@@ -2024,27 +2034,24 @@ function pipeline_go(host, port, all_requests)
|
||||
stdnse.debug3("HTTP pipeline: connlimit=%d, batchlimit=%d", connlimit, batchlimit)
|
||||
|
||||
while #responses < #all_requests do
|
||||
local status, err
|
||||
-- reconnect if necessary
|
||||
if connsent >= connlimit or resp.truncated or not socket:get_info() then
|
||||
socket:close()
|
||||
stdnse.debug3("HTTP pipeline: reconnecting")
|
||||
socket:set_timeout(pipeline_comm_opts.request_timeout)
|
||||
socket:connect(host, port, bopt)
|
||||
if not socket then
|
||||
return nil
|
||||
status, err = socket:connect(host, port, bopt)
|
||||
if not status then
|
||||
stdnse.debug3("HTTP pipeline: cannot reconnect: %s", err)
|
||||
return responses
|
||||
end
|
||||
partial = ""
|
||||
connsent = 0
|
||||
end
|
||||
if connlimit > connsent + #all_requests - #responses then
|
||||
connlimit = connsent + #all_requests - #responses
|
||||
end
|
||||
|
||||
-- decrease the connection limit to match what we still need to send
|
||||
connlimit = math.min(connlimit, connsent + #all_requests - #responses)
|
||||
-- determine the current batch size
|
||||
local batchsize = connlimit - connsent
|
||||
if batchsize > batchlimit then
|
||||
batchsize = batchlimit
|
||||
end
|
||||
local batchsize = math.min(connlimit - connsent, batchlimit)
|
||||
stdnse.debug3("HTTP pipeline: batch=%d, conn=%d/%d, resp=%d/%d", batchsize, connsent, connlimit, #responses, #all_requests)
|
||||
|
||||
-- build and send a batch of requests
|
||||
@@ -2055,7 +2062,11 @@ function pipeline_go(host, port, all_requests)
|
||||
req.options.header = force_header(req.options.header, "Connection", connmode)
|
||||
table.insert(requests, build_request(host, port, req.method, req.path, req.options))
|
||||
end
|
||||
socket:send(table.concat(requests))
|
||||
status, err = socket:send(table.concat(requests))
|
||||
if not status then
|
||||
stdnse.debug3("HTTP pipeline: cannot send: %s", err)
|
||||
return responses
|
||||
end
|
||||
|
||||
-- receive batch responses
|
||||
for i = 1, batchsize do
|
||||
@@ -2082,19 +2093,9 @@ function pipeline_go(host, port, all_requests)
|
||||
return responses
|
||||
end
|
||||
|
||||
-- Parsing of specific headers. skip_space and the read_* functions return the
|
||||
-- Parsing of specific headers. 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 function skip_space(s, pos)
|
||||
local _
|
||||
|
||||
_, pos = string.find(s, "^[ \t]*", pos)
|
||||
|
||||
return pos + 1
|
||||
end
|
||||
|
||||
-- See RFC 2616, section 2.2.
|
||||
local function read_token(s, pos)
|
||||
local _, token
|
||||
|
||||
@@ -216,9 +216,9 @@ end
|
||||
-- @param left String representing an IPv4 or IPv6 address. Shortened
|
||||
-- notation is permitted.
|
||||
-- @param op A comparison operator which may be one of the following strings:
|
||||
-- <code>"eq"</code>, <code>"ge"</code>, <code>"le"</code>,
|
||||
-- <code>"gt"</code> or <code>"lt"</code> (respectively ==, >=, <=,
|
||||
-- >, <).
|
||||
-- <code>"eq"</code>, <code>"ne"</code>, <code>"ge"</code>,
|
||||
-- <code>"le"</code>, <code>"gt"</code> or <code>"lt"</code>
|
||||
-- (respectively ==, ~=, >=, <=, >, <).
|
||||
-- @param right String representing an IPv4 or IPv6 address. Shortened
|
||||
-- notation is permitted.
|
||||
-- @usage
|
||||
@@ -381,6 +381,9 @@ expand_ip = function( ip, family )
|
||||
for hdt in string.gmatch( ip, "[%.z%x]+" ) do
|
||||
hexadectets[#hexadectets+1] = hdt
|
||||
end
|
||||
if #hexadectets == 0 then
|
||||
return nil, ( err4:gsub( "IPv4", "IPv6" ) )
|
||||
end
|
||||
|
||||
-- deal with IPv4in6 (last hexadectet only)
|
||||
local t = {}
|
||||
|
||||
@@ -83,35 +83,30 @@ IPP = {
|
||||
attrib.value = {}
|
||||
table.insert(attrib.value, { tag = attrib.tag, val = val })
|
||||
|
||||
repeat
|
||||
while pos + 3 < #data do
|
||||
local tag, name_len, val
|
||||
|
||||
if ( #data < pos + 3 ) then
|
||||
tag, name_len, pos = string.unpack(">BI2", data, pos)
|
||||
if name_len > 0 then
|
||||
-- done; start of a new attribute
|
||||
pos = pos - 3
|
||||
break
|
||||
end
|
||||
|
||||
tag, name_len, pos = string.unpack(">BI2", data, pos)
|
||||
if ( name_len == 0 ) then
|
||||
val, pos = string.unpack(">s2", data, pos)
|
||||
table.insert(attrib.value, { tag = tag, val = val })
|
||||
else
|
||||
pos = pos - 3
|
||||
end
|
||||
until( name_len ~= 0 )
|
||||
val, pos = string.unpack(">s2", data, pos)
|
||||
table.insert(attrib.value, { tag = tag, val = val })
|
||||
end
|
||||
|
||||
-- do minimal decoding
|
||||
for i=1, #attrib.value do
|
||||
if ( attrib.value[i].tag == IPP.Attribute.IPP_TAG_INTEGER ) then
|
||||
attrib.value[i].val = string.unpack(">I4", attrib.value[i].val)
|
||||
elseif ( attrib.value[i].tag == IPP.Attribute.IPP_TAG_ENUM ) then
|
||||
attrib.value[i].val = string.unpack(">I4", attrib.value[i].val)
|
||||
for _, av in ipairs(attrib.value) do
|
||||
if av.tag == IPP.Attribute.IPP_TAG_INTEGER then
|
||||
av.val = string.unpack(">I4", av.val)
|
||||
elseif av.tag == IPP.Attribute.IPP_TAG_ENUM then
|
||||
av.val = string.unpack(">I4", av.val)
|
||||
end
|
||||
end
|
||||
|
||||
if ( 1 == #attrib.value ) then
|
||||
attrib.value = attrib.value[1].val
|
||||
end
|
||||
--print(attrib.name, attrib.value, stdnse.tohex(val))
|
||||
|
||||
return pos, attrib
|
||||
end,
|
||||
@@ -152,26 +147,14 @@ IPP = {
|
||||
-- @param tag number containing the attribute tag
|
||||
getAttribute = function(self, name, tag)
|
||||
for _, attrib in ipairs(self.attribs) do
|
||||
if ( attrib.name == name ) then
|
||||
if ( not(tag) ) then
|
||||
return attrib
|
||||
elseif ( tag and attrib.tag == tag ) then
|
||||
return attrib
|
||||
end
|
||||
if attrib.name == name and (not tag or attrib.tag == tag) then
|
||||
return attrib
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
getAttributeValue = function(self, name, tag)
|
||||
for _, attrib in ipairs(self.attribs) do
|
||||
if ( attrib.name == name ) then
|
||||
if ( not(tag) ) then
|
||||
return attrib.value
|
||||
elseif ( tag and attrib.tag == tag ) then
|
||||
return attrib.value
|
||||
end
|
||||
end
|
||||
end
|
||||
return (self:getAttribute(name, tag) or {}).value
|
||||
end,
|
||||
|
||||
__tostring = function(self)
|
||||
@@ -332,7 +315,7 @@ Helper = {
|
||||
request:addAttributeGroup(ag)
|
||||
|
||||
local status, response = HTTP.Request( self.host, self.port, tostring(request) )
|
||||
if ( not(response) ) then
|
||||
if not status then
|
||||
return status, response
|
||||
end
|
||||
|
||||
@@ -350,9 +333,7 @@ Helper = {
|
||||
|
||||
local printer = {}
|
||||
for k, v in pairs(attrib) do
|
||||
if ( ag:getAttributeValue(k) ) then
|
||||
printer[v] = ag:getAttributeValue(k)
|
||||
end
|
||||
printer[v] = ag:getAttributeValue(k)
|
||||
end
|
||||
table.insert(printers, printer)
|
||||
end
|
||||
@@ -360,7 +341,7 @@ Helper = {
|
||||
end,
|
||||
|
||||
getQueueInfo = function(self, uri)
|
||||
local uri = uri or ("ipp://%s/"):format(self.host.ip)
|
||||
uri = uri or ("ipp://%s/"):format(self.host.ip)
|
||||
|
||||
local attribs = {
|
||||
IPP.Attribute:new(IPP.Attribute.IPP_TAG_CHARSET, "attributes-charset", "utf-8" ),
|
||||
@@ -375,7 +356,7 @@ Helper = {
|
||||
{ tag = IPP.Attribute.IPP_TAG_KEYWORD, val = "job-name" },
|
||||
{ tag = IPP.Attribute.IPP_TAG_KEYWORD, val = "job-state" },
|
||||
{ tag = IPP.Attribute.IPP_TAG_KEYWORD, val = "printer-uri" },
|
||||
-- { tag = IPP.Attribute.IPP_TAG_KEYWORD, val = "job-originating-user-name" },
|
||||
{ tag = IPP.Attribute.IPP_TAG_KEYWORD, val = "job-originating-user-name" },
|
||||
-- { tag = IPP.Attribute.IPP_TAG_KEYWORD, val = "job-printer-state-message" },
|
||||
-- { tag = IPP.Attribute.IPP_TAG_KEYWORD, val = "job-printer-uri" },
|
||||
{ tag = IPP.Attribute.IPP_TAG_KEYWORD, val = "time-at-creation" } } ),
|
||||
@@ -387,23 +368,25 @@ Helper = {
|
||||
request:addAttributeGroup(ag)
|
||||
|
||||
local status, response = HTTP.Request( self.host, self.port, tostring(request) )
|
||||
if ( not(response) ) then
|
||||
if not status then
|
||||
return status, response
|
||||
end
|
||||
|
||||
local results = {}
|
||||
for _, ag in ipairs(response:getAttributeGroups(IPP.Attribute.IPP_TAG_JOB)) do
|
||||
local uri = ag:getAttributeValue("printer-uri")
|
||||
local printer = uri:match(".*/(.*)$") or "Unknown"
|
||||
local printer = ag:getAttributeValue("printer-uri"):match(".*/(.*)$") or "Unknown"
|
||||
-- some jobs have multiple state attributes, so far the ENUM ones have been correct
|
||||
local state = ag:getAttributeValue("job-state", IPP.Attribute.IPP_TAG_ENUM) or ag:getAttributeValue("job-state")
|
||||
-- some jobs have multiple id tag, so far the INTEGER type have shown the correct ID
|
||||
local id = ag:getAttributeValue("job-id", IPP.Attribute.IPP_TAG_INTEGER) or ag:getAttributeValue("job-id")
|
||||
local attr = ag:getAttribute("time-at-creation")
|
||||
local tm = ag:getAttributeValue("time-at-creation")
|
||||
local size = ag:getAttributeValue("job-k-octets") .. "k"
|
||||
local jobname = ag:getAttributeValue("com.apple.print.JobInfo.PMJobName") or "Unknown"
|
||||
local owner = ag:getAttributeValue("com.apple.print.JobInfo.PMJobOwner") or "Unknown"
|
||||
local jobname = ag:getAttributeValue("com.apple.print.JobInfo.PMJobName")
|
||||
or ag:getAttributeValue("job-name")
|
||||
or "Unknown"
|
||||
local owner = ag:getAttributeValue("com.apple.print.JobInfo.PMJobOwner")
|
||||
or ag:getAttributeValue("job-originating-user-name")
|
||||
or "Unknown"
|
||||
|
||||
results[printer] = results[printer] or {}
|
||||
table.insert(results[printer], {
|
||||
|
||||
@@ -154,67 +154,54 @@ Comm = {
|
||||
-- @return status boolean true on success, false on failure
|
||||
-- @return string containing error message (if status is false)
|
||||
Connect = function(self, host, port, timeout)
|
||||
local status, err, socket
|
||||
status, err = self:ChkProgram()
|
||||
if (not(status)) then
|
||||
timeout = timeout or stdnse.get_timeout(host, 10000)
|
||||
local status, err = self:ChkProgram()
|
||||
if not status then
|
||||
return status, err
|
||||
end
|
||||
status, err = self:ChkVersion()
|
||||
if (not(status)) then
|
||||
if not status then
|
||||
return status, err
|
||||
end
|
||||
timeout = timeout or stdnse.get_timeout(host, 10000)
|
||||
local new_socket = function(...)
|
||||
local socket = nmap.new_socket(...)
|
||||
socket:set_timeout(timeout)
|
||||
return socket
|
||||
end
|
||||
if ( port.protocol == "tcp" ) then
|
||||
if nmap.is_privileged() then
|
||||
-- Try to bind to a reserved port
|
||||
for i = 1, 10, 1 do
|
||||
local resvport = math.random(512, 1023)
|
||||
socket = new_socket()
|
||||
status, err = socket:bind(nil, resvport)
|
||||
local socket = nmap.new_socket(port.protocol)
|
||||
if nmap.is_privileged() then
|
||||
-- Let's make several attempts to bind to an unused well-known port
|
||||
for _ = 1, 10 do
|
||||
local srcport = math.random(512, 1023)
|
||||
status, err = socket:bind(nil, srcport)
|
||||
if status then
|
||||
socket:set_timeout(timeout)
|
||||
status, err = socket:connect(host, port)
|
||||
if status then
|
||||
status, err = socket:connect(host, port)
|
||||
if status or err == "TIMEOUT" then break end
|
||||
socket:close()
|
||||
-- socket:connect() succeeds even if mksock_bind_addr() fails.
|
||||
-- It just assigns an ephemeral port instead of our choice,
|
||||
-- so we need to check the actual source port afterwards.
|
||||
local lport
|
||||
status, err, lport = socket:get_info()
|
||||
if status then
|
||||
if lport == srcport then
|
||||
break
|
||||
end
|
||||
status = false
|
||||
err = "Address already in use"
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
socket = new_socket()
|
||||
status, err = socket:connect(host, port)
|
||||
socket:close()
|
||||
end
|
||||
else
|
||||
if nmap.is_privileged() then
|
||||
-- Try to bind to a reserved port
|
||||
for i = 1, 10, 1 do
|
||||
local resvport = math.random(512, 1023)
|
||||
socket = new_socket("udp")
|
||||
status, err = socket:bind(nil, resvport)
|
||||
if status then
|
||||
status, err = socket:connect(host, port)
|
||||
if status or err == "TIMEOUT" then break end
|
||||
socket:close()
|
||||
end
|
||||
end
|
||||
else
|
||||
socket = new_socket("udp")
|
||||
status, err = socket:connect(host, port)
|
||||
end
|
||||
-- No privileges to force a specific source port
|
||||
status, err = socket:connect(host, port)
|
||||
end
|
||||
if (not(status)) then
|
||||
return status, string.format("%s connect error: %s",
|
||||
self.program, err)
|
||||
else
|
||||
self.socket = socket
|
||||
self.host = host
|
||||
self.ip = host.ip
|
||||
self.port = port.number
|
||||
self.proto = port.protocol
|
||||
return status, nil
|
||||
if not status then
|
||||
return status, ("%s connect error: %s"):format(self.program, err)
|
||||
end
|
||||
self.socket = socket
|
||||
self.host = host
|
||||
self.ip = host.ip
|
||||
self.port = port.number
|
||||
self.proto = port.protocol
|
||||
return status, nil
|
||||
end,
|
||||
|
||||
--- Disconnects from the remote program
|
||||
|
||||
@@ -74,7 +74,7 @@ local ELEMENTS = {["Type"] = "Type",
|
||||
["string"] = "SOAPACTIONS",
|
||||
["SubDeviceURLs"] = "Sub Device URLs"}
|
||||
|
||||
function get_text_callback(store, name)
|
||||
local function get_text_callback(store, name)
|
||||
if ELEMENTS[name] == nil then return end
|
||||
name = ELEMENTS[name]
|
||||
if name == 'SOAPACTIONS' or name == 'Sub Device URLs' or name == 'Type' then
|
||||
|
||||
@@ -1,21 +1,30 @@
|
||||
local comm = require "comm"
|
||||
local ipOps = require "ipOps"
|
||||
local nmap = require "nmap"
|
||||
local shortport = require "shortport"
|
||||
local stdnse = require "stdnse"
|
||||
local ipOps = require "ipOps"
|
||||
local target = require "target"
|
||||
local url = require "url"
|
||||
|
||||
description = [[
|
||||
Determines if the web server leaks its internal IP address when sending an HTTP/1.0 request without a Host header.
|
||||
Determines if the web server leaks its internal IP address when sending
|
||||
an HTTP/1.0 request without a Host header.
|
||||
|
||||
Some misconfigured web servers leak their internal IP address in the response
|
||||
headers when returning a redirect response. This is a known issue for some
|
||||
versions of Microsoft IIS, but affects other web servers as well.
|
||||
|
||||
If script argument <code>newtargets</code> is set, the script will
|
||||
add the found IP address as a new target into the scan queue. (See
|
||||
the documentation for NSE library <code>target</code> for details.)
|
||||
]]
|
||||
|
||||
---
|
||||
-- @usage nmap --script http-internal-ip-disclosure <target>
|
||||
-- @usage nmap --script http-internal-ip-disclosure --script-args http-internal-ip-disclosure.path=/path <target>
|
||||
-- @usage nmap --script http-internal-ip-disclosure --script-args http-internal-ip-disclosure.path=/mypath <target>
|
||||
--
|
||||
-- @args http-internal-ip-disclosure.path Path to URI. Default: /
|
||||
-- @args http-internal-ip-disclosure.path Path (or a table of paths) to probe
|
||||
-- Default: /
|
||||
--
|
||||
-- @output
|
||||
-- 80/tcp open http syn-ack
|
||||
@@ -27,61 +36,59 @@ versions of Microsoft IIS, but affects other web servers as well.
|
||||
--
|
||||
-- @see ssl-cert-intaddr.nse
|
||||
|
||||
author = "Josh Amishav-Zlatin"
|
||||
author = {"Josh Amishav-Zlatin", "nnposter"}
|
||||
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
|
||||
categories = { "vuln", "discovery", "safe" }
|
||||
|
||||
portrule = shortport.http
|
||||
|
||||
local function generateHttpV1_0Req(host, port, path)
|
||||
local redirectIP, privateIP
|
||||
local socket = nmap.new_socket()
|
||||
socket:connect(host, port)
|
||||
|
||||
local cmd = "GET " .. path .. " HTTP/1.0\r\n\r\n"
|
||||
socket:send(cmd)
|
||||
|
||||
while true do
|
||||
local status, lines = socket:receive_lines(1)
|
||||
if not status then
|
||||
break
|
||||
end
|
||||
|
||||
-- Check if the response contains a location header
|
||||
if lines:match("Location") then
|
||||
local locTarget = lines:match("Location: [%a%p%d]+")
|
||||
-- Check if the redirect location contains an IP address
|
||||
redirectIP = locTarget:match("[%d%.]+")
|
||||
if redirectIP then
|
||||
privateIP = ipOps.isPrivate(redirectIP)
|
||||
end
|
||||
|
||||
stdnse.debug1("Location: %s", locTarget )
|
||||
stdnse.debug1("Internal IP: %s", redirectIP )
|
||||
end
|
||||
end
|
||||
|
||||
socket:close()
|
||||
|
||||
-- Only report if the internal IP leaked is different then the target IP
|
||||
if privateIP and redirectIP ~= host.ip then
|
||||
return redirectIP
|
||||
end
|
||||
end
|
||||
|
||||
action = function(host, port)
|
||||
local output = stdnse.output_table()
|
||||
local path = stdnse.get_script_args(SCRIPT_NAME .. ".path") or "/"
|
||||
local IP = generateHttpV1_0Req(host, port, path)
|
||||
|
||||
-- Check /images which is often vulnerable on some unpatched IIS servers
|
||||
if not IP and path ~= "/images" then
|
||||
path = "/images"
|
||||
IP = generateHttpV1_0Req(host, port, path)
|
||||
local patharg = stdnse.get_script_args(SCRIPT_NAME .. ".path") or "/"
|
||||
if type(patharg) ~= "table" then
|
||||
patharg = {patharg}
|
||||
end
|
||||
local paths = stdnse.output_table()
|
||||
for _, path in ipairs(patharg) do
|
||||
paths[path] = 1
|
||||
end
|
||||
paths["/images"] = 1
|
||||
|
||||
if IP then
|
||||
output["Internal IP Leaked"] = IP
|
||||
return output
|
||||
local socket
|
||||
local bopt = nil
|
||||
local try = nmap.new_try(function () socket:close() end)
|
||||
for path in pairs(paths) do
|
||||
local req = "GET " .. path .. " HTTP/1.0\r\n\r\n"
|
||||
local resp
|
||||
if not bopt then
|
||||
socket, resp, bopt = comm.tryssl(host, port, req)
|
||||
if not socket then return end
|
||||
else
|
||||
try(socket:connect(host, port, bopt))
|
||||
try(socket:send(req))
|
||||
resp = ""
|
||||
end
|
||||
local findhead = function (s)
|
||||
return s:find("\r?\n\r?\n")
|
||||
end
|
||||
if not findhead(resp) then
|
||||
resp = resp .. try(socket:receive_buf(findhead, true))
|
||||
end
|
||||
socket:close()
|
||||
|
||||
local loc = resp:lower():match("\nlocation:[ \t]+(%S+)")
|
||||
local lochost = url.parse(loc or "").host
|
||||
if lochost and lochost ~= "" then
|
||||
-- remove any IPv6 enclosure
|
||||
lochost = lochost:gsub("^%[(.*)%]$", "%1")
|
||||
|
||||
if ipOps.isPrivate(lochost) and ipOps.compare_ip(lochost, "ne", host.ip) then
|
||||
if target.ALLOW_NEW_TARGETS then
|
||||
target.add(lochost)
|
||||
end
|
||||
local output = stdnse.output_table()
|
||||
output["Internal IP Leaked"] = lochost
|
||||
return output
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -51,34 +51,36 @@ Driver = {
|
||||
end,
|
||||
|
||||
-- connects to the rlogin service
|
||||
-- it sets the source port to a random value between 513 and 1024
|
||||
-- it sets the source port to a random value between 512 and 1023
|
||||
connect = function(self)
|
||||
|
||||
local status
|
||||
|
||||
local status, err
|
||||
self.socket = brute.new_socket()
|
||||
-- apparently wee need a source port below 1024
|
||||
-- this approach is not very elegant as it causes address already in
|
||||
-- use errors when the same src port is hit in a short time frame.
|
||||
-- hopefully the retry count should take care of this as a retry
|
||||
-- should choose a new random port as source.
|
||||
local srcport = math.random(513, 1024)
|
||||
self.socket:bind(nil, srcport)
|
||||
self.socket:set_timeout(self.timeout)
|
||||
local err
|
||||
status, err = self.socket:connect(self.host, self.port)
|
||||
|
||||
if ( status ) then
|
||||
local lport, _
|
||||
status, _, lport = self.socket:get_info()
|
||||
if (not(status) ) then
|
||||
return false, "failed to retrieve socket status"
|
||||
-- Let's make several attempts to bind to an unused well-known port
|
||||
for _ = 1, 10 do
|
||||
local srcport = math.random(512, 1023)
|
||||
status, err = self.socket:bind(nil, srcport)
|
||||
if status then
|
||||
self.socket:set_timeout(self.timeout)
|
||||
status, err = self.socket:connect(self.host, self.port)
|
||||
if status then
|
||||
-- socket:connect() succeeds even if mksock_bind_addr() fails.
|
||||
-- It just assigns an ephemeral port instead of our choice,
|
||||
-- so we need to check the actual source port afterwards.
|
||||
local lport
|
||||
status, err, lport = self.socket:get_info()
|
||||
if status then
|
||||
if lport == srcport then
|
||||
return status
|
||||
end
|
||||
status = false
|
||||
err = "Address already in use"
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
self.socket:close()
|
||||
end
|
||||
if ( not(status) ) then
|
||||
stdnse.debug3("ERROR: failed to connect to server")
|
||||
if not status then
|
||||
stdnse.debug2("Unable to bind to a well-known port (%s)", err)
|
||||
end
|
||||
return status
|
||||
end,
|
||||
|
||||
@@ -6,6 +6,6 @@ Exec=su-to-zenmap.sh %F
|
||||
Terminal=false
|
||||
Icon=zenmap
|
||||
Type=Application
|
||||
Categories=Application;Network;Security;
|
||||
Categories=Network;Security;
|
||||
Comment=A cross-platform GUI for the Nmap Security Scanner.
|
||||
Keywords=network;scan;scanner;IP;security;
|
||||
|
||||
@@ -6,6 +6,6 @@ Exec=zenmap %F
|
||||
Terminal=false
|
||||
Icon=zenmap
|
||||
Type=Application
|
||||
Categories=Application;Network;Security;
|
||||
Categories=Network;Security;
|
||||
Comment=A cross-platform GUI for the Nmap Security Scanner.
|
||||
Keywords=network;scan;scanner;IP;security;
|
||||
|
||||
Reference in New Issue
Block a user