1
0
mirror of https://github.com/nmap/nmap.git synced 2025-12-09 22:21:29 +00:00

Compare commits

...

23 Commits

Author SHA1 Message Date
nnposter
cf24dab1c9 Streamline the loop construct 2025-12-04 19:44:12 +00:00
nnposter
df4896eadb Remove unnecessary conditional 2025-12-04 19:39:52 +00:00
nnposter
472b586767 Refactor the script to address multiple issues
- Add support for HTTPS
  - Add support for IPv6
  - Add support for more than one path argument
  - Properly identify the Location header in the HTTP response
  - Properly identify the destination host in the Location header
  - Leverage normalized IP address comparison
  - Avoid processing the HTTP response body, possibly "endless"
  - Add the found IP address as a new scan target (optionally)
Close #3218, close #3191)
2025-12-02 23:55:01 +00:00
nnposter
306263da43 Explain the effect of arguments that are not script-specific. Close #3223, close #3221, fix #3211 2025-12-02 22:48:31 +00:00
nnposter
c0a01aa7e1 Keep a local function local 2025-12-02 04:49:44 +00:00
nnposter
46fe5228f4 Populate jobname and owner even without Apple-specific attributes 2025-11-28 03:48:38 +00:00
nnposter
6dc02c9bcd Remove duplicate code 2025-11-28 03:45:12 +00:00
nnposter
3d205335b9 Remove development leftover 2025-11-28 03:41:56 +00:00
nnposter
138c7b7467 Eliminate unnecessary repeated lookups 2025-11-28 03:40:56 +00:00
nnposter
5daccaed1d Remove unnecessary variable shadowing logically the same parameter 2025-11-25 03:03:26 +00:00
nnposter
790deb7daf Remove unused variable 2025-11-25 02:59:19 +00:00
nnposter
a04fc3389e Use the correct variable for error checking. Close #3232 2025-11-23 02:55:21 +00:00
nnposter
a74125aef5 Add missing cookie attributes 2025-11-01 23:34:30 +00:00
nnposter
f5a3251e97 Use the correct the port range, 512-1023, not 513-1024.
Detect if binding a socket to a given port failed and retry.
Close #3196
2025-11-01 22:52:49 +00:00
nnposter
8d7fa538e3 Properly detect if binding an RPC socket to a given port failed. Close #3194
Testing the return status of socket:bind() and socket:connect() is not enough.
For details, see #1939.
2025-11-01 22:34:51 +00:00
nnposter
8d06576dbb Remove deprecated category "Application". Close #3217 2025-10-28 22:57:18 +00:00
nnposter
d2d591ce0c Avoid a crash when the IP contains a colon but no hextets 2025-10-27 01:19:40 +00:00
nnposter
4f7c92fbac Include operand "ne" in the documentation 2025-10-26 19:54:18 +00:00
nnposter
d2fbcc6cd6 Perform effective socket error checking 2025-10-26 02:23:22 +00:00
nnposter
b4b921c913 Streamline the code by using math.min() 2025-10-26 02:14:47 +00:00
nnposter
81b0568452 Remove redundant code 2025-10-26 01:42:15 +00:00
nnposter
7a989ff957 Remove duplicate of previously defined skip_space() 2025-10-26 01:37:00 +00:00
nnposter
9289bbccee Skip over contiguous linear whitespace in a single step 2025-10-26 01:35:07 +00:00
11 changed files with 212 additions and 207 deletions

View File

@@ -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

View File

@@ -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>

View File

@@ -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

View File

@@ -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 = {}

View File

@@ -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
val, pos = string.unpack(">s2", data, pos)
tag, name_len, pos = string.unpack(">BI2", data, pos) table.insert(attrib.value, { tag = tag, val = val })
if ( name_len == 0 ) then end
val, pos = string.unpack(">s2", data, pos)
table.insert(attrib.value, { tag = tag, val = val })
else
pos = pos - 3
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,9 +333,7 @@ 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
@@ -360,7 +341,7 @@ Helper = {
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], {

View File

@@ -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
socket:set_timeout(timeout) for _ = 1, 10 do
return socket local srcport = math.random(512, 1023)
end status, err = socket:bind(nil, srcport)
if ( port.protocol == "tcp" ) then if status then
if nmap.is_privileged() then socket:set_timeout(timeout)
-- Try to bind to a reserved port status, err = socket:connect(host, 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 if status then
status, err = socket:connect(host, port) -- socket:connect() succeeds even if mksock_bind_addr() fails.
if status or err == "TIMEOUT" then break end -- It just assigns an ephemeral port instead of our choice,
socket:close() -- 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
end end
else socket:close()
socket = new_socket()
status, err = socket:connect(host, port)
end end
else else
if nmap.is_privileged() then -- No privileges to force a specific source port
-- Try to bind to a reserved port status, err = socket:connect(host, 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
end end
if (not(status)) then if not status then
return status, string.format("%s connect error: %s", return status, ("%s connect error: %s"):format(self.program, err)
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
end end
self.socket = socket
self.host = host
self.ip = host.ip
self.port = port.number
self.proto = port.protocol
return status, nil
end, end,
--- Disconnects from the remote program --- Disconnects from the remote program

View File

@@ -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

View File

@@ -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)
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) action = function(host, port)
local output = stdnse.output_table() local patharg = stdnse.get_script_args(SCRIPT_NAME .. ".path") or "/"
local path = stdnse.get_script_args(SCRIPT_NAME .. ".path") or "/" if type(patharg) ~= "table" then
local IP = generateHttpV1_0Req(host, port, path) patharg = {patharg}
-- 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 end
local paths = stdnse.output_table()
for _, path in ipairs(patharg) do
paths[path] = 1
end
paths["/images"] = 1
if IP then local socket
output["Internal IP Leaked"] = IP local bopt = nil
return output 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
end end

View File

@@ -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:set_timeout(self.timeout)
self.socket:bind(nil, srcport) status, err = self.socket:connect(self.host, self.port)
self.socket:set_timeout(self.timeout) if status then
local err -- socket:connect() succeeds even if mksock_bind_addr() fails.
status, err = self.socket:connect(self.host, self.port) -- It just assigns an ephemeral port instead of our choice,
-- so we need to check the actual source port afterwards.
if ( status ) then local lport
local lport, _ status, err, lport = self.socket:get_info()
status, _, lport = self.socket:get_info() if status then
if (not(status) ) then if lport == srcport then
return false, "failed to retrieve socket status" 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,

View File

@@ -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;

View File

@@ -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;