229 lines
8.3 KiB
Lua
229 lines
8.3 KiB
Lua
local dhcp = require "dhcp"
|
|
local rand = require "rand"
|
|
local nmap = require "nmap"
|
|
local outlib = require "outlib"
|
|
local shortport = require "shortport"
|
|
local stdnse = require "stdnse"
|
|
local string = require "string"
|
|
local table = require "table"
|
|
local ipOps = require "ipOps"
|
|
|
|
description = [[
|
|
Sends a DHCPINFORM request to a host on UDP port 67 to obtain all the local configuration parameters
|
|
without allocating a new address.
|
|
|
|
DHCPINFORM is a DHCP request that returns useful information from a DHCP server, without allocating an IP
|
|
address. The request sends a list of which fields it wants to know (a handful by default, every field if
|
|
verbosity is turned on), and the server responds with the fields that were requested. It should be noted
|
|
that the server doesn't have to return every field, nor does it have to return them in the same order,
|
|
or honour the request at all. A Linksys WRT54g, for example, completely ignores the list of requested
|
|
fields and returns a few standard ones. This script displays every field it receives.
|
|
|
|
With script arguments, the type of DHCP request can be changed, which can lead to interesting results.
|
|
Additionally, the MAC address can be randomized, which in should override the cache on the DHCP server and
|
|
assign a new IP address. Extra requests can also be sent to exhaust the IP address range more quickly.
|
|
|
|
Some of the more useful fields:
|
|
* DHCP Server (the address of the server that responded)
|
|
* Subnet Mask
|
|
* Router
|
|
* DNS Servers
|
|
* Hostname
|
|
]]
|
|
|
|
---
|
|
-- @see broadcast-dhcp6-discover.nse
|
|
-- @see broadcast-dhcp-discover.nse
|
|
--
|
|
-- @args dhcp-discover.dhcptype The type of DHCP request to make. By default,
|
|
-- DHCPINFORM is sent, but this argument can change it to DHCPOFFER,
|
|
-- DHCPREQUEST, DHCPDECLINE, DHCPACK, DHCPNAK, DHCPRELEASE or
|
|
-- DHCPINFORM. Not all types will evoke a response from all servers,
|
|
-- and many require different fields to contain specific values.
|
|
-- @args dhcp-discover.mac Set to <code>native</code> (default) or
|
|
-- <code>random</code> or a specific client MAC address in the DHCP
|
|
-- request. Keep in mind that you may not see the response if
|
|
-- a non-native address is used. Setting it to <code>random</code> will
|
|
-- possibly cause the DHCP server to reserve a new IP address each time.
|
|
-- @args dhcp-discover.clientid Client identifier to use in DHCP option 61.
|
|
-- The value is a string, while hardware type 0, appropriate for FQDNs,
|
|
-- is assumed. Example: clientid=kurtz is equivalent to specifying
|
|
-- clientid-hex=00:6b:75:72:74:7a (see below).
|
|
-- @args dhcp-discover.clientid-hex Client identifier to use in DHCP option 61.
|
|
-- The value is a hexadecimal string, where the first octet is
|
|
-- the hardware type.
|
|
-- @args dhcp-discover.requests Set to an integer to make up to that many
|
|
-- requests (and display the results).
|
|
--
|
|
-- @usage
|
|
-- nmap -sU -p 67 --script=dhcp-discover <target>
|
|
-- @output
|
|
-- Interesting ports on 192.168.1.1:
|
|
-- PORT STATE SERVICE
|
|
-- 67/udp open dhcps
|
|
-- | dhcp-discover:
|
|
-- | DHCP Message Type: DHCPACK
|
|
-- | Server Identifier: 192.168.1.1
|
|
-- | IP Address Lease Time: 1 day, 0:00:00
|
|
-- | Subnet Mask: 255.255.255.0
|
|
-- | Router: 192.168.1.1
|
|
-- |_ Domain Name Server: 208.81.7.10, 208.81.7.14
|
|
--
|
|
-- @xmloutput
|
|
-- <elem key="DHCP Message Type">DHCPACK</elem>
|
|
-- <elem key="Server Identifier">192.168.1.1</elem>
|
|
-- <elem key="IP Address Lease Time">1 day, 0:00:00</elem>
|
|
-- <elem key="Subnet Mask">255.255.255.0</elem>
|
|
-- <elem key="Router">192.168.1.1</elem>
|
|
-- <table key="Domain Name Server">
|
|
-- <elem>208.81.7.10</elem>
|
|
-- <elem>208.81.7.14</elem>
|
|
-- </table>
|
|
--
|
|
|
|
--
|
|
-- 2022-04-22 - Revised by nnposter
|
|
-- o Implemented script arguments "clientid" and "clientid-hex" to allow
|
|
-- passing a specific client identifier (option 61)
|
|
--
|
|
-- 2020-01-14 - Revised by nnposter
|
|
-- o Added script argument "mac" to prescribe a specific MAC address
|
|
-- o Deprecated argument "randomize_mac" in favor of "mac=random"
|
|
--
|
|
-- 2011-12-28 - Revised by Patrik Karlsson <patrik@cqure.net>
|
|
-- o Removed DoS code and placed script into discovery and safe categories
|
|
--
|
|
-- 2011-12-27 - Revised by Patrik Karlsson <patrik@cqure.net>
|
|
-- o Changed script to use DHCPINFORM instead of DHCPDISCOVER
|
|
--
|
|
|
|
|
|
author = "Ron Bowes"
|
|
|
|
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
|
|
|
|
categories = {"discovery", "safe"}
|
|
|
|
|
|
-- We want to run against a specific host if UDP/67 is open
|
|
function portrule(host, port)
|
|
if nmap.address_family() ~= 'inet' then
|
|
stdnse.debug1("is IPv4 compatible only.")
|
|
return false
|
|
end
|
|
|
|
return shortport.portnumber(67, "udp")(host, port)
|
|
end
|
|
|
|
action = function(host, port)
|
|
local dhcptype = (stdnse.get_script_args(SCRIPT_NAME .. ".dhcptype") or "DHCPINFORM"):upper()
|
|
local dhcptypeid = dhcp.request_types[dhcptype]
|
|
if not dhcptypeid then
|
|
return stdnse.format_output(false, "Invalid request type (use "
|
|
.. table.concat(dhcp.request_types_str, " / ")
|
|
.. ")")
|
|
end
|
|
|
|
local reqcount = tonumber(stdnse.get_script_args(SCRIPT_NAME .. ".requests") or 1)
|
|
if not reqcount then
|
|
return stdnse.format_output(false, "Invalid request count")
|
|
end
|
|
|
|
local iface, err = nmap.get_interface_info(host.interface)
|
|
if not (iface and iface.address) then
|
|
return stdnse.format_output(false, "Couldn't determine local IP for interface: " .. host.interface)
|
|
end
|
|
|
|
local options = {}
|
|
local overrides = {}
|
|
|
|
local macaddr = (stdnse.get_script_args(SCRIPT_NAME .. ".mac") or "native"):lower()
|
|
-- Support for legacy argument "randomize_mac"
|
|
local randomize = (stdnse.get_script_args(SCRIPT_NAME .. ".randomize_mac") or "false"):lower()
|
|
if randomize == "true" or randomize == "1" then
|
|
stdnse.debug1("Use %s.mac=random instead of %s.randomize_mac=%s", SCRIPT_NAME, SCRIPT_NAME, randomize)
|
|
macaddr = "random"
|
|
end
|
|
if macaddr ~= "native" then
|
|
-- Set the scanner as a relay agent
|
|
overrides.giaddr = string.unpack("<I4", ipOps.ip_to_str(iface.address))
|
|
end
|
|
local macaddr_iter
|
|
if macaddr:find("^ra?nd") then
|
|
macaddr_iter = function () return rand.random_string(6) end
|
|
else
|
|
if macaddr == "native" then
|
|
macaddr = host.mac_addr_src
|
|
else
|
|
macaddr = macaddr:gsub(":", "")
|
|
if not (#macaddr == 12 and macaddr:find("^%x+$")) then
|
|
return stdnse.format_output(false, "Invalid MAC address")
|
|
end
|
|
macaddr = stdnse.fromhex(macaddr)
|
|
end
|
|
macaddr_iter = function () return macaddr end
|
|
end
|
|
|
|
local clientid = stdnse.get_script_args(SCRIPT_NAME .. ".clientid")
|
|
if clientid then
|
|
clientid = "\x00" .. clientid -- hardware type 0 presumed
|
|
else
|
|
clientid = stdnse.get_script_args(SCRIPT_NAME .. ".clientid-hex")
|
|
if clientid then
|
|
clientid = clientid:gsub(":", "")
|
|
if not clientid:find("^%x+$") then
|
|
return stdnse.format_output(false, "Invalid hexadecimal client ID")
|
|
end
|
|
clientid = stdnse.fromhex(clientid)
|
|
end
|
|
end
|
|
if clientid then
|
|
if #clientid == 0 or #clientid > 255 then
|
|
return stdnse.format_output(false, "Client ID must be between 1 and 255 characters long")
|
|
end
|
|
table.insert(options, {number = 61, type = "string", value = clientid })
|
|
end
|
|
|
|
local results = {}
|
|
for i = 1, reqcount do
|
|
local macaddr = macaddr_iter()
|
|
stdnse.debug1("Client MAC address: %s", stdnse.tohex(macaddr, {separator = ":"}))
|
|
local status, result = dhcp.make_request(host.ip, dhcptypeid, iface.address, macaddr, options, nil, overrides)
|
|
if not status then
|
|
return stdnse.format_output(false, "Couldn't send DHCP request: " .. result)
|
|
end
|
|
table.insert(results, result)
|
|
end
|
|
|
|
if #results == 0 then
|
|
return nil
|
|
end
|
|
|
|
nmap.set_port_state(host, port, "open")
|
|
|
|
local response = stdnse.output_table()
|
|
|
|
-- Display the results
|
|
for i, result in ipairs(results) do
|
|
local result_table = stdnse.output_table()
|
|
|
|
if dhcptype ~= "DHCPINFORM" then
|
|
result_table["IP Offered"] = result.yiaddr_str
|
|
end
|
|
for _, v in ipairs(result.options) do
|
|
if type(v.value) == 'table' then
|
|
outlib.list_sep(v.value)
|
|
end
|
|
result_table[ v.name ] = v.value
|
|
end
|
|
|
|
if(#results == 1) then
|
|
response = result_table
|
|
else
|
|
response[string.format("Response %d of %d", i, #results)] = result_table
|
|
end
|
|
end
|
|
|
|
return response
|
|
end
|