diff --git a/CHANGELOG b/CHANGELOG index 3a29735a2..f1e7ba6de 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,9 @@ # Nmap Changelog ($Id$); -*-text-*- +o [NSE] Removed DoS code from dhcp-discover and placed it into the discover and + safe categories. Added support for adding options to DHCP requests in the + dhcp library. [Patrik] + o [NSE] Added a telnet-encryption script which detects if a remote telnet server supports the (weak) encryption option. This is particularly interesting due to a remotely exploitable root diff --git a/nselib/dhcp.lua b/nselib/dhcp.lua index 9be8b733e..33d0169c6 100644 --- a/nselib/dhcp.lua +++ b/nselib/dhcp.lua @@ -5,6 +5,14 @@ -- which have a trivial one-function interface, can send out DHCP packets of many -- types and parse the responses. -- +-- @author "Ron Bowes" + +-- +-- 2011-12-28 - Revised by Patrik Karlsson +-- o Split dhcp_send into dhcp_send, dhcp_receive +-- o Added basic support for adding options to requests +-- o Added possibility to ovverride transaction id +-- o Added WPAD action module(... or "dhcp", package.seeall) @@ -354,23 +362,13 @@ actions[61] = {name="Client Identifier (client)", func=read_string, actions[252]= {name="WPAD", func=read_string, default=false} --- Does the send/receive, doesn't build/parse anything. -local function dhcp_send(host, packet, transaction_id) - local socket - local status, err, data - - socket = nmap.new_socket("udp") - socket:bind(nil, 68) - socket:set_timeout(5000) - - if(status == false) then - return false, "Couldn't create socket: " .. err - end - stdnse.print_debug(1, "dhcp: Created UDP socket") - +local function dhcp_send(socket, host, packet) -- Send out the packet - socket:sendto(host, { number=67, protocol="udp" }, packet) + return socket:sendto(host, { number=67, protocol="udp" }, packet) +end - -- Read the response +local function dhcp_receive(socket, transaction_id) + local status, data = socket:receive() if ( not(status) ) then socket:close() @@ -380,14 +378,10 @@ local function dhcp_send(host, packet, transaction_id) -- This pulls back 4 bytes in the packet that correspond to the transaction id. This should be randomly -- generated and different for every instance of a script (to prevent collisions) while status and data:sub(5, 8) ~= transaction_id do - local status, data = socket:receive() - end - - -- Close our sockets - socket:close() + status, data = socket:receive() + end - -- Finally, return the data - return true, data + return status, data end --- Builds a DHCP packet @@ -400,6 +394,11 @@ end --@param mac_address Your mac address (as a string up to 16 bytes) where the server will send the response. Like -- ip_address, setting to the broadcast address (FF:FF:FF:FF:FF:FF) is -- common (host.mac_addr_src works). +--@param options [optional] A table of additional request options where each option is a table containing the +-- following fields: +-- * number - The option number +-- * type - The option type ("string" or "ip") +-- * value - The option value --@param request_options [optional] The options to request from the server, as an array of integers. For the -- acceptable options, see the actions table above or have a look at rfc2132. -- Some DHCP servers (such as my Linksys WRT54g) will ignore this list and send whichever @@ -414,7 +413,7 @@ end -- --@return status (true or false) --@return The parsed response, as a table. -function dhcp_build(request_type, ip_address, mac_address, request_options, overrides, lease_time, transaction_id) +function dhcp_build(request_type, ip_address, mac_address, options, request_options, overrides, lease_time, transaction_id) local packet = '' -- Set up the default overrides @@ -438,7 +437,7 @@ function dhcp_build(request_type, ip_address, mac_address, request_options, over -- Header packet = packet .. bin.pack(">CCCC", overrides['op'] or 1, overrides['htype'] or 1, overrides['hlen'] or 6, overrides['hops'] or 0) -- BOOTREQUEST, 10mb ethernet, 6 bytes long, 0 hops - packet = packet .. transaction_id -- Transaction ID + packet = packet .. ( overrides['xid'] or transaction_id ) -- Transaction ID = packet = packet .. bin.pack(">SS", overrides['secs'] or 0, overrides['flags'] or 0x0000) -- Secs, flags packet = packet .. bin.pack("A", ip_address) -- Client address packet = packet .. bin.pack("CCC", 0x35, 1, request_type) -- Request type + + for _, option in ipairs(options or {}) do + packet = packet .. bin.pack(">C", option.number) + if ( "string" == option.type ) then + packet = packet .. bin.pack("p", option.value) + elseif( "ip" == option.type ) then + packet = packet .. bin.pack(">CI", 4, option.value) + end + end + packet = packet .. bin.pack(">CCA", 0x37, #request_options, request_options) -- Request options packet = packet .. bin.pack(">CCI", 0x33, 4, lease_time or 1) -- Lease time @@ -586,6 +595,11 @@ end --@param mac_address Your mac address (as a string up to 16 bytes) where the server will send the response. Like -- ip_address, setting to the broadcast address (FF:FF:FF:FF:FF:FF) is -- common (host.mac_addr_src works). +--@param options [optional] A table of additional request options where each option is a table containing the +-- following fields: +-- * number - The option number +-- * type - The option type ("string" or "ip") +-- * value - The option value --@param request_options [optional] The options to request from the server, as an array of integers. For the -- acceptable options, see the actions table above or have a look at rfc2132. -- Some DHCP servers (such as my Linksys WRT54g) will ignore this list and send whichever @@ -598,24 +612,36 @@ end --@param lease_time [optional] The lease time used when requestint an IP. Default: 1 second. --@return status (true or false) --@return The parsed response, as a table. -function make_request(target, request_type, ip_address, mac_address, request_options, overrides, lease_time) +function make_request(target, request_type, ip_address, mac_address, options, request_options, overrides, lease_time) -- A unique id that identifies this particular session (and lets us filter out what we don't want to see) - local transaction_id = bin.pack("I", ipOps.todword(ip_address)), mac_address, request_options, overrides, lease_time, transaction_id) + local status, packet = dhcp_build(request_type, bin.pack(">I", ipOps.todword(ip_address)), mac_address, options, request_options, overrides, lease_time, transaction_id) if(not(status)) then stdnse.print_debug(1, "dhcp: Couldn't build packet: " .. packet) return false, "Couldn't build packet: " .. packet end + local socket = nmap.new_socket("udp") + socket:bind(nil, 68) + socket:set_timeout(5000) + -- Send the packet and get the response - local status, response = dhcp_send(target, packet, transaction_id) + local status, response = dhcp_send(socket, target, packet) if(not(status)) then stdnse.print_debug(1, "dhcp: Couldn't send packet: " .. response) - return false, "Couldn't send/receive packet: " .. response + return false, "Couldn't send packet: " .. response end + status, response = dhcp_receive(socket, transaction_id) + socket:close() + + if ( not(status) ) then + stdnse.print_debug(1, "dhcp: Couldn't receive packet: " .. response) + return false, "Couldn't receive packet: " .. response + end + -- Parse the response local status, parsed = dhcp_parse(response, transaction_id) if(not(status)) then diff --git a/scripts/dhcp-discover.nse b/scripts/dhcp-discover.nse index c54698256..af265da5c 100644 --- a/scripts/dhcp-discover.nse +++ b/scripts/dhcp-discover.nse @@ -22,7 +22,7 @@ Some of the more useful fields: ]] --- --- @args dhcptype The type of DHCP request to make. By default, DHCPDISCOVER is sent, but this +-- @args 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. @@ -30,17 +30,13 @@ Some of the more useful fields: -- the request (keep in mind that you may not see the response). This should -- cause the router to reserve a new IP address each time. -- @args requests Set to an integer to make up to that many requests (and display the results). --- @args fake_requests Set to an integer to make that many fake requests before the real one(s). --- This could be useful, for example, if you also use randomize_mac --- and you want to try exhausting all addresses. -- -- @output -- Interesting ports on 192.168.1.1: -- PORT STATE SERVICE -- 67/udp open dhcps -- | dhcp-discover: --- | | IP Offered: 192.168.1.101 --- | | DHCP Message Type: DHCPOFFER +-- | | 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 @@ -48,16 +44,24 @@ Some of the more useful fields: -- |_ |_ Domain Name Server: 208.81.7.10, 208.81.7.14 -- +-- +-- 2011-12-28 - Revised by Patrik Karlsson +-- o Removed DoS code and placed script into discovery and safe categories +-- +-- 2011-12-27 - Revised by Patrik Karlsson +-- o Changed script to use DHCPINFORM instead of DHCPDISCOVER +-- + + author = "Ron Bowes" license = "Same as Nmap--See http://nmap.org/book/man-legal.html" -categories = {"discovery", "intrusive"} +categories = {"discovery", "safe"} require 'bin' require 'bit' require 'dhcp' -require 'ipOps' require 'shortport' require 'stdnse' @@ -73,34 +77,7 @@ end local function go(host, port) - -- Create fake requests if the user asked to. These are fired and forgotten, we ignore the responses. - if(nmap.registry.args.fake_requests) then - for i=1, tonumber(nmap.registry.args.fake_requests), 1 do - -- Build and send a DHCP request using the specified request type, or DHCPDISCOVER - local request_type = dhcp.request_types[nmap.registry.args.dhcptype or "DHCPDISCOVER"] - if(request_type == nil) then - return false, "Valid request types: " .. stdnse.strjoin(", ", dhcp.request_types_str) - end - - -- Generate the MAC address, if it's random (TODO: if I can enumerate interfaces, I should fall back to that instead) - local mac_addr = host.mac_addr_src - if(nmap.registry.args.randomize_mac == 'true' or nmap.registry.args.randomize_mac == '1') then - stdnse.print_debug(2, "dhcp-discover: Generating a random MAC address") - mac_addr = "" - for j=1, 6, 1 do - mac_addr = mac_addr .. string.char(math.random(1, 255)) - end - end - - local status, result = dhcp.make_request(host.ip, host.interface, request_type, "0.0.0.0", mac_addr) - if(status == false) then - stdnse.print_debug(1, "dhcp-discover: Couldn't send DHCP request: %s", result) - return false, "Couldn't send DHCP request: " .. result - end - end - end - - -- Build and send a DHCP request using the specified request type, or DHCPDISCOVER + -- Build and send a DHCP request using the specified request type, or DHCPINFORM local requests = tonumber(nmap.registry.args.requests or 1) local results = {} for i = 1, requests, 1 do @@ -142,11 +119,11 @@ action = function(host, port) local status, results = go(host, port) - if(status == false) then + if(not(status)) then return stdnse.format_output(false, results) end - if(results == nil) then + if(not(results)) then return nil end