local coroutine = require "coroutine" local dhcp = require "dhcp" local ipOps = require "ipOps" local math = require "math" local nmap = require "nmap" local outlib = require "outlib" local packet = require "packet" local rand = require "rand" local stdnse = require "stdnse" local string = require "string" local table = require "table" description = [[ Sends a DHCP request to the broadcast address (255.255.255.255) and reports the results. The script uses a static MAC address (DE:AD:CO:DE:CA:FE) while doing so in order to prevent scope exhaustion. The script reads the response using pcap by opening a listening pcap socket on all available ethernet interfaces that are reported up. If no response has been received before the timeout has been reached (default 10 seconds) the script will abort execution. The script needs to be run as a privileged user, typically root. ]] --- -- @see broadcast-dhcp6-discover.nse -- @see dhcp-discover.nse -- -- @usage -- sudo nmap --script broadcast-dhcp-discover -- -- @output -- | broadcast-dhcp-discover: -- | Response 1 of 1: -- | Interface: wlp1s0 -- | IP Offered: 192.168.1.114 -- | DHCP Message Type: DHCPOFFER -- | 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: 192.168.1.1 -- |_ Domain Name: localdomain -- -- @xmloutput --
random
or a specific
-- client MAC address in the DHCP request. "DE:AD:C0:DE:CA:FE"
-- is used by default. Setting it to random
will
-- possibly cause the DHCP server to reserve a new IP address
-- each time.
-- @args broadcast-dhcp-discover.timeout time in seconds to wait for a response
-- (default: 10s)
--
-- Created 01/14/2020 - v0.2 - updated by nnposter
-- o Implemented script argument "mac" to force a specific MAC address
--
-- Created 07/14/2011 - v0.1 - created by Patrik Karlsson
author = "Patrik Karlsson"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"broadcast", "safe"}
prerule = function()
if not nmap.is_privileged() then
stdnse.verbose1("not running for lack of privileges.")
return false
end
if nmap.address_family() ~= 'inet' then
stdnse.debug1("is IPv4 compatible only.")
return false
end
return true
end
-- Gets a list of available interfaces based on link and up filters
--
-- @param link string containing the link type to filter
-- @param up string containing the interface status to filter
-- @return result table containing the matching interfaces
local function getInterfaces(link, up)
if( not(nmap.list_interfaces) ) then return end
local interfaces, err = nmap.list_interfaces()
local result
if ( not(err) ) then
for _, iface in ipairs(interfaces) do
if ( iface.link == link and iface.up == up ) then
result = result or {}
result[iface.device] = true
end
end
end
return result
end
-- Listens for an incoming dhcp response
--
-- @param iface string with the name of the interface to listen to
-- @param timeout number of ms to wait for a response
-- @param xid the DHCP transaction id
-- @param result a table to which the result is written
local function dhcp_listener(sock, iface, timeout, xid, result)
local condvar = nmap.condvar(result)
local start_time = nmap.clock_ms()
local now = start_time
while( now - start_time < timeout ) do
sock:set_timeout(timeout - (now - start_time))
local status, _, _, data = sock:pcap_receive()
if ( status ) then
local p = packet.Packet:new( data, #data )
if ( p and p.udp_dport ) then
local data = data:sub(p.udp_offset + 9)
local status, response = dhcp.dhcp_parse(data, xid)
if ( status ) then
response.iface = iface
table.insert( result, response )
end
end
end
now = nmap.clock_ms()
end
sock:close()
condvar "signal"
end
local function fail (err) return stdnse.format_output(false, err) end
action = function()
local host, port = "255.255.255.255", 67
local timeout = stdnse.parse_timespec(stdnse.get_script_args("broadcast-dhcp-discover.timeout"))
timeout = (timeout or 10) * 1000
local macaddr = (stdnse.get_script_args(SCRIPT_NAME .. ".mac") or "DE:AD:C0:DE:CA:FE"):lower()
if macaddr:find("^ra?nd") then
macaddr = rand.random_string(6)
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
local interfaces
-- first check if the user supplied an interface
if ( nmap.get_interface() ) then
interfaces = { [nmap.get_interface()] = true }
else
-- As the response will be sent to the "offered" ip address we need
-- to use pcap to pick it up. However, we don't know what interface
-- our packet went out on, so lets get a list of all interfaces and
-- run pcap on all of them, if they're a) up and b) ethernet.
interfaces = getInterfaces("ethernet", "up")
end
if( not(interfaces) ) then return fail("Failed to retrieve interfaces (try setting one explicitly using -e)") end
local transaction_id = string.pack("