summaryrefslogtreecommitdiffstats
path: root/scripts/vtam-enum.nse
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/vtam-enum.nse')
-rw-r--r--scripts/vtam-enum.nse277
1 files changed, 277 insertions, 0 deletions
diff --git a/scripts/vtam-enum.nse b/scripts/vtam-enum.nse
new file mode 100644
index 0000000..edf3cf8
--- /dev/null
+++ b/scripts/vtam-enum.nse
@@ -0,0 +1,277 @@
+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 = [[
+Many mainframes use VTAM screens to connect to various applications
+(CICS, IMS, TSO, and many more).
+
+This script attempts to brute force those VTAM application IDs.
+
+This script is based on mainframe_brute by Dominic White
+(https://github.com/sensepost/mainframe_brute). However, this script
+doesn't rely on any third party libraries or tools and instead uses
+the NSE TN3270 library which emulates a TN3270 screen in lua.
+
+Application IDs only allows for 8 byte IDs, that is the only specific rule
+found for application IDs.
+]]
+
+---
+--@args idlist Path to list of application IDs to test.
+-- Defaults to <code>nselib/data/vhosts-default.lst</code>.
+--@args vtam-enum.commands Commands in a semi-colon separated list needed
+-- to access VTAM. Defaults to <code>nothing</code>.
+--@args vtam-enum.path Folder used to store valid transaction id 'screenshots'
+-- Defaults to <code>None</code> and doesn't store anything.
+--@args vtam-enum.macros When set to true does not prepend the application ID
+-- with 'logon applid()'. Default is <code>false</code>.
+--
+--@usage
+-- nmap --script vtam-enum -p 23 <targets>
+--
+-- nmap --script vtam-enum --script-args idlist=defaults.txt,
+-- vtam-enum.command="exit;logon applid(logos)",vtam-enum.macros=true
+-- vtam-enum.path="/home/dade/screenshots/" -p 23 -sV <targets>
+--
+--@output
+-- PORT STATE SERVICE VERSION
+-- 23/tcp open tn3270 IBM Telnet TN3270
+-- | vtam-enum:
+-- | VTAM Application ID:
+-- | applid:TSO - Valid credentials
+-- | applid:CICSTS51 - Valid credentials
+-- |_ Statistics: Performed 14 guesses in 5 seconds, average tps: 2
+--
+-- @changelog
+-- 2015-07-04 - v0.1 - created by Soldier of Fortran
+-- 2015-11-04 - v0.2 - significant upgrades and speed increases
+-- 2015-11-14 - v0.3 - rewrote iterator
+-- 2017-01-13 - v0.4 - Fixed 'macros' bug with default vtam screen and test
+-- and added threshold for macros screen checking
+-- 2019-02-01 - v0.5 - Disabling Enhanced mode
+
+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 Screen generated by the VTAM command 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()
+ o.tn3270:disable_tn3270e()
+ return o
+ end,
+ connect = function( self )
+ local status, err = self.tn3270:initiate(self.host,self.port)
+ if not status then
+ stdnse.debug2("Could not initiate TN3270: %s", err )
+ return false
+ end
+ 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['key2']
+ local macros = self.options['key3']
+ local cmdfmt = "logon applid(%s)"
+ local type = "applid"
+ local threshold = 75
+ -- instead of sending 'logon applid(<appname>)' when macros=true
+ -- we try to logon with just the command
+ if macros then
+ cmdfmt = "%s"
+ type ="macro"
+ threshold = 90 -- sometimes the screen barely changes
+ end
+ stdnse.verbose(2,"Trying VTAM ID: %s", pass)
+
+ local previous_screen = self.tn3270:get_screen_raw()
+ self.tn3270:send_cursor(cmdfmt:format(pass))
+ self.tn3270:get_all_data()
+ self.tn3270:get_screen_debug(2)
+ local current_screen = self.tn3270:get_screen_raw()
+
+ if (self.tn3270:find('UNABLE TO ESTABLISH SESSION') or -- thanks goes to Dominic White for creating these
+ self.tn3270:find('COMMAND UNRECOGNI[SZ]ED') or
+ self.tn3270:find('USSMSG0[1-4]') or
+ self.tn3270:find('SESSION NOT BOUND') or
+ self.tn3270:find('INVALID COMMAND') or
+ self.tn3270:find('PARAMETER OMITTED') or
+ self.tn3270:find('REQUERIDO PARAMETRO PERDIDO') or
+ self.tn3270:find('Your command is unrecognized') or
+ self.tn3270:find('invalid command or syntax') or
+ self.tn3270:find('UNSUPPORTED FUNCTION') or
+ self.tn3270:find('REQSESS error') or
+ self.tn3270:find('syntax invalid') or
+ self.tn3270:find('INVALID SYSTEM') or
+ self.tn3270:find('NOT VALID') or
+ self.tn3270:find('INVALID USERID, APPLID') ) or
+ self.tn3270:find('UNABLE TO CONNECT TO THE REQUESTED APPLICATION') or
+ screen_diff(previous_screen, current_screen) > threshold then
+ -- Looks like an invalid APPLID.
+ stdnse.verbose(2,'Invalid Application ID: %s',string.upper(pass))
+ return false, brute.Error:new( "Invalid VTAM Application ID" )
+ else
+ stdnse.verbose(2,"Valid Application ID: %s",string.upper(pass))
+ 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
+ return true, creds.Account:new(type,string.upper(pass), creds.State.VALID)
+ end
+ end
+}
+
+--- Tests the target to see if we can use logon applid(<id>) for enumeration
+--
+-- @param host host NSE object
+-- @param port port NSE object
+-- @param commands optional script-args of commands to use to get to VTAM
+-- @return status true on success, false on failure
+local function vtam_test( host, port, commands, macros)
+ local tn = tn3270.Telnet:new()
+ tn:disable_tn3270e()
+ local status, err = tn:initiate(host,port)
+ stdnse.debug1("Testing if VTAM and 'logon applid' command supported")
+ stdnse.debug2("Connecting TN3270 to %s:%s", host.targetname or host.ip, port.number)
+
+ if not status then
+ stdnse.debug1("Could not initiate TN3270: %s", err )
+ return false
+ end
+
+ stdnse.debug2("Displaying initial TN3270 Screen:")
+ tn:get_screen_debug(2) -- prints TN3270 screen to debug
+
+ if commands ~= nil then
+ local run = stringaux.strsplit(";%s*", commands)
+ for i = 1, #run do
+ stdnse.debug(2,"Issuing Command (#%s of %s) or %s", i, #run ,run[i])
+ tn:send_cursor(run[i])
+ tn:get_screen_debug(2)
+ end
+ end
+ stdnse.debug2("Sending VTAM command: IBMTEST")
+ tn:send_cursor('IBMTEST')
+ tn:get_all_data()
+ tn:get_screen_debug(2)
+ local isVTAM = false
+ if not macros and tn:find('IBMECHO ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789') then
+ stdnse.debug2("IBMTEST Returned: IBMECHO ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.")
+ stdnse.debug1("VTAM Test Success!")
+ isVTAM = true
+ elseif macros then
+ isVTAM = true
+ end
+
+ if not macros then
+ -- now testing if we can send 'logon applid(<id>)'
+ -- certain systems interpret 'logon' as the tso logon
+ tn:send_cursor('LOGON APPLID(FAKE)')
+ tn:get_all_data()
+ tn:get_screen_debug(2)
+ if tn:find('INVALID USERID') then
+ isVTAM = false
+ end
+ tn:disconnect()
+ end
+ return isVTAM
+end
+
+-- Checks if it's a valid VTAM name
+local valid_vtam = function(x)
+ return (string.len(x) <= 8 and string.match(x,"[%w@#%$]"))
+end
+
+function iter(t)
+ local i, val
+ return function()
+ i, val = next(t, i)
+ return val
+ end
+end
+
+action = function(host, port)
+ local vtam_id_file = stdnse.get_script_args("idlist")
+ local path = stdnse.get_script_args(SCRIPT_NAME .. '.path') -- Folder for screen grabs
+ local macros = stdnse.get_script_args(SCRIPT_NAME .. '.macros') or false -- if set to true, doesn't prepend the commands with 'logon applid'
+ local commands = stdnse.get_script_args(SCRIPT_NAME .. '.commands') -- Commands to send to get to VTAM
+ local vtam_ids = {"tso", "CICS", "IMS", "NETVIEW", "TPX"} -- these are defaults usually seen
+ vtam_id_file = ( (vtam_id_file and nmap.fetchfile(vtam_id_file)) or vtam_id_file ) or
+ nmap.fetchfile("nselib/data/vhosts-default.lst")
+
+ for l in io.lines(vtam_id_file) do
+ local cleaned_line = string.gsub(l,"[\r\n]","")
+ if not cleaned_line:match("#!comment:") then
+ table.insert(vtam_ids, cleaned_line)
+ end
+ end
+
+ if vtam_test(host, port, commands, macros) then
+ local options = { key1 = commands, key2 = path, key3=macros }
+ stdnse.verbose("Starting VTAM Application ID Enumeration")
+ 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(vtam_ids), valid_vtam))
+ engine.options.passonly = true
+ engine.options:setTitle("VTAM Application ID")
+ local status, result = engine:start()
+ return result
+ else
+ return "Not VTAM or 'logon applid' command not accepted. Try with script arg 'vtam-enum.macros=true'"
+ end
+
+end