diff --git a/CHANGELOG b/CHANGELOG
index a9e8fb8a8..e3a4c851f 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,11 @@
# Nmap Changelog ($Id$); -*-text-*-
+o [NSE] Script cics-enum enumerates CICS transaction IDs, mapping to screens in
+ TN3270 services. [Soldier of Fortran]
+
+o [NSE] Script cics-user-enum brute-forces usernames for CICS users on TN3270
+ services. [Soldier of Fortran]
+
o [NSE] Script tso-enum enumerates usernames for TN3270 Telnet services, and
tso-brute brute-forces passwords for the same services. [Soldier of Fortran]
diff --git a/scripts/cics-enum.nse b/scripts/cics-enum.nse
new file mode 100644
index 000000000..80d93b342
--- /dev/null
+++ b/scripts/cics-enum.nse
@@ -0,0 +1,325 @@
+local nmap = require "nmap"
+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 table = require "table"
+local string = require "string"
+
+
+description = [[
+CICS transaction ID enumerator for IBM mainframes.
+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.
+
+CICS only allows for 4 byte transaction IDs, that is the only specific rule
+found for CICS transaction IDs.
+]]
+
+---
+-- @args idlist Path to list of transaction IDs.
+-- Defaults to the list of CICS transactions from IBM.
+-- @args cics-enum.commands Commands in a semi-colon seperated list needed
+-- to access CICS. Defaults to CICS.
+-- @args cics-enum.path Folder used to store valid transaction id 'screenshots'
+-- Defaults to None and doesn't store anything.
+--
+-- @usage
+-- nmap --script=cics-enum -p 23
+--
+-- nmap --script=cics-enum --script-args=idlist=default_cics.txt,
+-- cics-enum.command="exit;logon applid(cics42)",
+-- cics-enum.path="/home/dade/screenshots/",cics-enum.noSSL=true -p 23
+--
+-- @output
+-- PORT STATE SERVICE
+-- 23/tcp open tn3270
+-- | cics-enum:
+-- | Accounts:
+-- | CBAM: Valid - CICS Transaction ID
+-- | CETR: Valid - CICS Transaction ID
+-- | CEST: Valid - CICS Transaction ID
+-- | CMSG: Valid - CICS Transaction ID
+-- | CEDA: Valid - CICS Transaction ID
+-- | CEDF: Potentially Valid - CICS Transaction ID
+-- | DSNC: Valid - CICS Transaction ID
+-- |_ Statistics: Performed 31 guesses in 114 seconds, average tps: 0
+--
+-- @changelog
+-- 2015-07-04 - v0.1 - created by Soldier of Fortran
+-- 2015-11-14 - v0.2 - rewrote iterator
+--
+-- @author Philip Young
+-- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html
+--
+
+author = "Philip Young aka Soldier of Fortran"
+license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
+categories = {"intrusive", "brute"}
+portrule = shortport.port_or_service({23,992}, "tn3270")
+
+--- Saves the Screen generated by the CICS 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
+
+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 )
+ local status, err = self.tn3270:initiate(self.host,self.port)
+ self.tn3270:get_screen_debug(2)
+ if not status then
+ stdnse.debug("Could not initiate TN3270: %s", err )
+ return false
+ end
+ return true
+ end,
+ disconnect = function( self )
+ self.tn3270:disconnect()
+ self.tn3270 = nil
+ return true
+ end,
+ login = function (self, user, pass) -- pass is actually the CICS transaction we want to try
+ local commands = self.options['key1']
+ local path = self.options['key2']
+ local timeout = 300
+ local max_blank = 1
+ local loop = 1
+ local err, status
+ stdnse.debug(2,"Getting to CICS")
+ local run = stdnse.strsplit(";%s*", commands)
+ for i = 1, #run do
+ stdnse.debug(1,"Issuing Command (#%s of %s): %s", i, #run ,run[i])
+ self.tn3270:send_cursor(run[i])
+ self.tn3270:get_all_data()
+ self.tn3270:get_screen_debug(2)
+ end
+ while self.tn3270:isClear() and max_blank < 7 do
+ stdnse.debug(2, "Screen is not clear for %s. Reading all data with a timeout of %s. Count %s",pass, timeout, max_blank)
+ self.tn3270:get_all_data(timeout)
+ timeout = timeout + 100
+ max_blank = max_blank + 1
+ end
+
+ while not self.tn3270:isClear() and loop < 10 do
+ -- by this point we're at *some* CICS transaction
+ -- so we send F3 to exit it
+ stdnse.debug(2,"Sending: F3")
+ self.tn3270:send_pf(3) -- send F3
+ self.tn3270:get_all_data()
+ self.tn3270:get_screen_debug(2)
+ -- now we want to clear the screen
+ self.tn3270:send_clear()
+ self.tn3270:get_all_data()
+ stdnse.debug(2,"Current CLEARed Screen. Loop: %s", loop )
+ self.tn3270:get_screen_debug(2)
+ loop = loop + 1
+ end
+
+ if loop == 10 then
+ -- something is wrong but we can still try transactions. Print error to debug.
+ stdnse.debug('Error. Failed to get to a blank screen under CICS (sending F3 followed by CLEAR). Try lowering maxthreads to fix.')
+ end
+ stdnse.verbose("Trying Transaction ID: %s", pass)
+ stdnse.debug(2,"Sending Transaction ID: %s", pass)
+ self.tn3270:send_cursor(pass)
+ self.tn3270:get_all_data()
+
+ max_blank = 1
+ while self.tn3270:isClear() and max_blank < 7 do
+ stdnse.debug(2, "Screen is not clear for %s. Reading all data with a timeout of %s. Count %s",pass, timeout, max_blank)
+ self.tn3270:get_all_data(timeout)
+ timeout = timeout + 100
+ max_blank = max_blank + 1
+ end
+
+ stdnse.debug(2,"Screen Recieved for Transaction ID: %s", pass)
+ self.tn3270:get_screen_debug(2)
+ if self.tn3270:find('not recognized') then -- known invalid command
+ stdnse.debug("Invalid CICS Transaction ID: %s", string.upper(pass))
+ return false, brute.Error:new( "Incorrect CICS Transaction ID" )
+ elseif self.tn3270:isClear() then
+ stdnse.debug(2,"Empty Screen when we expect an error.")
+ -- this can mean that the transaction ID was valid
+ -- but it didn't send a screen update so you should check by hand.
+ -- We're not dumping this screen to disk because it's blank.
+ return true, creds.Account:new("CICS ID [blank screen]", string.upper(pass), creds.State.VALID)
+ elseif self.tn3270:find('Unauthorized') or self.tn3270:find('DFHAC2002') then
+ -- this is a VALID cics transaction but you must be authenticated to used it
+ -- This will be the same screen for each so we dont bother saving it either
+ stdnse.verbose("Valid CICS Transaction ID [requires auth]: %s", string.upper(pass))
+ return true, creds.Account:new("CICS ID [requires auth]", string.upper(pass), creds.State.VALID)
+ else
+ stdnse.verbose("Valid CICS Transaction ID: %s", string.upper(pass))
+ if path ~= nil then
+ stdnse.verbose(2,"Writting screen to: %s", path..string.upper(pass)..".txt")
+ 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("CICS ID", string.upper(pass), creds.State.VALID)
+ end
+ return false, brute.Error:new("Something went wrong, we didn't get a proper response")
+ end
+}
+
+--- Tests the target to see if we can even get to CICS
+--
+-- @param host host NSE object
+-- @param port port NSE object
+-- @param commands optional script-args of commands to use to get to CICS
+-- @return status true on success, false on failure
+
+local function cics_test( host, port, commands )
+ stdnse.debug("Checking for CICS")
+ local tn = tn3270.Telnet:new()
+ local status, err = tn:initiate(host,port)
+ local cics = false -- initially we're not at CICS
+ if not status then
+ stdnse.debug("Could not initiate TN3270: %s", err )
+ return cics
+ end
+ tn:get_screen_debug(2) -- prints TN3270 screen to debug
+ stdnse.debug("Getting to CICS")
+ local run = stdnse.strsplit(";%s*", commands)
+ for i = 1, #run do
+ stdnse.debug(1,"Issuing Command (#%s of %s): %s", i, #run ,run[i])
+ tn:send_cursor(run[i])
+ tn:get_all_data()
+ tn:get_screen_debug(2)
+ end
+ tn:get_all_data()
+ tn:get_screen_debug(2) -- for debug purposes
+ -- we should technically be at CICS. So we send:
+ -- * F3 to exit the CICS program
+ -- * CLEAR (a tn3270 command) to clear the screen.
+ -- (you need to clear before sending a transaction ID)
+ -- * a known default CICS transaction ID with predictable outcome
+ -- (CESF with 'Sign-off is complete.' as the result)
+ -- to confirm that we were in CICS. If so we return true
+ -- otherwise we return false
+ local count = 1
+ while not tn:isClear() and count < 6 do
+ -- some systems will just kick you off others are slow in responding
+ -- this loop continues to try getting out of CICS 6 times. If it can't
+ -- then we probably weren't in CICS to begin with.
+ if tn:find("Signon") then
+ stdnse.debug(2,"Found 'Signon' sending PF3")
+ tn:send_pf(3)
+ tn:get_all_data()
+ end
+ tn:get_all_data()
+ stdnse.debug(2,"Clearing the Screen")
+ tn:send_clear()
+ tn:get_all_data()
+ tn:get_screen_debug(2)
+ count = count + 1
+ end
+ if count == 6 then
+ return cics
+ end
+ stdnse.debug(2,"Sending CESF (CICS Default Sign-off)")
+ tn:send_cursor('CESF')
+ tn:get_all_data()
+ if tn:isClear() then
+ tn:get_all_data(1000)
+ end
+ tn:get_screen_debug(2)
+
+ if tn:find('Sign-off is complete.') then
+ tn:disconnect()
+ cics = true
+ end
+ tn:disconnect()
+ return cics
+end
+
+-- Filter iterator for unpwdb
+-- CICS is limited to 4 characters.
+local valid_cics = function(x)
+ return (string.len(x) <= 4)
+end
+
+function iter(t)
+ local i, val
+ return function()
+ i, val = next(t, i)
+ return val
+ end
+end
+
+action = function(host, port)
+ local cics_id_file = stdnse.get_script_args("idlist")
+ local path = stdnse.get_script_args(SCRIPT_NAME .. '.path') -- Folder for screenshots
+ local commands = stdnse.get_script_args(SCRIPT_NAME .. '.commands') or 'cics'-- VTAM commands/macros to get to CICS
+ local cics_ids = {"CADP", "CATA", "CATD", "CATR", "CBAM", "CCIN", "CCRL", "CDBC", "CDBD",
+ "CDBF", "CDBI", "CDBM", "CDBN", "CDBO", "CDBQ", "CDBT", "CDFS", "CDST",
+ "CDTS", "CEBR", "CEBT", "CECI", "CECS", "CEDA", "CEDB", "CEDC", "CEDF",
+ "CEDX", "CEGN", "CEHP", "CEHS", "CEKL", "CEMN", "CEMT", "CEOT", "CEPD",
+ "CEPF", "CEPH", "CEPM", "CEPQ", "CEPS", "CEPT", "CESC", "CESD", "CESF",
+ "CESL", "CESN", "CEST", "CETR", "CEX2", "CFCL", "CFCR", "CFOR", "CFQR",
+ "CFQS", "CFTL", "CFTS", "CGRP", "CHLP", "CIDP", "CIEP", "CIND", "CIS1",
+ "CIS4", "CISB", "CISC", "CISD", "CISE", "CISM", "CISP", "CISQ", "CISR",
+ "CISS", "CIST", "CISU", "CISX", "CITS", "CJLR", "CJSA", "CJSL", "CJSR",
+ "CJTR", "CKAM", "CKBC", "CKBM", "CKBP", "CKBR", "CKCN", "CKDL", "CKDP",
+ "CKQC", "CKRS", "CKRT", "CKSD", "CKSQ", "CKTI", "CLDM", "CLQ2", "CLR1",
+ "CLR2", "CLS1", "CLS2", "CLS3", "CLS4", "CMAC", "CMPX", "CMSG", "CMTS",
+ "COVR", "CPCT", "CPIA", "CPIH", "CPIL", "CPIQ", "CPIR", "CPIS", "CPLT",
+ "CPMI", "CPSS", "CQPI", "CQPO", "CQRY", "CRLR", "CRMD", "CRMF", "CRPA",
+ "CRPC", "CRPM", "CRSQ", "CRSR", "CRST", "CRSY", "CRTE", "CRTP", "CRTX",
+ "CSAC", "CSCY", "CSFE", "CSFR", "CSFU", "CSGM", "CSHA", "CSHQ", "CSHR",
+ "CSKP", "CSMI", "CSM1", "CSM2", "CSM3", "CSM5", "CSNC", "CSNE", "CSOL",
+ "CSPG", "CSPK", "CSPP", "CSPQ", "CSPS", "CSQC", "CSRK", "CSRS", "CSSF",
+ "CSSY", "CSTE", "CSTP", "CSXM", "CSZI", "CTIN", "CTSD", "CVMI", "CWBA",
+ "CWBG", "CWTO", "CWWU", "CWXN", "CWXU", "CW2A", "CXCU", "CXRE", "CXRT",
+ "DSNC"} -- Default CICS from https://www-01.ibm.com/support/knowledgecenter/SSGMCP_5.2.0/com.ibm.cics.ts.systemprogramming.doc/topics/dfha726.html
+
+ cics_id_file = ( (cics_id_file and nmap.fetchfile(cics_id_file)) or cics_id_file )
+
+ if cics_id_file then
+ for l in io.lines(cics_id_file) do
+ if not l:match("#!comment:") then
+ table.insert(cics_ids, l)
+ end
+ end
+ end
+
+ if cics_test(host, port, commands) then
+ local options = { key1 = commands, key2 = path }
+ stdnse.debug("Starting CICS Transaction 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(cics_ids), valid_cics))
+ engine.options.passonly = true
+ engine.options:setTitle("CICS Transaction ID")
+ local status, result = engine:start()
+ return result
+ else
+ return "Could not get to CICS. Aborting."
+ end
+end
diff --git a/scripts/cics-user-enum.nse b/scripts/cics-user-enum.nse
new file mode 100644
index 000000000..6ab89d81c
--- /dev/null
+++ b/scripts/cics-user-enum.nse
@@ -0,0 +1,252 @@
+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 string = require "string"
+
+description = [[
+CICS User ID enumeration script for the CESL/CESN Login screen.
+]]
+
+---
+-- @args idlist Path to list of transaction IDs.
+-- Defaults to the list of CICS transactions from IBM.
+-- @args cics-user-enum.commands Commands in a semi-colon seperated list needed
+-- to access CICS. Defaults to CICS.
+-- @args cics-user-enum.transaction By default this script uses the CESL transaction.
+-- on some systems the transactio ID CESN is needed. Use this argument to change the
+-- logon transaction ID.
+--
+-- @usage
+-- nmap --script=cics-user-enum -p 23
+--
+-- nmap --script=cics-user-enum --script-args userdb=users.txt,
+-- cics-user-enum.commands="exit;logon applid(cics42)" -p 23
+--
+-- @output
+-- PORT STATE SERVICE
+-- 23/tcp open tn3270
+-- | cics-user-enum:
+-- | Accounts:
+-- | PLAGUE: Valid - CICS User ID
+-- |_ Statistics: Performed 31 guesses in 114 seconds, average tps: 0
+--
+-- @changelog
+-- 2016-08-29 - v0.1 - created by Soldier of Fortran
+--
+-- @author Philip Young
+-- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html
+--
+
+author = "Philip Young aka Soldier of Fortran"
+license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
+categories = {"intrusive", "brute"}
+portrule = shortport.port_or_service({23,992}, "tn3270")
+
+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 )
+ local status, err = self.tn3270:initiate(self.host,self.port)
+ self.tn3270:get_screen_debug(2)
+ if not status then
+ stdnse.debug("Could not initiate TN3270: %s", err )
+ return false
+ end
+ return true
+ end,
+ disconnect = function( self )
+ self.tn3270:disconnect()
+ self.tn3270 = nil
+ return true
+ end,
+ login = function (self, user, pass) -- pass is actually the UserID we want to try
+ local commands = self.options['commands']
+ local transaction = self.options['trn']
+ local timeout = 300
+ local max_blank = 1
+ local loop = 1
+ local err
+ stdnse.debug(2,"Getting to CICS")
+ local run = stdnse.strsplit(";%s*", commands)
+ for i = 1, #run do
+ stdnse.debug(1,"Issuing Command (#%s of %s): %s", i, #run ,run[i])
+ self.tn3270:send_cursor(run[i])
+ self.tn3270:get_all_data()
+ self.tn3270:get_screen_debug(2)
+ end
+ -- Are we at the logon transaction?
+ if not (self.tn3270:find('SIGN ON TO CICS') and self.tn3270:find("Signon to CICS")) then
+ -- We might be at some weird screen, lets try and exit it then clear it out
+ stdnse.debug(2,"Sending: F3")
+ self.tn3270:send_pf(3) -- send F3
+ self.tn3270:get_all_data()
+ stdnse.debug(2,"Clearing the Screen")
+ self.tn3270:send_clear()
+ self.tn3270:get_all_data()
+ self.tn3270:get_screen_debug(2)
+ stdnse.debug(2,"Sending Transaction ID: %s", transaction)
+ self.tn3270:send_cursor(transaction)
+ self.tn3270:get_all_data()
+ -- Have we encoutered a slow system?
+ if self.tn3270:isClear() then
+ self.tn3270:get_all_data(1000)
+ end
+ self.tn3270:get_screen_debug(2)
+ end
+ -- At this point we MUST be at CESL/CESN to try accounts.
+ -- If we're not then we quit with an error
+ if not (self.tn3270:find('SIGN ON TO CICS') and self.tn3270:find("Signon to CICS")) then
+ err:setRetry( true )
+ return false, err
+ end
+
+ -- Ok we're good we're at CESL/CESN. Enter the USERID.
+ stdnse.verbose("Trying User ID: %s", pass)
+ self.tn3270:send_cursor(pass)
+ self.tn3270:get_all_data()
+ stdnse.debug(2,"Screen Recieved for User ID: %s", pass)
+ self.tn3270:get_screen_debug(2)
+ -- So far only support for TopSecret, ACF2
+ -- TODO: Add RACF error messages if they exist?
+ if self.tn3270:find('TSS7145E') or
+ self.tn3270:find('ACF01004') then
+ -- known invalid userid
+ -- TopSecret: TSS7145E
+ -- ACF2: ACF01004
+ -- RACF: TODO
+ stdnse.debug("Invalid CICS User ID: %s", string.upper(pass))
+ return false, brute.Error:new( "Incorrect CICS User ID" )
+ elseif self.tn3270:find('TSS7102E') or
+ self.tn3270:find('ACF01012') then
+ -- TopSecret: TSS7102E Password Missing
+ -- ACF2: ACF01012 PASSWORD NOT MATCHED
+ -- RACF: TODO
+ stdnse.verbose("Valid CICS User ID: %s", string.upper(pass))
+ return true, creds.Account:new("CICS User", string.upper(pass), creds.State.VALID)
+ else
+ stdnse.verbose("Valid(?) CICS User ID: %s", string.upper(pass))
+ -- The user may be valid for another reason, lets store that reason.
+ stdnse.verbose(2,"User: " .. user .. " MSG:" .. self.tn3270:get_screen():sub(2,80))
+ return true, creds.Account:new("CICS User: ".. string.upper(pass),'Reason: ' .. self.tn3270:get_screen():sub(2,80), creds.State.VALID)
+ end
+
+ return false, brute.Error:new("Something went wrong, we didn't get a proper response")
+ end
+}
+
+--- Tests the target to see if we can even get to CICS
+--
+-- @param host host NSE object
+-- @param port port NSE object
+-- @param commands optional script-args of commands to use to get to CICS
+-- @return status true on success, false on failure
+
+local function cics_test( host, port, commands, transaction )
+ stdnse.verbose(2,"Checking for CICS Login Page")
+ local tn = tn3270.Telnet:new()
+ local status, err = tn:initiate(host,port)
+ local cesl = false -- initially we're not at CICS
+ if not status then
+ stdnse.debug("Could not initiate TN3270: %s", err )
+ return false
+ end
+ tn:get_screen_debug(2) -- prints TN3270 screen to debug
+ stdnse.debug("Getting to CICS")
+ local run = stdnse.strsplit(";%s*", commands)
+ for i = 1, #run do
+ stdnse.debug(1,"Issuing Command (#%s of %s): %s", i, #run ,run[i])
+ tn:send_cursor(run[i])
+ tn:get_all_data()
+ tn:get_screen_debug(2)
+ end
+ tn:get_all_data()
+ tn:get_screen_debug(2) -- for debug purposes
+ -- We should now be at CICS. Check if we're already at the logon screen
+ if tn:find('SIGN ON TO CICS') and tn:find("Signon to CICS") then
+ stdnse.verbose(2,"At CICS Login Transaction")
+ tn:disconnect()
+ return true
+ end
+ -- Uh oh. We're not at the logon screen. Now we need to send:
+ -- * F3 to exit the CICS program
+ -- * CLEAR (a tn3270 command) to clear the screen.
+ -- (you need to clear before sending a transaction ID)
+ -- * a known default CICS transaction ID with predictable outcome
+ -- (CESF with 'Sign-off is complete.' as the result)
+ -- to confirm that we were in CICS. If so we return true
+ -- otherwise we return false
+ local count = 1
+ while not tn:isClear() and count < 6 do
+ -- some systems will just kick you off others are slow in responding
+ -- this loop continues to try getting out of whatever transaction 5 times. If it can't
+ -- then we probably weren't in CICS to begin with.
+ stdnse.debug(2,"Sending: F3")
+ tn:send_pf(3) -- send F3
+ tn:get_all_data()
+ stdnse.debug(2,"Clearing the Screen")
+ tn:send_clear()
+ tn:get_all_data()
+ tn:get_screen_debug(2)
+ count = count + 1
+ end
+ if count == 5 then
+ return false, 'Could not get to CICS after 5 attempts. Is this even CICS?'
+ end
+ stdnse.debug(2,"Sending %s", transaction)
+ tn:send_cursor(transaction)
+ tn:get_all_data()
+ if tn:isClear() then
+ tn:get_all_data(1000)
+ end
+ tn:get_screen_debug(2)
+
+ if tn:find('SIGN ON TO CICS') and tn:find("Signon to CICS") then
+ stdnse.verbose(2,"At CICS Login Transaction (%s)", transaction)
+ tn:disconnect()
+ return true
+ end
+ tn:disconnect()
+ return false, 'Could not get to '.. transaction ..' (CICS Logon Screen)'
+end
+
+-- Filter iterator for unpwdb
+-- IDs are limited to 8 alpha numeric and @, #, $ and can't start with a number
+-- pattern:
+-- ^%D = The first char must NOT be a digit
+-- [%w@#%$] = All letters including the special chars @, #, and $.
+local valid_name = function(x)
+ return (string.len(x) <= 7 and string.match(x,"^%D+[%w@#%$]"))
+end
+
+action = function(host, port)
+ local commands = stdnse.get_script_args(SCRIPT_NAME .. '.commands') or "cics"
+ local transaction = stdnse.get_script_args(SCRIPT_NAME .. '.transaction') or "CESL"
+ local cicstst, err = cics_test(host, port, commands, transaction)
+ if cicstst then
+ local options = { commands = commands, trn = transaction }
+ stdnse.debug("Starting CICS User ID Enumeration")
+ local engine = brute.Engine:new(Driver, host, port, options)
+ engine.options.script_name = SCRIPT_NAME
+ engine:setPasswordIterator(unpwdb.filter_iterator(brute.usernames_iterator(),valid_name))
+ engine.options.passonly = true
+ engine.options:setTitle("CICS User ID")
+ local status, result = engine:start()
+ -- port.version.extrainfo = "Security: " .. secprod
+ -- nmap.set_port_version(host, port)
+ return "result"
+ else
+ return err
+ end
+
+end
diff --git a/scripts/script.db b/scripts/script.db
index cd9448c9b..e2d1363de 100644
--- a/scripts/script.db
+++ b/scripts/script.db
@@ -58,6 +58,8 @@ Entry { filename = "broadcast-xdmcp-discover.nse", categories = { "broadcast", "
Entry { filename = "cassandra-brute.nse", categories = { "brute", "intrusive", } }
Entry { filename = "cassandra-info.nse", categories = { "default", "discovery", "safe", } }
Entry { filename = "cccam-version.nse", categories = { "version", } }
+Entry { filename = "cics-enum.nse", categories = { "brute", "intrusive", } }
+Entry { filename = "cics-user-enum.nse", categories = { "brute", "intrusive", } }
Entry { filename = "citrix-brute-xml.nse", categories = { "brute", "intrusive", } }
Entry { filename = "citrix-enum-apps-xml.nse", categories = { "discovery", "safe", } }
Entry { filename = "citrix-enum-apps.nse", categories = { "discovery", "safe", } }