ID NMAP:LU-ENUM.NSE Type nmap Reporter Philip Young aka Soldier of Fortran Modified 2019-03-21T04:15:20
Description
Attempts to enumerate Logical Units (LU) of TN3270E servers.
When connecting to a TN3270E server you are assigned a Logical Unit (LU) or you can tell the TN3270E server which LU you'd like to use. Typically TN3270E servers are configured to give you an LU from a pool of LUs. They can also have LUs set to take you to a specific application. This script attempts to guess valid LUs that bypass the default LUs you are assigned. For example, if a TN3270E server sends you straight to TPX you could use this script to find LUs that take you to TSO, CICS, etc.
Script Arguments
lu-enum.path
Folder used to store valid logical unit 'screenshots' Defaults to None and doesn't store anything. This stores all valid logical units.
lulist
Path to list of Logical Units to test. Defaults the initial Logical Unit TN3270E provides, replacing the last two characters with 00-99.
PORT STATE SERVICE REASON VERSION
23/tcp open tn3270 syn-ack IBM Telnet TN3270 (TN3270E)
| lu-enum:
| Logical Units:
| LU:BSLVLU69 - Valid credentials
|_ Statistics: Performed 7 guesses in 7 seconds, average tps: 1.0
Requires
stdnse
shortport
tn3270
brute
creds
unpwdb
io
nmap
string
stringaux
table
local stdnse = require "stdnse"
local shortport = require "shortport"
local tn3270 = require "tn3270"
local brute = require "brute"
local creds = require "creds"
local unpwdb = require "unpwdb"
local io = require "io"
local nmap = require "nmap"
local string = require "string"
local stringaux = require "stringaux"
local table = require "table"
description = [[
Attempts to enumerate Logical Units (LU) of TN3270E servers.
When connecting to a TN3270E server you are assigned a Logical Unit (LU) or you can tell
the TN3270E server which LU you'd like to use. Typically TN3270E servers are configured to
give you an LU from a pool of LUs. They can also have LUs set to take you to a specific
application. This script attempts to guess valid LUs that bypass the default LUs you are
assigned. For example, if a TN3270E server sends you straight to TPX you could use this
script to find LUs that take you to TSO, CICS, etc.
]]
---
--@args lulist Path to list of Logical Units to test.
-- Defaults the initial Logical Unit TN3270E provides, replacing the
-- last two characters with <code>00-99</code>.
--@args lu-enum.path Folder used to store valid logical unit 'screenshots'
-- Defaults to <code>None</code> and doesn't store anything. This stores
-- all valid logical units.
--@usage
-- nmap --script lu-enum -p 23 <targets>
--
--@usage
-- nmap --script lu-enum --script-args lulist=lus.txt,
-- lu-enum.path="/home/dade/screenshots/" -p 23 -sV <targets>
--
--@output
-- PORT STATE SERVICE REASON VERSION
-- 23/tcp open tn3270 syn-ack IBM Telnet TN3270 (TN3270E)
-- | lu-enum:
-- | Logical Units:
-- | LU:BSLVLU69 - Valid credentials
-- |_ Statistics: Performed 7 guesses in 7 seconds, average tps: 1.0
--
-- @changelog
-- 2019-02-04 - v0.1 - created by Soldier of Fortran
author = "Philip Young aka Soldier of Fortran"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"intrusive", "brute"}
portrule = shortport.port_or_service({23,992}, "tn3270")
--- Saves the TN3270E terminal screen to disk
--
-- @param filename string containing the name and full path to the file
-- @param data contains the data
-- @return status true on success, false on failure
-- @return err string containing error message if status is false
local function save_screens( filename, data )
local f = io.open( filename, "w")
if not f then return false, ("Failed to open file (%s)"):format(filename) end
if not(f:write(data)) then return false, ("Failed to write file (%s)"):format(filename) end
f:close()
return true
end
--- Compares two screens and returns the difference as a percentage
--
-- @param1 the original screen
-- @param2 the screen to compare to
local function screen_diff( orig_screen, current_screen )
if orig_screen == current_screen then return 100 end
if #orig_screen == 0 or #current_screen == 0 then return 0 end
local m = 1
for i = 1 , #orig_screen do
if orig_screen:byte(i) == current_screen:byte(i) then
m = m + 1
end
end
return (m/1920)*100
end
Driver = {
new = function(self, host, port, options)
local o = {}
setmetatable(o, self)
self.__index = self
o.host = host
o.port = port
o.options = options
o.tn3270 = tn3270.Telnet:new()
return o
end,
connect = function( self )
return true
end,
disconnect = function( self )
self.tn3270:disconnect()
self.tn3270 = nil
end,
login = function (self, user, pass) -- pass is actually the username we want to try
local path = self.options['path']
local original = self.options['no_lu']
local threshold = 90
stdnse.verbose(2,"Trying Logical Unit: %s", pass)
self.tn3270:set_lu(pass)
local status, err = self.tn3270:initiate(self.host,self.port)
if not status then
stdnse.debug(2,"Could not initiate TN3270: %s", err )
stdnse.verbose(2, "Invalid LU: %s",string.upper(pass))
return false, brute.Error:new( "Invalid Logical Unit" )
end
self.tn3270:get_all_data()
self.tn3270:get_screen_debug(2)
if path ~= nil then
stdnse.verbose(2,"Writting screen to: %s", path..string.upper(pass)..".txt")
local status, err = save_screens(path..string.upper(pass)..".txt",self.tn3270:get_screen())
if not status then
stdnse.verbose(2,"Failed writting screen to: %s", path..string.upper(pass)..".txt")
end
end
stdnse.debug(3, "compare results: %s ", tostring(screen_diff(original, self.tn3270:get_screen_raw())))
if screen_diff(original, self.tn3270:get_screen_raw()) > threshold then
stdnse.verbose(2,'Same Screen for LU: %s',string.upper(pass))
return false, brute.Error:new( "Invalid Logical Unit" )
else
stdnse.verbose(2,"Valid Logical Unit: %s",string.upper(pass))
return true, creds.Account:new("LU", string.upper(pass), creds.State.VALID)
end
end
}
--- Tests the target to see if we can connect with TN3270E
--
-- @param host host NSE object
-- @param port port NSE object
-- @return status true on success, false on failure
local function lu_test( host, port )
local tn = tn3270.Telnet:new()
local status, err = tn:initiate(host,port)
if not status then
stdnse.debug(1,"[lu_test] Could not initiate TN3270: %s", err )
return false
end
stdnse.debug(2,"[lu_test] Displaying initial TN3270 Screen:")
tn:get_screen_debug(2) -- prints TN3270 screen to debug
if tn.state == tn.TN3270E_DATA then -- Could make a function in the library 'istn3270e'
stdnse.debug(1,"[lu_test] Orig screen: %s", tn:get_screen_raw())
return true, tn:get_lu(), tn:get_screen_raw()
else
return false, 'Not in TN3270E Mode. LU not supported.', ''
end
end
-- Checks if it's a valid Logical Unit name
local valid_lu = function(x)
return (string.len(x) <= 8 and string.match(x,"[%w@#%$]"))
end
-- iterator function
function iter(t)
local i, val
return function()
i, val = next(t, i)
return val
end
end
action = function(host, port)
local lu_id_file = stdnse.get_script_args("lulist")
local path = stdnse.get_script_args(SCRIPT_NAME .. '.path') -- Folder for screen grabs
local logical_units = {}
lu_id_file = ((lu_id_file and nmap.fetchfile(lu_id_file)) or lu_id_file)
local status, lu, orig_screen = lu_test( host, port )
if status then
if not lu_id_file then
-- we have to do this here because we don't have an LU to use for the template until now
stdnse.debug(3, "No LU list provided, auto generating a list using template: %s##", lu:sub(1, (#lu-2)))
for i=1,99 do
table.insert(logical_units, lu:sub(1, (#lu-2)) .. string.format("%02d", i))
end
else
for l in io.lines(lu_id_file) do
local cleaned_line = string.gsub(l,"[\r\n]","")
if not cleaned_line:match("#!comment:") then
table.insert(logical_units, cleaned_line)
end
end
end
-- Make sure we pass the original screen we got to the brute
local options = { no_lu = orig_screen, path = path }
if path ~= nil then stdnse.verbose(2,"Saving Screenshots to: %s", path) end
local engine = brute.Engine:new(Driver, host, port, options)
engine.options.script_name = SCRIPT_NAME
engine:setPasswordIterator(unpwdb.filter_iterator(iter(logical_units), valid_lu))
engine.options.passonly = true
engine.options:setTitle("Logical Units")
local status, result = engine:start()
return result
else
stdnse.debug(1,"Not in TN3270E mode, LU not supported.")
return lu
end
end
{"id": "NMAP:LU-ENUM.NSE", "bulletinFamily": "scanner", "title": "lu-enum NSE Script", "description": "Attempts to enumerate Logical Units (LU) of TN3270E servers. \n\nWhen connecting to a TN3270E server you are assigned a Logical Unit (LU) or you can tell the TN3270E server which LU you'd like to use. Typically TN3270E servers are configured to give you an LU from a pool of LUs. They can also have LUs set to take you to a specific application. This script attempts to guess valid LUs that bypass the default LUs you are assigned. For example, if a TN3270E server sends you straight to TPX you could use this script to find LUs that take you to TSO, CICS, etc.\n\n## Script Arguments \n\n#### lu-enum.path \n\nFolder used to store valid logical unit 'screenshots' Defaults to `None` and doesn't store anything. This stores all valid logical units.\n\n#### lulist \n\nPath to list of Logical Units to test. Defaults the initial Logical Unit TN3270E provides, replacing the last two characters with `00-99`.\n\n#### brute.credfile, brute.delay, brute.emptypass, brute.firstonly, brute.guesses, brute.mode, brute.passonly, brute.retries, brute.start, brute.threads, brute.unique, brute.useraspass \n\nSee the documentation for the brute library. \n\n#### creds.[service], creds.global \n\nSee the documentation for the creds library. \n\n#### passdb, unpwdb.passlimit, unpwdb.timelimit, unpwdb.userlimit, userdb \n\nSee the documentation for the unpwdb library. \n\n## Example Usage \n\n * nmap --script lu-enum -p 23 <targets>\n \n\n * nmap --script lu-enum --script-args lulist=lus.txt,\n lu-enum.path=\"/home/dade/screenshots/\" -p 23 -sV <targets>\n \n\n## Script Output \n \n \n PORT STATE SERVICE REASON VERSION\n 23/tcp open tn3270 syn-ack IBM Telnet TN3270 (TN3270E)\n | lu-enum: \n | Logical Units: \n | LU:BSLVLU69 - Valid credentials\n |_ Statistics: Performed 7 guesses in 7 seconds, average tps: 1.0\n \n\n## Requires \n\n * stdnse\n * shortport\n * tn3270\n * brute\n * creds\n * unpwdb\n * io\n * nmap\n * string\n * stringaux\n * table\n\n* * *\n", "published": "2019-03-21T04:15:20", "modified": "2019-03-21T04:15:20", "cvss": {"score": 0.0, "vector": "NONE"}, "href": "https://nmap.org/nsedoc/scripts/lu-enum.html", "reporter": "Philip Young aka Soldier of Fortran", "references": [], "cvelist": [], "type": "nmap", "lastseen": "2019-03-31T17:00:12", "edition": 1, "viewCount": 13, "enchantments": {"score": {"value": -0.4, "vector": "NONE", "modified": "2019-03-31T17:00:12", "rev": 2}, "dependencies": {"references": [], "modified": "2019-03-31T17:00:12", "rev": 2}, "vulnersScore": -0.4}, "sourceData": "local stdnse = require \"stdnse\"\nlocal shortport = require \"shortport\"\nlocal tn3270 = require \"tn3270\"\nlocal brute = require \"brute\"\nlocal creds = require \"creds\"\nlocal unpwdb = require \"unpwdb\"\nlocal io = require \"io\"\nlocal nmap = require \"nmap\"\nlocal string = require \"string\"\nlocal stringaux = require \"stringaux\"\nlocal table = require \"table\"\n\ndescription = [[\nAttempts to enumerate Logical Units (LU) of TN3270E servers.\n\nWhen connecting to a TN3270E server you are assigned a Logical Unit (LU) or you can tell\nthe TN3270E server which LU you'd like to use. Typically TN3270E servers are configured to \ngive you an LU from a pool of LUs. They can also have LUs set to take you to a specific\napplication. This script attempts to guess valid LUs that bypass the default LUs you are\nassigned. For example, if a TN3270E server sends you straight to TPX you could use this\nscript to find LUs that take you to TSO, CICS, etc.\n]]\n\n---\n--@args lulist Path to list of Logical Units to test.\n-- Defaults the initial Logical Unit TN3270E provides, replacing the \n-- last two characters with <code>00-99</code>.\n--@args lu-enum.path Folder used to store valid logical unit 'screenshots'\n-- Defaults to <code>None</code> and doesn't store anything. This stores \n-- all valid logical units.\n--@usage\n-- nmap --script lu-enum -p 23 <targets>\n--\n--@usage\n-- nmap --script lu-enum --script-args lulist=lus.txt,\n-- lu-enum.path=\"/home/dade/screenshots/\" -p 23 -sV <targets>\n--\n--@output\n-- PORT STATE SERVICE REASON VERSION\n-- 23/tcp open tn3270 syn-ack IBM Telnet TN3270 (TN3270E)\n-- | lu-enum: \n-- | Logical Units: \n-- | LU:BSLVLU69 - Valid credentials\n-- |_ Statistics: Performed 7 guesses in 7 seconds, average tps: 1.0\n-- \n-- @changelog\n-- 2019-02-04 - v0.1 - created by Soldier of Fortran\n\nauthor = \"Philip Young aka Soldier of Fortran\"\nlicense = \"Same as Nmap--See https://nmap.org/book/man-legal.html\"\ncategories = {\"intrusive\", \"brute\"}\n\nportrule = shortport.port_or_service({23,992}, \"tn3270\")\n\n--- Saves the TN3270E terminal screen to disk\n--\n-- @param filename string containing the name and full path to the file\n-- @param data contains the data\n-- @return status true on success, false on failure\n-- @return err string containing error message if status is false\nlocal function save_screens( filename, data )\n local f = io.open( filename, \"w\")\n if not f then return false, (\"Failed to open file (%s)\"):format(filename) end\n if not(f:write(data)) then return false, (\"Failed to write file (%s)\"):format(filename) end\n f:close()\n return true\nend\n\n--- Compares two screens and returns the difference as a percentage\n--\n-- @param1 the original screen\n-- @param2 the screen to compare to\nlocal function screen_diff( orig_screen, current_screen )\n if orig_screen == current_screen then return 100 end\n if #orig_screen == 0 or #current_screen == 0 then return 0 end\n local m = 1\n for i = 1 , #orig_screen do\n if orig_screen:byte(i) == current_screen:byte(i) then\n m = m + 1\n end\n end\n return (m/1920)*100\nend\n\nDriver = {\n new = function(self, host, port, options)\n local o = {}\n setmetatable(o, self)\n self.__index = self\n o.host = host\n o.port = port\n o.options = options\n o.tn3270 = tn3270.Telnet:new()\n return o\n end,\n connect = function( self )\n return true\n end,\n disconnect = function( self )\n self.tn3270:disconnect()\n self.tn3270 = nil\n end,\n login = function (self, user, pass) -- pass is actually the username we want to try\n local path = self.options['path']\n local original = self.options['no_lu']\n local threshold = 90\n stdnse.verbose(2,\"Trying Logical Unit: %s\", pass)\n self.tn3270:set_lu(pass)\n local status, err = self.tn3270:initiate(self.host,self.port)\n if not status then\n stdnse.debug(2,\"Could not initiate TN3270: %s\", err )\n stdnse.verbose(2, \"Invalid LU: %s\",string.upper(pass))\n return false, brute.Error:new( \"Invalid Logical Unit\" )\n end\n self.tn3270:get_all_data()\n self.tn3270:get_screen_debug(2)\n if path ~= nil then\n stdnse.verbose(2,\"Writting screen to: %s\", path..string.upper(pass)..\".txt\")\n local status, err = save_screens(path..string.upper(pass)..\".txt\",self.tn3270:get_screen())\n if not status then\n stdnse.verbose(2,\"Failed writting screen to: %s\", path..string.upper(pass)..\".txt\")\n end\n end\n\n stdnse.debug(3, \"compare results: %s \", tostring(screen_diff(original, self.tn3270:get_screen_raw())))\n if screen_diff(original, self.tn3270:get_screen_raw()) > threshold then\n stdnse.verbose(2,'Same Screen for LU: %s',string.upper(pass))\n return false, brute.Error:new( \"Invalid Logical Unit\" )\n else\n stdnse.verbose(2,\"Valid Logical Unit: %s\",string.upper(pass))\n return true, creds.Account:new(\"LU\", string.upper(pass), creds.State.VALID)\n end\n end\n}\n\n--- Tests the target to see if we can connect with TN3270E\n--\n-- @param host host NSE object\n-- @param port port NSE object\n-- @return status true on success, false on failure\nlocal function lu_test( host, port )\n local tn = tn3270.Telnet:new()\n local status, err = tn:initiate(host,port)\n \n if not status then\n stdnse.debug(1,\"[lu_test] Could not initiate TN3270: %s\", err )\n return false\n end\n\n stdnse.debug(2,\"[lu_test] Displaying initial TN3270 Screen:\")\n tn:get_screen_debug(2) -- prints TN3270 screen to debug\n if tn.state == tn.TN3270E_DATA then -- Could make a function in the library 'istn3270e'\n stdnse.debug(1,\"[lu_test] Orig screen: %s\", tn:get_screen_raw())\n return true, tn:get_lu(), tn:get_screen_raw()\n else \n return false, 'Not in TN3270E Mode. LU not supported.', ''\n end\n\nend\n\n-- Checks if it's a valid Logical Unit name\nlocal valid_lu = function(x)\n return (string.len(x) <= 8 and string.match(x,\"[%w@#%$]\"))\nend\n\n-- iterator function\nfunction iter(t)\n local i, val\n return function()\n i, val = next(t, i)\n return val\n end\nend\n\naction = function(host, port)\n local lu_id_file = stdnse.get_script_args(\"lulist\")\n local path = stdnse.get_script_args(SCRIPT_NAME .. '.path') -- Folder for screen grabs\n local logical_units = {}\n lu_id_file = ((lu_id_file and nmap.fetchfile(lu_id_file)) or lu_id_file) \n\n local status, lu, orig_screen = lu_test( host, port )\n if status then\n \n \n if not lu_id_file then\n -- we have to do this here because we don't have an LU to use for the template until now\n stdnse.debug(3, \"No LU list provided, auto generating a list using template: %s##\", lu:sub(1, (#lu-2)))\n for i=1,99 do\n table.insert(logical_units, lu:sub(1, (#lu-2)) .. string.format(\"%02d\", i))\n end\n else \n for l in io.lines(lu_id_file) do\n local cleaned_line = string.gsub(l,\"[\\r\\n]\",\"\")\n if not cleaned_line:match(\"#!comment:\") then\n table.insert(logical_units, cleaned_line)\n end\n end\n end\n \n \n -- Make sure we pass the original screen we got to the brute \n local options = { no_lu = orig_screen, path = path }\n if path ~= nil then stdnse.verbose(2,\"Saving Screenshots to: %s\", path) end\n local engine = brute.Engine:new(Driver, host, port, options)\n engine.options.script_name = SCRIPT_NAME\n engine:setPasswordIterator(unpwdb.filter_iterator(iter(logical_units), valid_lu))\n engine.options.passonly = true\n engine.options:setTitle(\"Logical Units\")\n local status, result = engine:start()\n return result\n else\n stdnse.debug(1,\"Not in TN3270E mode, LU not supported.\")\n return lu\n end\n\nend\n", "nmap": {"categories": ["brute", "intrusive"], "scriptType": "portrule"}}