diff --git a/CHANGELOG b/CHANGELOG
index 1a1cd8685..7785283c9 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,9 @@
#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]
diff --git a/scripts/http-internal-ip-disclosure.nse b/scripts/http-internal-ip-disclosure.nse
index aeb04b92c..e3da5a5c9 100644
--- a/scripts/http-internal-ip-disclosure.nse
+++ b/scripts/http-internal-ip-disclosure.nse
@@ -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 newtargets is set, the script will
+add the found IP address as a new target into the scan queue. (See
+the documentation for NSE library target for details.)
]]
---
-- @usage nmap --script http-internal-ip-disclosure
--- @usage nmap --script http-internal-ip-disclosure --script-args http-internal-ip-disclosure.path=/path
+-- @usage nmap --script http-internal-ip-disclosure --script-args http-internal-ip-disclosure.path=/mypath
--
--- @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