mirror of
https://github.com/nmap/nmap.git
synced 2025-12-09 22:21: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-*-
|
#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]
|
Nmap 7.98 [2025-08-21]
|
||||||
|
|
||||||
o [SECURITY] Rebuilt the Windows self-installer with NSIS 3.11, addressing
|
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
|
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 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
|
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
|
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>. The online NSE Documentation Portal at <ulink url="https://nmap.org/nsedoc/"/>
|
<option>--script-args 'user=foo,pass=",{}=bar",whois={whodb=nofollow+ripe},xmpp-info.server_name=localhost'</option>.
|
||||||
lists the arguments that each script accepts.
|
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>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|||||||
@@ -276,7 +276,6 @@ local function get_quoted_string(s, offset, crlf)
|
|||||||
-- continuation." So there are really two definitions of quoted-string,
|
-- continuation." So there are really two definitions of quoted-string,
|
||||||
-- depending on whether it's in a header field or not. This function does
|
-- depending on whether it's in a header field or not. This function does
|
||||||
-- not allow CRLF.
|
-- not allow CRLF.
|
||||||
c = s:sub(i, i)
|
|
||||||
if c ~= "\t" and c:match("^[\0\001-\031\127]$") then
|
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)))
|
error(string.format("Unexpected control character in quoted-string: 0x%02X.", c:byte(1)))
|
||||||
end
|
end
|
||||||
@@ -292,10 +291,9 @@ local function skip_lws(s, pos)
|
|||||||
local _, e
|
local _, e
|
||||||
|
|
||||||
while true do
|
while true do
|
||||||
while string.match(s, "^[ \t]", pos) do
|
_, pos = string.find(s, "^[ \t]*", pos)
|
||||||
pos = pos + 1
|
pos = pos + 1
|
||||||
end
|
_, e = string.find(s, "^\r?\n[ \t]+", pos)
|
||||||
_, e = string.find(s, "^\r?\n[ \t]", pos)
|
|
||||||
if not e then
|
if not e then
|
||||||
return pos
|
return pos
|
||||||
end
|
end
|
||||||
@@ -360,7 +358,19 @@ local function validate_options(options)
|
|||||||
stdnse.debug1("http: options.cookies[i].max-age should be a string")
|
stdnse.debug1("http: options.cookies[i].max-age should be a string")
|
||||||
bad = true
|
bad = true
|
||||||
end
|
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)
|
stdnse.debug1("http: Unknown field in cookie table: %s", cookie_key)
|
||||||
-- Ignore unrecognized attributes (per RFC 6265, Section 5.2)
|
-- Ignore unrecognized attributes (per RFC 6265, Section 5.2)
|
||||||
end
|
end
|
||||||
@@ -2024,27 +2034,24 @@ function pipeline_go(host, port, all_requests)
|
|||||||
stdnse.debug3("HTTP pipeline: connlimit=%d, batchlimit=%d", connlimit, batchlimit)
|
stdnse.debug3("HTTP pipeline: connlimit=%d, batchlimit=%d", connlimit, batchlimit)
|
||||||
|
|
||||||
while #responses < #all_requests do
|
while #responses < #all_requests do
|
||||||
|
local status, err
|
||||||
-- reconnect if necessary
|
-- reconnect if necessary
|
||||||
if connsent >= connlimit or resp.truncated or not socket:get_info() then
|
if connsent >= connlimit or resp.truncated or not socket:get_info() then
|
||||||
socket:close()
|
socket:close()
|
||||||
stdnse.debug3("HTTP pipeline: reconnecting")
|
stdnse.debug3("HTTP pipeline: reconnecting")
|
||||||
socket:set_timeout(pipeline_comm_opts.request_timeout)
|
socket:set_timeout(pipeline_comm_opts.request_timeout)
|
||||||
socket:connect(host, port, bopt)
|
status, err = socket:connect(host, port, bopt)
|
||||||
if not socket then
|
if not status then
|
||||||
return nil
|
stdnse.debug3("HTTP pipeline: cannot reconnect: %s", err)
|
||||||
|
return responses
|
||||||
end
|
end
|
||||||
partial = ""
|
partial = ""
|
||||||
connsent = 0
|
connsent = 0
|
||||||
end
|
end
|
||||||
if connlimit > connsent + #all_requests - #responses then
|
-- decrease the connection limit to match what we still need to send
|
||||||
connlimit = connsent + #all_requests - #responses
|
connlimit = math.min(connlimit, connsent + #all_requests - #responses)
|
||||||
end
|
|
||||||
|
|
||||||
-- determine the current batch size
|
-- determine the current batch size
|
||||||
local batchsize = connlimit - connsent
|
local batchsize = math.min(connlimit - connsent, batchlimit)
|
||||||
if batchsize > batchlimit then
|
|
||||||
batchsize = batchlimit
|
|
||||||
end
|
|
||||||
stdnse.debug3("HTTP pipeline: batch=%d, conn=%d/%d, resp=%d/%d", batchsize, connsent, connlimit, #responses, #all_requests)
|
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
|
-- 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)
|
req.options.header = force_header(req.options.header, "Connection", connmode)
|
||||||
table.insert(requests, build_request(host, port, req.method, req.path, req.options))
|
table.insert(requests, build_request(host, port, req.method, req.path, req.options))
|
||||||
end
|
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
|
-- receive batch responses
|
||||||
for i = 1, batchsize do
|
for i = 1, batchsize do
|
||||||
@@ -2082,19 +2093,9 @@ function pipeline_go(host, port, all_requests)
|
|||||||
return responses
|
return responses
|
||||||
end
|
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.
|
-- 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.
|
-- See RFC 2616, section 2.2.
|
||||||
local function read_token(s, pos)
|
local function read_token(s, pos)
|
||||||
local _, token
|
local _, token
|
||||||
|
|||||||
@@ -216,9 +216,9 @@ end
|
|||||||
-- @param left String representing an IPv4 or IPv6 address. Shortened
|
-- @param left String representing an IPv4 or IPv6 address. Shortened
|
||||||
-- notation is permitted.
|
-- notation is permitted.
|
||||||
-- @param op A comparison operator which may be one of the following strings:
|
-- @param op A comparison operator which may be one of the following strings:
|
||||||
-- <code>"eq"</code>, <code>"ge"</code>, <code>"le"</code>,
|
-- <code>"eq"</code>, <code>"ne"</code>, <code>"ge"</code>,
|
||||||
-- <code>"gt"</code> or <code>"lt"</code> (respectively ==, >=, <=,
|
-- <code>"le"</code>, <code>"gt"</code> or <code>"lt"</code>
|
||||||
-- >, <).
|
-- (respectively ==, ~=, >=, <=, >, <).
|
||||||
-- @param right String representing an IPv4 or IPv6 address. Shortened
|
-- @param right String representing an IPv4 or IPv6 address. Shortened
|
||||||
-- notation is permitted.
|
-- notation is permitted.
|
||||||
-- @usage
|
-- @usage
|
||||||
@@ -381,6 +381,9 @@ expand_ip = function( ip, family )
|
|||||||
for hdt in string.gmatch( ip, "[%.z%x]+" ) do
|
for hdt in string.gmatch( ip, "[%.z%x]+" ) do
|
||||||
hexadectets[#hexadectets+1] = hdt
|
hexadectets[#hexadectets+1] = hdt
|
||||||
end
|
end
|
||||||
|
if #hexadectets == 0 then
|
||||||
|
return nil, ( err4:gsub( "IPv4", "IPv6" ) )
|
||||||
|
end
|
||||||
|
|
||||||
-- deal with IPv4in6 (last hexadectet only)
|
-- deal with IPv4in6 (last hexadectet only)
|
||||||
local t = {}
|
local t = {}
|
||||||
|
|||||||
@@ -83,35 +83,30 @@ IPP = {
|
|||||||
attrib.value = {}
|
attrib.value = {}
|
||||||
table.insert(attrib.value, { tag = attrib.tag, val = val })
|
table.insert(attrib.value, { tag = attrib.tag, val = val })
|
||||||
|
|
||||||
repeat
|
while pos + 3 < #data do
|
||||||
local tag, name_len, val
|
local tag, name_len, val
|
||||||
|
tag, name_len, pos = string.unpack(">BI2", data, pos)
|
||||||
if ( #data < pos + 3 ) then
|
if name_len > 0 then
|
||||||
|
-- done; start of a new attribute
|
||||||
|
pos = pos - 3
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
|
|
||||||
tag, name_len, pos = string.unpack(">BI2", data, pos)
|
|
||||||
if ( name_len == 0 ) then
|
|
||||||
val, pos = string.unpack(">s2", data, pos)
|
val, pos = string.unpack(">s2", data, pos)
|
||||||
table.insert(attrib.value, { tag = tag, val = val })
|
table.insert(attrib.value, { tag = tag, val = val })
|
||||||
else
|
|
||||||
pos = pos - 3
|
|
||||||
end
|
end
|
||||||
until( name_len ~= 0 )
|
|
||||||
|
|
||||||
-- do minimal decoding
|
-- do minimal decoding
|
||||||
for i=1, #attrib.value do
|
for _, av in ipairs(attrib.value) do
|
||||||
if ( attrib.value[i].tag == IPP.Attribute.IPP_TAG_INTEGER ) then
|
if av.tag == IPP.Attribute.IPP_TAG_INTEGER then
|
||||||
attrib.value[i].val = string.unpack(">I4", attrib.value[i].val)
|
av.val = string.unpack(">I4", av.val)
|
||||||
elseif ( attrib.value[i].tag == IPP.Attribute.IPP_TAG_ENUM ) then
|
elseif av.tag == IPP.Attribute.IPP_TAG_ENUM then
|
||||||
attrib.value[i].val = string.unpack(">I4", attrib.value[i].val)
|
av.val = string.unpack(">I4", av.val)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if ( 1 == #attrib.value ) then
|
if ( 1 == #attrib.value ) then
|
||||||
attrib.value = attrib.value[1].val
|
attrib.value = attrib.value[1].val
|
||||||
end
|
end
|
||||||
--print(attrib.name, attrib.value, stdnse.tohex(val))
|
|
||||||
|
|
||||||
return pos, attrib
|
return pos, attrib
|
||||||
end,
|
end,
|
||||||
@@ -152,26 +147,14 @@ IPP = {
|
|||||||
-- @param tag number containing the attribute tag
|
-- @param tag number containing the attribute tag
|
||||||
getAttribute = function(self, name, tag)
|
getAttribute = function(self, name, tag)
|
||||||
for _, attrib in ipairs(self.attribs) do
|
for _, attrib in ipairs(self.attribs) do
|
||||||
if ( attrib.name == name ) then
|
if attrib.name == name and (not tag or attrib.tag == tag) then
|
||||||
if ( not(tag) ) then
|
|
||||||
return attrib
|
return attrib
|
||||||
elseif ( tag and attrib.tag == tag ) then
|
|
||||||
return attrib
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
getAttributeValue = function(self, name, tag)
|
getAttributeValue = function(self, name, tag)
|
||||||
for _, attrib in ipairs(self.attribs) do
|
return (self:getAttribute(name, tag) or {}).value
|
||||||
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
|
|
||||||
end,
|
end,
|
||||||
|
|
||||||
__tostring = function(self)
|
__tostring = function(self)
|
||||||
@@ -332,7 +315,7 @@ Helper = {
|
|||||||
request:addAttributeGroup(ag)
|
request:addAttributeGroup(ag)
|
||||||
|
|
||||||
local status, response = HTTP.Request( self.host, self.port, tostring(request) )
|
local status, response = HTTP.Request( self.host, self.port, tostring(request) )
|
||||||
if ( not(response) ) then
|
if not status then
|
||||||
return status, response
|
return status, response
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -350,17 +333,15 @@ Helper = {
|
|||||||
|
|
||||||
local printer = {}
|
local printer = {}
|
||||||
for k, v in pairs(attrib) do
|
for k, v in pairs(attrib) do
|
||||||
if ( ag:getAttributeValue(k) ) then
|
|
||||||
printer[v] = ag:getAttributeValue(k)
|
printer[v] = ag:getAttributeValue(k)
|
||||||
end
|
end
|
||||||
end
|
|
||||||
table.insert(printers, printer)
|
table.insert(printers, printer)
|
||||||
end
|
end
|
||||||
return true, printers
|
return true, printers
|
||||||
end,
|
end,
|
||||||
|
|
||||||
getQueueInfo = function(self, uri)
|
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 = {
|
local attribs = {
|
||||||
IPP.Attribute:new(IPP.Attribute.IPP_TAG_CHARSET, "attributes-charset", "utf-8" ),
|
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-name" },
|
||||||
{ tag = IPP.Attribute.IPP_TAG_KEYWORD, val = "job-state" },
|
{ 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 = "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-state-message" },
|
||||||
-- { tag = IPP.Attribute.IPP_TAG_KEYWORD, val = "job-printer-uri" },
|
-- { tag = IPP.Attribute.IPP_TAG_KEYWORD, val = "job-printer-uri" },
|
||||||
{ tag = IPP.Attribute.IPP_TAG_KEYWORD, val = "time-at-creation" } } ),
|
{ tag = IPP.Attribute.IPP_TAG_KEYWORD, val = "time-at-creation" } } ),
|
||||||
@@ -387,23 +368,25 @@ Helper = {
|
|||||||
request:addAttributeGroup(ag)
|
request:addAttributeGroup(ag)
|
||||||
|
|
||||||
local status, response = HTTP.Request( self.host, self.port, tostring(request) )
|
local status, response = HTTP.Request( self.host, self.port, tostring(request) )
|
||||||
if ( not(response) ) then
|
if not status then
|
||||||
return status, response
|
return status, response
|
||||||
end
|
end
|
||||||
|
|
||||||
local results = {}
|
local results = {}
|
||||||
for _, ag in ipairs(response:getAttributeGroups(IPP.Attribute.IPP_TAG_JOB)) do
|
for _, ag in ipairs(response:getAttributeGroups(IPP.Attribute.IPP_TAG_JOB)) do
|
||||||
local uri = ag:getAttributeValue("printer-uri")
|
local printer = ag:getAttributeValue("printer-uri"):match(".*/(.*)$") or "Unknown"
|
||||||
local printer = uri:match(".*/(.*)$") or "Unknown"
|
|
||||||
-- some jobs have multiple state attributes, so far the ENUM ones have been correct
|
-- 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")
|
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
|
-- 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 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 tm = ag:getAttributeValue("time-at-creation")
|
||||||
local size = ag:getAttributeValue("job-k-octets") .. "k"
|
local size = ag:getAttributeValue("job-k-octets") .. "k"
|
||||||
local jobname = ag:getAttributeValue("com.apple.print.JobInfo.PMJobName") or "Unknown"
|
local jobname = ag:getAttributeValue("com.apple.print.JobInfo.PMJobName")
|
||||||
local owner = ag:getAttributeValue("com.apple.print.JobInfo.PMJobOwner") or "Unknown"
|
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 {}
|
results[printer] = results[printer] or {}
|
||||||
table.insert(results[printer], {
|
table.insert(results[printer], {
|
||||||
|
|||||||
@@ -154,67 +154,54 @@ Comm = {
|
|||||||
-- @return status boolean true on success, false on failure
|
-- @return status boolean true on success, false on failure
|
||||||
-- @return string containing error message (if status is false)
|
-- @return string containing error message (if status is false)
|
||||||
Connect = function(self, host, port, timeout)
|
Connect = function(self, host, port, timeout)
|
||||||
local status, err, socket
|
timeout = timeout or stdnse.get_timeout(host, 10000)
|
||||||
status, err = self:ChkProgram()
|
local status, err = self:ChkProgram()
|
||||||
if (not(status)) then
|
if not status then
|
||||||
return status, err
|
return status, err
|
||||||
end
|
end
|
||||||
status, err = self:ChkVersion()
|
status, err = self:ChkVersion()
|
||||||
if (not(status)) then
|
if not status then
|
||||||
return status, err
|
return status, err
|
||||||
end
|
end
|
||||||
timeout = timeout or stdnse.get_timeout(host, 10000)
|
local socket = nmap.new_socket(port.protocol)
|
||||||
local new_socket = function(...)
|
if nmap.is_privileged() then
|
||||||
local socket = nmap.new_socket(...)
|
-- 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)
|
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)
|
|
||||||
if status then
|
|
||||||
status, err = socket:connect(host, port)
|
status, err = socket:connect(host, port)
|
||||||
if status or err == "TIMEOUT" then break end
|
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 = socket:get_info()
|
||||||
|
if status then
|
||||||
|
if lport == srcport then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
status = false
|
||||||
|
err = "Address already in use"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
socket:close()
|
socket:close()
|
||||||
end
|
end
|
||||||
end
|
|
||||||
else
|
else
|
||||||
socket = new_socket()
|
-- No privileges to force a specific source port
|
||||||
status, err = socket:connect(host, port)
|
status, err = socket:connect(host, port)
|
||||||
end
|
end
|
||||||
else
|
if not status then
|
||||||
if nmap.is_privileged() then
|
return status, ("%s connect error: %s"):format(self.program, err)
|
||||||
-- 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
|
||||||
end
|
|
||||||
else
|
|
||||||
socket = new_socket("udp")
|
|
||||||
status, err = socket:connect(host, port)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if (not(status)) then
|
|
||||||
return status, string.format("%s connect error: %s",
|
|
||||||
self.program, err)
|
|
||||||
else
|
|
||||||
self.socket = socket
|
self.socket = socket
|
||||||
self.host = host
|
self.host = host
|
||||||
self.ip = host.ip
|
self.ip = host.ip
|
||||||
self.port = port.number
|
self.port = port.number
|
||||||
self.proto = port.protocol
|
self.proto = port.protocol
|
||||||
return status, nil
|
return status, nil
|
||||||
end
|
|
||||||
end,
|
end,
|
||||||
|
|
||||||
--- Disconnects from the remote program
|
--- Disconnects from the remote program
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ local ELEMENTS = {["Type"] = "Type",
|
|||||||
["string"] = "SOAPACTIONS",
|
["string"] = "SOAPACTIONS",
|
||||||
["SubDeviceURLs"] = "Sub Device URLs"}
|
["SubDeviceURLs"] = "Sub Device URLs"}
|
||||||
|
|
||||||
function get_text_callback(store, name)
|
local function get_text_callback(store, name)
|
||||||
if ELEMENTS[name] == nil then return end
|
if ELEMENTS[name] == nil then return end
|
||||||
name = ELEMENTS[name]
|
name = ELEMENTS[name]
|
||||||
if name == 'SOAPACTIONS' or name == 'Sub Device URLs' or name == 'Type' then
|
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 nmap = require "nmap"
|
||||||
local shortport = require "shortport"
|
local shortport = require "shortport"
|
||||||
local stdnse = require "stdnse"
|
local stdnse = require "stdnse"
|
||||||
local ipOps = require "ipOps"
|
local target = require "target"
|
||||||
|
local url = require "url"
|
||||||
|
|
||||||
description = [[
|
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
|
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
|
headers when returning a redirect response. This is a known issue for some
|
||||||
versions of Microsoft IIS, but affects other web servers as well.
|
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 <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
|
-- @output
|
||||||
-- 80/tcp open http syn-ack
|
-- 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
|
-- @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"
|
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
|
||||||
categories = { "vuln", "discovery", "safe" }
|
categories = { "vuln", "discovery", "safe" }
|
||||||
|
|
||||||
portrule = shortport.http
|
portrule = shortport.http
|
||||||
|
|
||||||
local function generateHttpV1_0Req(host, port, path)
|
action = function(host, port)
|
||||||
local redirectIP, privateIP
|
local patharg = stdnse.get_script_args(SCRIPT_NAME .. ".path") or "/"
|
||||||
local socket = nmap.new_socket()
|
if type(patharg) ~= "table" then
|
||||||
socket:connect(host, port)
|
patharg = {patharg}
|
||||||
|
|
||||||
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
|
end
|
||||||
|
local paths = stdnse.output_table()
|
||||||
-- Check if the response contains a location header
|
for _, path in ipairs(patharg) do
|
||||||
if lines:match("Location") then
|
paths[path] = 1
|
||||||
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
|
end
|
||||||
|
paths["/images"] = 1
|
||||||
|
|
||||||
stdnse.debug1("Location: %s", locTarget )
|
local socket
|
||||||
stdnse.debug1("Internal IP: %s", redirectIP )
|
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
|
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
|
end
|
||||||
|
|
||||||
socket:close()
|
socket:close()
|
||||||
|
|
||||||
-- Only report if the internal IP leaked is different then the target IP
|
local loc = resp:lower():match("\nlocation:[ \t]+(%S+)")
|
||||||
if privateIP and redirectIP ~= host.ip then
|
local lochost = url.parse(loc or "").host
|
||||||
return redirectIP
|
if lochost and lochost ~= "" then
|
||||||
end
|
-- remove any IPv6 enclosure
|
||||||
end
|
lochost = lochost:gsub("^%[(.*)%]$", "%1")
|
||||||
|
|
||||||
action = function(host, port)
|
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()
|
local output = stdnse.output_table()
|
||||||
local path = stdnse.get_script_args(SCRIPT_NAME .. ".path") or "/"
|
output["Internal IP Leaked"] = lochost
|
||||||
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)
|
|
||||||
end
|
|
||||||
|
|
||||||
if IP then
|
|
||||||
output["Internal IP Leaked"] = IP
|
|
||||||
return output
|
return output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|||||||
@@ -51,34 +51,36 @@ Driver = {
|
|||||||
end,
|
end,
|
||||||
|
|
||||||
-- connects to the rlogin service
|
-- 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)
|
connect = function(self)
|
||||||
|
local status, err
|
||||||
local status
|
|
||||||
|
|
||||||
self.socket = brute.new_socket()
|
self.socket = brute.new_socket()
|
||||||
-- apparently wee need a source port below 1024
|
-- Let's make several attempts to bind to an unused well-known port
|
||||||
-- this approach is not very elegant as it causes address already in
|
for _ = 1, 10 do
|
||||||
-- use errors when the same src port is hit in a short time frame.
|
local srcport = math.random(512, 1023)
|
||||||
-- hopefully the retry count should take care of this as a retry
|
status, err = self.socket:bind(nil, srcport)
|
||||||
-- should choose a new random port as source.
|
if status then
|
||||||
local srcport = math.random(513, 1024)
|
|
||||||
self.socket:bind(nil, srcport)
|
|
||||||
self.socket:set_timeout(self.timeout)
|
self.socket:set_timeout(self.timeout)
|
||||||
local err
|
|
||||||
status, err = self.socket:connect(self.host, self.port)
|
status, err = self.socket:connect(self.host, self.port)
|
||||||
|
if status then
|
||||||
if ( status ) then
|
-- socket:connect() succeeds even if mksock_bind_addr() fails.
|
||||||
local lport, _
|
-- It just assigns an ephemeral port instead of our choice,
|
||||||
status, _, lport = self.socket:get_info()
|
-- so we need to check the actual source port afterwards.
|
||||||
if (not(status) ) then
|
local lport
|
||||||
return false, "failed to retrieve socket status"
|
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
|
end
|
||||||
else
|
|
||||||
self.socket:close()
|
self.socket:close()
|
||||||
end
|
end
|
||||||
if ( not(status) ) then
|
if not status then
|
||||||
stdnse.debug3("ERROR: failed to connect to server")
|
stdnse.debug2("Unable to bind to a well-known port (%s)", err)
|
||||||
end
|
end
|
||||||
return status
|
return status
|
||||||
end,
|
end,
|
||||||
|
|||||||
@@ -6,6 +6,6 @@ Exec=su-to-zenmap.sh %F
|
|||||||
Terminal=false
|
Terminal=false
|
||||||
Icon=zenmap
|
Icon=zenmap
|
||||||
Type=Application
|
Type=Application
|
||||||
Categories=Application;Network;Security;
|
Categories=Network;Security;
|
||||||
Comment=A cross-platform GUI for the Nmap Security Scanner.
|
Comment=A cross-platform GUI for the Nmap Security Scanner.
|
||||||
Keywords=network;scan;scanner;IP;security;
|
Keywords=network;scan;scanner;IP;security;
|
||||||
|
|||||||
@@ -6,6 +6,6 @@ Exec=zenmap %F
|
|||||||
Terminal=false
|
Terminal=false
|
||||||
Icon=zenmap
|
Icon=zenmap
|
||||||
Type=Application
|
Type=Application
|
||||||
Categories=Application;Network;Security;
|
Categories=Network;Security;
|
||||||
Comment=A cross-platform GUI for the Nmap Security Scanner.
|
Comment=A cross-platform GUI for the Nmap Security Scanner.
|
||||||
Keywords=network;scan;scanner;IP;security;
|
Keywords=network;scan;scanner;IP;security;
|
||||||
|
|||||||
Reference in New Issue
Block a user