diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 07:42:04 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-17 07:42:04 +0000 |
commit | 0d47952611198ef6b1163f366dc03922d20b1475 (patch) | |
tree | 3d840a3b8c0daef0754707bfb9f5e873b6b1ac13 /scripts/cics-user-brute.nse | |
parent | Initial commit. (diff) | |
download | nmap-upstream.tar.xz nmap-upstream.zip |
Adding upstream version 7.94+git20230807.3be01efb1+dfsg.upstream/7.94+git20230807.3be01efb1+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'scripts/cics-user-brute.nse')
-rw-r--r-- | scripts/cics-user-brute.nse | 299 |
1 files changed, 299 insertions, 0 deletions
diff --git a/scripts/cics-user-brute.nse b/scripts/cics-user-brute.nse new file mode 100644 index 0000000..768813e --- /dev/null +++ b/scripts/cics-user-brute.nse @@ -0,0 +1,299 @@ +local nmap = require "nmap" +local string = require "string" +local stringaux = require "stringaux" +local stdnse = require "stdnse" +local shortport = require "shortport" +local tn3270 = require "tn3270" +local brute = require "brute" +local creds = require "creds" +local unpwdb = require "unpwdb" + +description = [[ +CICS User ID brute forcing script for the CESL login screen. +]] + +--- +-- @args cics-user-brute.commands Commands in a semi-colon separated list needed +-- to access CICS. Defaults to <code>CICS</code>. +-- +-- @usage +-- nmap --script=cics-user-brute -p 23 <targets> +-- +-- nmap --script=cics-user-brute --script-args userdb=users.txt, +-- cics-user-brute.commands="exit;logon applid(cics42)" -p 23 <targets> +-- +-- @output +-- PORT STATE SERVICE +-- 23/tcp open tn3270 +-- | cics-user-brute: +-- | 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 +-- 2016-10-26 - v0.2 - Added RACF support +-- 2017-01-23 - v0.3 - Rewrote script to use fields and skip enumeration to speed up testing +-- 2019-02-01 - v0.4 - Disabled new TN3270E support + +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") + +--- Registers User IDs that no longer need to be tested +-- +-- @param username to stop checking +local function register_invalid( username ) + if nmap.registry.cicsinvalid == nil then + nmap.registry.cicsinvalid = {} + end + stdnse.debug(2,"Registering %s", username) + nmap.registry.cicsinvalid[username] = 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(brute.new_socket()) + o.tn3270:disable_tn3270e() + 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 + stdnse.debug(2,"Connect Successful") + return true + end, + disconnect = function( self ) + self.tn3270:disconnect() + self.tn3270 = nil + return true + end, + login = function (self, user, pass) + local commands = self.options['key1'] + local timeout = 300 + local max_blank = 1 + local loop = 1 + local err + stdnse.debug(2,"Getting to CICS") + local run = stringaux.strsplit(";%s*", commands) + for i = 1, #run do + stdnse.debug(2,"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') or 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 'CESL'") + self.tn3270:send_cursor('CESL') + 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 to try accounts. + -- If we're not then we quit with an error + if not (self.tn3270:find('SIGN ON TO CICS') or self.tn3270:find("Signon to CICS")) then + local err = brute.Error:new( "Can't get to CESL") + err:setRetry( true ) + return false, err + end + + -- Ok we're good we're at CESL. Send the Userid and Password. + local fields = self.tn3270:writeable() -- Get the writeable field areas + local user_loc = {fields[2][1],user} -- This is the 'UserID:' field + local pass_loc = {fields[4][1],pass} -- This is the 'Password:' field ([2] is a group ID) + stdnse.verbose('[BRUTE] Trying CICS: ' .. user ..' : ' .. pass) + stdnse.debug(3,"[BRUTE] Location:" .. fields[2][1] .. " x " .. fields[4][1]) + self.tn3270:send_locations({user_loc,pass_loc}) + self.tn3270:get_all_data() + stdnse.debug(2,"Screen Received for User ID: %s/%s", user, pass) + self.tn3270:get_screen_debug(2) + + local loop = 1 + while loop < 300 and self.tn3270:find('DFHCE3520') do -- still at Enter UserID message? + stdnse.verbose('Trying CICS: ' .. user ..' : ' .. pass) + self.tn3270:send_locations({user_loc,pass_loc}) + self.tn3270:get_all_data() + stdnse.debug(2,"Screen Received for User ID: %s/%s", user, pass) + self.tn3270:get_screen_debug(2) + loop = loop + 1 -- We don't want this to loop forever + end + + if loop == 300 then + local err = brute.Error:new( "Error with UserID entry") + err:setRetry( true ) + return false, err + end + + -- Now check what we received if its valid or not + if self.tn3270:find('TSS7101E') or + self.tn3270:find('DFHCE3530') or + self.tn3270:find('DFHCE3532') then + -- Top Secret: + -- TSS7101E Password is Incorrect + -- RACF: + -- DFHCE3530/2 Your userid or password is invalid. Please retype both. + return false, brute.Error:new( "Incorrect password" ) + elseif self.tn3270:find('TSS7145E') or + self.tn3270:find("TSS714[0-3]E") or + self.tn3270:find('TSS7160E') or + self.tn3270:find('TSS7120E') then + -- Top Secret: + -- TSS7140E Accessor ID Has Expired: No Longer Valid + -- TSS7141E Use of Accessor ID Suspended + -- TSS7142E Accessor ID Not Yet Available for Use - Still Inactive + -- TSS7143E Accessor ID Has Been Inactive Too Long + -- TSS7120E PASSWORD VIOLATION THRESHOLD EXCEEDED + -- TSS7145E ACCESSOR ID <acid> NOT DEFINED TO SECURITY + -- TSS7160E Facility <X> Not Authorized for Your Use + -- Store the invalid ID in the registry so we don't keep trying it with subsequent passwords + -- when using default brute library. + register_invalid(user) + return false, brute.Error:new( "User ID Not Able to Use CICS" ) + elseif self.tn3270:find("DFHCE3549") or + self.tn3270:find('TSS7000I') or + self.tn3270:find('TSS7110E Password Has Expired. New Password Missing') or + self.tn3270:find('TSS7001I') then + stdnse.verbose("Valid CICS UserID / Password: " .. user .. "/" .. pass) + return true, creds.Account:new(user, pass, creds.State.VALID) + else + -- ok whoa, something happened, print the screen but don't store as valid + stdnse.verbose("Valid(?) user/pass with current output " .. user .. "/" .. pass .. "\n" .. self.tn3270:get_screen()) + return false, brute.Error:new( "Incorrect password" ) + 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.verbose(2,"Checking for CICS Login Page") + local tn = tn3270.Telnet:new() + tn:disable_tn3270e() + 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(2,"Getting to CICS") + local run = stringaux.strsplit(";%s*", commands) + for i = 1, #run do + stdnse.debug(2,"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 (CESL)") + 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 'CESL'") + tn:send_cursor('CESL') + tn:get_all_data() + if tn:isClear() then + tn:get_all_data(1000) + end + tn:get_screen_debug(2) + + if tn:find("Signon to CICS") then + stdnse.verbose(2,"At CICS Login Transaction (CESL)") + tn:disconnect() + return true + end + tn:disconnect() + return false, 'Could not get to CESL (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) + if nmap.registry.tsoinvalid and nmap.registry.tsoinvalid[x] then + return false + end + return (string.len(x) <= 8 and string.match(x,"^%D+[%w@#%$]")) +end + +-- Checks string to see if it follows valid password limitations +local valid_pass = function(x) + return (string.len(x) <= 8 ) +end + +action = function(host, port) + local commands = stdnse.get_script_args(SCRIPT_NAME .. '.commands') or "cics" + local cicstst, err = cics_test(host, port, commands) + if cicstst then + local options = { key1 = commands } + local engine = brute.Engine:new(Driver, host, port, options) + stdnse.debug(2,"Starting CICS Brute Forcing") + engine:setUsernameIterator(unpwdb.filter_iterator(brute.usernames_iterator(),valid_name)) + engine:setPasswordIterator(unpwdb.filter_iterator(brute.passwords_iterator(),valid_pass)) + engine.options.script_name = SCRIPT_NAME + engine.options:setTitle("CICS User Accounts") + local status, result = engine:start() + return result + else + return err + end +end |