mirror of
https://github.com/nmap/nmap.git
synced 2025-12-09 06:01:28 +00:00
Many scripts were documented as using timespecs (10s, 5000ms, etc) for timeout script-args, but one 1 or 2 actually did. Now all timeout script-args will accept timespecs, except those which took a number of milliseconds, which remain unchanged. Also fixed some documentation issues (missing script name in arg description, missing nsedoc for args, etc)
214 lines
6.3 KiB
Lua
214 lines
6.3 KiB
Lua
local bin = require "bin"
|
|
local coroutine = require "coroutine"
|
|
local dhcp = require "dhcp"
|
|
local ipOps = require "ipOps"
|
|
local math = require "math"
|
|
local nmap = require "nmap"
|
|
local packet = require "packet"
|
|
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.
|
|
]]
|
|
|
|
---
|
|
-- @usage
|
|
-- sudo nmap --script broadcast-dhcp-discover
|
|
--
|
|
-- @output
|
|
-- | broadcast-dhcp-discover:
|
|
-- | 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
|
|
--
|
|
-- @args broadcast-dhcp-discover.timeout time in seconds to wait for a response
|
|
-- (default: 10s)
|
|
--
|
|
|
|
-- Version 0.1
|
|
-- Created 07/14/2011 - v0.1 - created by Patrik Karlsson
|
|
|
|
author = "Patrik Karlsson"
|
|
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
|
|
categories = {"broadcast", "safe"}
|
|
|
|
|
|
|
|
prerule = function()
|
|
if not nmap.is_privileged() then
|
|
stdnse.print_verbose("%s not running for lack of privileges.", SCRIPT_NAME)
|
|
return false
|
|
end
|
|
|
|
if nmap.address_family() ~= 'inet' then
|
|
stdnse.print_debug("%s is IPv4 compatible only.", SCRIPT_NAME)
|
|
return false
|
|
end
|
|
return true
|
|
end
|
|
|
|
-- Creates a random MAC address
|
|
--
|
|
-- @return mac_addr string containing a random MAC
|
|
local function randomizeMAC()
|
|
local mac_addr = ""
|
|
for j=1, 6 do
|
|
mac_addr = mac_addr .. string.char(math.random(1, 255))
|
|
end
|
|
return mac_addr
|
|
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, timeout, xid, result)
|
|
local condvar = nmap.condvar(result)
|
|
|
|
sock:set_timeout(100)
|
|
|
|
local start_time = nmap.clock_ms()
|
|
while( nmap.clock_ms() - start_time < timeout ) do
|
|
local status, _, _, data = sock:pcap_receive()
|
|
-- abort, once another thread has picked up our response
|
|
if ( #result > 0 ) then
|
|
sock:close()
|
|
condvar "signal"
|
|
return
|
|
end
|
|
|
|
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
|
|
table.insert( result, response )
|
|
sock:close()
|
|
condvar "signal"
|
|
return
|
|
end
|
|
end
|
|
end
|
|
end
|
|
sock:close()
|
|
condvar "signal"
|
|
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
|
|
|
|
-- randomizing the MAC could exhaust dhcp servers with small scopes
|
|
-- if ran multiple times, so we should probably refrain from doing
|
|
-- this?
|
|
local mac = string.char(0xDE,0xAD,0xC0,0xDE,0xCA,0xFE)--randomizeMAC()
|
|
|
|
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 "\n ERROR: Failed to retrieve interfaces (try setting one explicitly using -e)" end
|
|
|
|
local transaction_id = bin.pack("<I", math.random(0, 0x7FFFFFFF))
|
|
local request_type = dhcp.request_types["DHCPDISCOVER"]
|
|
local ip_address = bin.pack(">I", ipOps.todword("0.0.0.0"))
|
|
|
|
-- we nead to set the flags to broadcast
|
|
local request_options, overrides, lease_time = nil, { flags = 0x8000 }, nil
|
|
local status, packet = dhcp.dhcp_build(request_type, ip_address, mac, nil, request_options, overrides, lease_time, transaction_id)
|
|
if (not(status)) then return "\n ERROR: Failed to build packet" end
|
|
|
|
local threads = {}
|
|
local result = {}
|
|
local condvar = nmap.condvar(result)
|
|
|
|
-- start a listening thread for each interface
|
|
for iface, _ in pairs(interfaces) do
|
|
local sock, co
|
|
sock = nmap.new_socket()
|
|
sock:pcap_open(iface, 1500, false, "ip && udp && port 68")
|
|
co = stdnse.new_thread( dhcp_listener, sock, timeout, transaction_id, result )
|
|
threads[co] = true
|
|
end
|
|
|
|
local socket = nmap.new_socket("udp")
|
|
socket:bind(nil, 68)
|
|
socket:sendto( host, port, packet )
|
|
socket:close()
|
|
|
|
-- wait until all threads are done
|
|
repeat
|
|
for thread in pairs(threads) do
|
|
if coroutine.status(thread) == "dead" then threads[thread] = nil end
|
|
end
|
|
if ( next(threads) ) then
|
|
condvar "wait"
|
|
end
|
|
until next(threads) == nil
|
|
|
|
local response = {}
|
|
-- Display the results
|
|
for i, r in ipairs(result) do
|
|
table.insert(response, string.format("IP Offered: %s", r.yiaddr_str))
|
|
for _, v in ipairs(r.options) do
|
|
if(type(v['value']) == 'table') then
|
|
table.insert(response, string.format("%s: %s", v['name'], stdnse.strjoin(", ", v['value'])))
|
|
else
|
|
table.insert(response, string.format("%s: %s\n", v['name'], v['value']))
|
|
end
|
|
end
|
|
end
|
|
return stdnse.format_output(true, response)
|
|
end
|