summaryrefslogtreecommitdiffstats
path: root/scripts/tftp-enum.nse
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/tftp-enum.nse')
-rw-r--r--scripts/tftp-enum.nse212
1 files changed, 212 insertions, 0 deletions
diff --git a/scripts/tftp-enum.nse b/scripts/tftp-enum.nse
new file mode 100644
index 0000000..831cbad
--- /dev/null
+++ b/scripts/tftp-enum.nse
@@ -0,0 +1,212 @@
+local datafiles = require "datafiles"
+local nmap = require "nmap"
+local shortport = require "shortport"
+local stdnse = require "stdnse"
+local string = require "string"
+local stringaux = require "stringaux"
+local table = require "table"
+local rand = require "rand"
+
+description = [[
+Enumerates TFTP (trivial file transfer protocol) filenames by testing
+for a list of common ones.
+
+TFTP doesn't provide directory listings. This script tries to retrieve
+filenames from a list. The list is composed of static names from the
+file <code>tftplist.txt</code>, plus configuration filenames for Cisco
+devices that change based on the target address, of the form
+<code>A.B.C.X-confg</code> for an IP address A.B.C.D and for X in 0 to
+255.
+
+Use the <code>tftp-enum.filelist</code> script argument to search for
+other static filenames.
+
+This script is a reimplementation of tftptheft from
+http://code.google.com/p/tftptheft/.
+]]
+
+---
+-- @usage nmap -sU -p 69 --script tftp-enum.nse --script-args tftp-enum.filelist=customlist.txt <host>
+--
+-- @args filelist - file name with list of filenames to enumerate at tftp server
+--
+-- @output
+-- PORT STATE SERVICE REASON
+-- 69/udp open tftp script-set
+-- | tftp-enum:
+-- |_ bootrom.ld
+
+author = "Alexander Rudakov"
+license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
+categories = { "discovery", "intrusive" }
+
+
+local REQUEST_ERROR = -1
+local FILE_FOUND = 1
+local FILE_NOT_FOUND = 2
+
+portrule = shortport.portnumber(69, "udp")
+
+-- return a new array containing the concatenation of all of its
+-- parameters. Scaler parameters are included in place, and array
+-- parameters have their values shallow-copied to the final array.
+-- Note that userdata and function values are treated as scalar.
+local function array_concat(...)
+ local t = {}
+ for n = 1, select("#", ...) do
+ local arg = select(n, ...)
+ if type(arg) == "table" then
+ for _, v in ipairs(arg) do
+ t[#t + 1] = v
+ end
+ else
+ t[#t + 1] = arg
+ end
+ end
+ return t
+end
+
+local generate_cisco_address_confg = function(base_address)
+ local filenames = {}
+ local octets = stringaux.strsplit("%.", base_address)
+
+ for i = 0, 255 do
+ local address_confg = octets[1] .. "." .. octets[2] .. "." .. octets[3] .. "." .. i .. "-confg"
+ table.insert(filenames, address_confg)
+ end
+
+ return filenames
+end
+
+local generate_filenames = function(host)
+ local customlist = stdnse.get_script_args('tftp-enum.filelist')
+ local cisco = false
+ local status, default_filenames = datafiles.parse_file(customlist or "nselib/data/tftplist.txt" , {})
+ if not status then
+ stdnse.debug1("Can not open file with tftp file names list")
+ return {}
+ else
+
+ for i, filename in ipairs(default_filenames) do
+ if filename:match('{[Mm][Aa][Cc]}') then
+ if not host.mac_addr then
+ goto next_filename
+ else
+ filename = filename:gsub('{M[Aa][Cc]}', string.upper(stdnse.tohex(host.mac_addr)))
+ filename = filename:gsub('{m[aA][cC]}', stdnse.tohex(host.mac_addr))
+ end
+ end
+
+ if filename:match('{cisco}') then
+ cisco = true
+ table.remove(default_filenames,i)
+ end
+ ::next_filename::
+ end
+
+ if cisco == true then
+ local cisco_address_confg_filenames = generate_cisco_address_confg(host.ip)
+ return array_concat(default_filenames, cisco_address_confg_filenames)
+ end
+ end
+ return default_filenames
+end
+
+
+local create_tftp_file_request = function(filename)
+ return "\0\x01" .. filename .. "\0octet\0"
+end
+
+local check_file_present = function(host, port, filename)
+ stdnse.debug1("checking file %s", filename)
+
+ local file_request = create_tftp_file_request(filename)
+
+
+ local socket = nmap.new_socket()
+ socket:connect(host, port)
+ local status, lhost, lport, rhost, rport = socket:get_info()
+ stdnse.debug1("lhost: %s, lport: %s", lhost, lport);
+
+
+ if (not (status)) then
+ stdnse.debug1("error %s", lhost)
+ socket:close()
+ return REQUEST_ERROR
+ end
+
+
+ local bind_socket = nmap.new_socket("udp")
+ stdnse.debug1("local port = %d", lport)
+
+ socket:send(file_request)
+ socket:close()
+
+ local bindOK, error = bind_socket:bind(nil, lport)
+
+
+ stdnse.debug1("starting listener")
+
+ if (not (bindOK)) then
+ stdnse.debug1("Error in bind %s", error)
+ bind_socket:close()
+ return REQUEST_ERROR
+ end
+
+
+ local recvOK, data = bind_socket:receive()
+
+ if (not (recvOK)) then
+ stdnse.debug1("Error in receive %s", data)
+ bind_socket:close()
+ return REQUEST_ERROR
+ end
+
+ if (data:byte(1) == 0x00 and data:byte(2) == 0x03) then
+ bind_socket:close()
+ return FILE_FOUND
+ elseif (data:byte(1) == 0x00 and data:byte(2) == 0x05) then
+ bind_socket:close()
+ return FILE_NOT_FOUND
+ else
+ bind_socket:close()
+ return REQUEST_ERROR
+ end
+
+ return FILE_NOT_FOUND
+end
+
+local check_open_tftp = function(host, port)
+ local random_name = rand.random_string(8, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_")
+ local ret_value = check_file_present(host, port, random_name)
+ if (ret_value == FILE_FOUND or ret_value == FILE_NOT_FOUND) then
+ return true
+ else
+ return false
+ end
+end
+
+action = function(host, port)
+
+ if (not (check_open_tftp(host, port))) then
+ stdnse.debug1("tftp seems not active")
+ return
+ end
+
+ stdnse.debug1("tftp detected")
+
+ port.service = "tftp"
+ nmap.set_port_state(host, port, "open")
+
+ local results = {}
+ local filenames = generate_filenames(host)
+
+ for i, filename in ipairs(filenames) do
+ local request_status = check_file_present(host, port, filename)
+ if (request_status == FILE_FOUND) then
+ table.insert(results, filename)
+ end
+ end
+
+ return stdnse.format_output(true, results)
+end