mirror of
https://github.com/nmap/nmap.git
synced 2025-12-06 04:31:29 +00:00
Update upnp-info: structured output, correct targets added, etc.
This commit is contained in:
193
nselib/upnp.lua
193
nselib/upnp.lua
@@ -41,6 +41,9 @@ local stdnse = require "stdnse"
|
|||||||
local string = require "string"
|
local string = require "string"
|
||||||
local table = require "table"
|
local table = require "table"
|
||||||
local target = require "target"
|
local target = require "target"
|
||||||
|
local slaxml = require "slaxml"
|
||||||
|
local url = require "url"
|
||||||
|
local outlib = require "outlib"
|
||||||
_ENV = stdnse.module("upnp", stdnse.seeall)
|
_ENV = stdnse.module("upnp", stdnse.seeall)
|
||||||
|
|
||||||
Util = {
|
Util = {
|
||||||
@@ -56,6 +59,17 @@ Util = {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local device_elements = {
|
||||||
|
deviceType = true,
|
||||||
|
serviceType = true,
|
||||||
|
friendlyName = true,
|
||||||
|
manufacturer = true,
|
||||||
|
modelDescription = true,
|
||||||
|
modelName = true,
|
||||||
|
modelNumber = true,
|
||||||
|
UDN = true,
|
||||||
|
}
|
||||||
|
|
||||||
Comm = {
|
Comm = {
|
||||||
|
|
||||||
--- Creates a new Comm instance
|
--- Creates a new Comm instance
|
||||||
@@ -130,7 +144,6 @@ Comm = {
|
|||||||
receiveResponse = function( self )
|
receiveResponse = function( self )
|
||||||
local status, response
|
local status, response
|
||||||
local result = {}
|
local result = {}
|
||||||
local host_responses = {}
|
|
||||||
|
|
||||||
repeat
|
repeat
|
||||||
status, response = self.socket:receive()
|
status, response = self.socket:receive()
|
||||||
@@ -140,31 +153,19 @@ Comm = {
|
|||||||
break
|
break
|
||||||
end
|
end
|
||||||
|
|
||||||
local status, _, _, ip, _ = self.socket:get_info()
|
local status = self:decodeResponse( response, result )
|
||||||
if ( not(status) ) then
|
if ( not(status) ) then
|
||||||
return false, "Failed to retrieve socket information"
|
return false, "Failed to decode UPNP response"
|
||||||
end
|
|
||||||
if target.ALLOW_NEW_TARGETS then target.add(ip) end
|
|
||||||
|
|
||||||
if ( not(host_responses[ip]) ) then
|
|
||||||
local status, output = self:decodeResponse( response )
|
|
||||||
if ( not(status) ) then
|
|
||||||
return false, "Failed to decode UPNP response"
|
|
||||||
end
|
|
||||||
output = { output }
|
|
||||||
output.name = ip
|
|
||||||
table.insert( result, output )
|
|
||||||
host_responses[ip] = true
|
|
||||||
end
|
end
|
||||||
until ( not( self.mcast ) )
|
until ( not( self.mcast ) )
|
||||||
|
|
||||||
if ( self.mcast ) then
|
if ( self.mcast ) then
|
||||||
table.sort(result, Util.ipCompare)
|
return true, outlib.sorted_by_key(result, Util.ipCompare)
|
||||||
return true, result
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if ( status and #result > 0 ) then
|
if status then
|
||||||
return true, result[1]
|
local i, v = next(result)
|
||||||
|
return (not not i), v
|
||||||
else
|
else
|
||||||
return false, "Received no responses"
|
return false, "Received no responses"
|
||||||
end
|
end
|
||||||
@@ -175,34 +176,59 @@ Comm = {
|
|||||||
-- @param response as received over the socket
|
-- @param response as received over the socket
|
||||||
-- @return status boolean true on success, false on failure
|
-- @return status boolean true on success, false on failure
|
||||||
-- @return response table or string suitable for output or error message if status is false
|
-- @return response table or string suitable for output or error message if status is false
|
||||||
decodeResponse = function( self, response )
|
decodeResponse = function( self, response, results )
|
||||||
local output = {}
|
local output = stdnse.output_table()
|
||||||
|
local key
|
||||||
|
|
||||||
if response ~= nil then
|
-- We should get a response back that has contains one line for the server, and one line for the xml file location
|
||||||
-- We should get a response back that has contains one line for the server, and one line for the xml file location
|
-- these match any combination of upper and lower case responses
|
||||||
-- these match any combination of upper and lower case responses
|
local usn = string.match(response, "\n[Uu][Ss][Nn]:%s*([Uu][Uu][Ii][Dd]:[%x-]+)")
|
||||||
local server, location
|
if usn then
|
||||||
server = string.match(response, "[Ss][Ee][Rr][Vv][Ee][Rr]:%s*(.-)\r?\n")
|
key = usn
|
||||||
if server ~= nil then table.insert(output, "Server: " .. server ) end
|
output.usn = usn
|
||||||
location = string.match(response, "[Ll][Oo][Cc][Aa][Tt][Ii][Oo][Nn]:%s*(.-)\r?\n")
|
end
|
||||||
if location ~= nil then
|
local location = string.match(response, "\n[Ll][Oo][Cc][Aa][Tt][Ii][Oo][Nn]:%s*(.-)\r?\n")
|
||||||
table.insert(output, "Location: " .. location )
|
if location then
|
||||||
|
local loc_url = url.parse(location)
|
||||||
|
if loc_url.host then
|
||||||
|
key = loc_url.host
|
||||||
|
if target.ALLOW_NEW_TARGETS then target.add(loc_url.host) end
|
||||||
|
end
|
||||||
|
output.location = location
|
||||||
|
end
|
||||||
|
|
||||||
local v = nmap.verbosity()
|
if key and results[key] then
|
||||||
|
return false, "Already recorded a response for this host"
|
||||||
|
end
|
||||||
|
|
||||||
-- the following check can output quite a lot of information, so we require at least one -v flag
|
local server = string.match(response, "\n[Ss][Ee][Rr][Vv][Ee][Rr]:%s*(.-)\r?\n")
|
||||||
if v > 0 then
|
if server ~= nil then output.server = server end
|
||||||
local status, result = self:retrieveXML( location )
|
|
||||||
if status then
|
if location and nmap.verbosity() > 0 then
|
||||||
table.insert(output, result)
|
-- the following check can output quite a lot of information, so we require at least one -v flag
|
||||||
|
local status, result = self:retrieveXML( location )
|
||||||
|
if status then
|
||||||
|
if result.webserver ~= output.server then
|
||||||
|
output.webserver = result.webserver
|
||||||
|
end
|
||||||
|
result.webserver = nil
|
||||||
|
if usn and result[usn] then
|
||||||
|
for k, v in pairs(result[usn]) do
|
||||||
|
output[k] = v
|
||||||
end
|
end
|
||||||
|
result[usn] = nil
|
||||||
|
end
|
||||||
|
if #result > 0 then
|
||||||
|
output.devices = result
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if #output > 0 then
|
end
|
||||||
return true, output
|
|
||||||
else
|
if #output > 0 then
|
||||||
return false, "Could not decode response"
|
results[key] = output
|
||||||
end
|
return true
|
||||||
|
else
|
||||||
|
return false, "Could not decode response"
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
|
|
||||||
@@ -223,55 +249,74 @@ Comm = {
|
|||||||
response = http.get_url( location, options )
|
response = http.get_url( location, options )
|
||||||
else
|
else
|
||||||
-- otherwise, split the location into an IP address, port, and path name for the xml file
|
-- otherwise, split the location into an IP address, port, and path name for the xml file
|
||||||
local xhost, xport, xfile
|
local loc_url = url.parse(location)
|
||||||
xhost = string.match(location, "http://(.-)/")
|
options.scheme = loc_url.scheme
|
||||||
-- check to see if the host portion of the location specifies a port
|
local xhost = loc_url.host
|
||||||
-- if not, use port 80 as a standard web server port
|
local xport = loc_url.port or url.get_default_port(loc_url.scheme) or 80
|
||||||
if xhost ~= nil and string.match(xhost, ":") then
|
local xfile = loc_url.path
|
||||||
xport = string.match(xhost, ":(.*)")
|
if loc_url.query then
|
||||||
xhost = string.match(xhost, "(.*):")
|
xfile = xfile .. "?" .. loc_url.query
|
||||||
end
|
end
|
||||||
|
|
||||||
-- check to see if the IP address returned matches the IP address we scanned
|
-- check to see if the IP address returned matches the IP address we scanned
|
||||||
if xhost ~= self.host.ip then
|
if not ipOps.compare_ip(xhost, "eq", self.host.ip) then
|
||||||
stdnse.debug1("IP addresses did not match! Found %s, using %s instead.", xhost, self.host.ip)
|
stdnse.debug1("IP addresses did not match! Found %s, using %s instead.", xhost, self.host.ip)
|
||||||
xhost = self.host.ip
|
xhost = self.host.ip
|
||||||
end
|
end
|
||||||
|
|
||||||
if xport == nil then
|
if xhost and xport and xfile then
|
||||||
xport = 80
|
|
||||||
end
|
|
||||||
|
|
||||||
-- extract the path name from the location field, but strip off the \r that HTTP servers return
|
|
||||||
xfile = string.match(location, "http://.-(/.-)\013")
|
|
||||||
if xfile ~= nil then
|
|
||||||
response = http.get( xhost, xport, xfile, options )
|
response = http.get( xhost, xport, xfile, options )
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if response ~= nil then
|
if response.body then
|
||||||
local output = {}
|
local output = stdnse.output_table()
|
||||||
|
|
||||||
-- extract information about the webserver that is handling responses for the UPnP system
|
-- extract information about the webserver that is handling responses for the UPnP system
|
||||||
local webserver = response['header']['server']
|
local webserver = response['header']['server']
|
||||||
if webserver ~= nil then table.insert(output, "Webserver: " .. webserver) end
|
if webserver then output.webserver = webserver end
|
||||||
|
|
||||||
-- the schema for UPnP includes a number of <device> entries, which can a number of interesting fields
|
-- the schema for UPnP includes a number of <device> entries, which can a number of interesting fields
|
||||||
for device in string.gmatch(response['body'], "<deviceType>(.-)</UDN>") do
|
local element
|
||||||
local fn, mnf, mdl, nm, ver
|
local devices = {}
|
||||||
|
local depth = 0
|
||||||
fn = string.match(device, "<friendlyName>(.-)</friendlyName>")
|
local parser = slaxml.parser:new({
|
||||||
mnf = string.match(device, "<manufacturer>(.-)</manufacturer>")
|
startElement = function(name)
|
||||||
mdl = string.match(device, "<modelDescription>(.-)</modelDescription>")
|
if name == "device" then
|
||||||
nm = string.match(device, "<modelName>(.-)</modelName>")
|
depth = depth + 1
|
||||||
ver = string.match(device, "<modelNumber>(.-)</modelNumber>")
|
devices[depth] = stdnse.output_table()
|
||||||
|
elseif devices[depth] and device_elements[name] then
|
||||||
if fn ~= nil then table.insert(output, "Name: " .. fn) end
|
assert(not element, "nested element unexpected")
|
||||||
if mnf ~= nil then table.insert(output,"Manufacturer: " .. mnf) end
|
element = name
|
||||||
if mdl ~= nil then table.insert(output,"Model Descr: " .. mdl) end
|
end
|
||||||
if nm ~= nil then table.insert(output,"Model Name: " .. nm) end
|
end,
|
||||||
if ver ~= nil then table.insert(output,"Model Version: " .. ver) end
|
closeElement = function(name)
|
||||||
end
|
if element then
|
||||||
|
assert(name == element, "close tag unexpected")
|
||||||
|
element = nil
|
||||||
|
elseif name == "device" then
|
||||||
|
local dev = devices[depth]
|
||||||
|
assert(dev and dev.UDN, "missing device or UDN")
|
||||||
|
output[dev.UDN] = dev
|
||||||
|
dev.UDN = nil
|
||||||
|
devices[depth] = nil
|
||||||
|
depth = depth - 1
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
text = function(content)
|
||||||
|
if element then
|
||||||
|
local dev = devices[depth]
|
||||||
|
if element == "serviceType" then
|
||||||
|
local services = dev.services or {}
|
||||||
|
services[#services+1] = content
|
||||||
|
dev.services = services
|
||||||
|
else
|
||||||
|
dev[element] = content
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
parser:parseSAX(response.body, {stripWhitespace=true})
|
||||||
return true, output
|
return true, output
|
||||||
else
|
else
|
||||||
return false, "Could not retrieve XML file"
|
return false, "Could not retrieve XML file"
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ action = function()
|
|||||||
local status, result = helper:queryServices()
|
local status, result = helper:queryServices()
|
||||||
|
|
||||||
if ( status ) then
|
if ( status ) then
|
||||||
return stdnse.format_output(true, result)
|
return result
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -50,6 +50,6 @@ action = function(host, port)
|
|||||||
|
|
||||||
if ( status ) then
|
if ( status ) then
|
||||||
nmap.set_port_state(host, port, "open")
|
nmap.set_port_state(host, port, "open")
|
||||||
return stdnse.format_output(true, result)
|
return result
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user