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 nmap = require "nmap"
local string = require "string"
description = [[
TSO User ID enumerator for IBM mainframes (z/OS). The TSO logon panel
tells you when a user ID is valid or invalid with the message:
IKJ56420I Userid not authorized to use TSO
.
The TSO logon process can work in two ways:
1) You get prompted with IKJ56700A ENTER USERID -
to which you reply with the user you want to use.
If the user ID is valid it will give you a normal
TSO logon screen. Otherwise it will give you the
screen logon error above.
2) You're given the TSO logon panel and enter your user ID
at the Userid ===>
prompt. If you give
it an invalid user ID you receive the error message above.
This script relies on the NSE TN3270 library which emulates a
TN3270 screen for NMAP.
TSO user IDs have the following rules:
- it cannot begin with a number
- only contains alpha-numeric characters and @, #, $.
- it cannot be longer than 7 chars
]]
---
-- @args tso-enum.commands Commands in a semi-colon seperated list needed
-- to access TSO. Defaults to tso
.
--
-- @usage
-- nmap --script=tso-enum -p 23
--
-- @usage
-- nmap -sV -p 9923 10.32.70.10 --script tso-enum --script-args userdb=tso_users.txt,tso-enum.commands="logon applid(tso)"
--
-- @output
-- PORT STATE SERVICE VERSION
-- 23/tcp open tn3270 IBM Telnet TN3270
-- | tso-enum:
-- | TSO User ID:
-- | TSO User:RAZOR - Valid User ID
-- | TSO User:BLADE - Valid User ID
-- | TSO User:PLAGUE - Valid User ID
-- |_ Statistics: Performed 6 guesses in 3 seconds, average tps: 2
--
-- @changelog
-- 2015-07-04 - v0.1 - created by Soldier of Fortran
-- 2015-10-30 - v0.2 - streamlined the code, relying on brute and unpwdb and
-- renamed to tso-enum.
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,623}, {"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:send_pf(3)
self.tn3270:disconnect()
self.tn3270 = nil
return true
end,
login = function (self, user, pass)
-- pass is actually the user id we want to try
local commands = self.options['key1']
local cmd_DATA = false
stdnse.debug(2,"Getting to TSO")
local run = stdnse.strsplit(";%s*", commands)
for i = 1, #run do
stdnse.debug(1,"Issuing Command (#%s of %s): %s", i, #run ,run[i])
if string.find(string.upper(run[i]),"logon applid") ~= nil then
stdnse.verbose(2,"Trying User ID: %s", pass)
self.tn3270:send_cursor(run[i] .. " DATA(" .. pass .. ")")
cmd_DATA = true
else
self.tn3270:send_cursor(run[i])
end
self.tn3270:get_all_data()
end
self.tn3270:get_screen_debug(2)
if not self.tn3270:find("ENTER USERID") and not self.tn3270:find("TSO/E LOGON") then
local err = brute.Error:new( "TSO Unavailable" )
-- This error occurs on too many concurrent application requests it
-- should be temporary. If not, the script errors and less threads should be used.
err:setRetry( true )
return false, err
end
if not cmd_DATA then
stdnse.verbose(2,"Trying User ID: %s", pass)
self.tn3270:send_cursor(pass)
self.tn3270:get_all_data()
-- some systems require an enter after sending a valid user ID
if self.tn3270:find("***") then
self.tn3270:send_enter()
self.tn3270:get_all_data()
end
end
stdnse.debug(2,"Screen Recieved for User ID: %s", pass)
self.tn3270:get_screen_debug(2)
if self.tn3270:find('not authorized to use TSO') then -- invalid user ID
return false, brute.Error:new( "Incorrect User ID" )
elseif ( self.tn3270:find('NO USER APPLID AVAILABLE') ) or
( self.tn3270:isClear() ) or (not self.tn3270:find('TSO/E LOGON') ) then
local err = brute.Error:new( "TSO Unavailable" )
-- This error occurs on too many concurrent application requests it
-- should be temporary. If not, the script errors and less threads should be used.
err:setRetry( true )
return false, err
else
stdnse.verbose("Valid TSO User ID: %s", string.upper(pass))
return true, creds.Account:new("TSO User",string.upper(pass), " Valid User ID")
end
end
}
--- Tests the target to see if we can even get to TSO
--
-- @param host host NSE object
-- @param port port NSE object
-- @param commands script-args of commands to use to get to TSO
-- @return status true on success, false on failure
-- @return name of security product installed
local function tso_test( host, port, commands )
stdnse.debug("Checking for TSO")
local tn = tn3270.Telnet:new()
local status, err = tn:initiate(host,port)
local tso = false -- initially we're not at TSO logon panel
local secprod = "RACF"
tn:get_screen_debug(2) -- prints TN3270 screen to debug
if not status then
stdnse.debug("Could not initiate TN3270: %s", err )
return tso, "Could not Initiate TN3270"
end
stdnse.debug("Getting to TSO")
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()
end
tn:get_screen_debug(2)
if tn:find("ENTER USERID") or tn:find("TSO/E LOGON") then
tso = true
-- Patch OA44855 removed the ability to enumerator users
-- we check for that here
tn:send_cursor("notreal")
tn:get_all_data()
if tn:find("IKJ56476I ENTER PASSWORD") then
return false, "Could not enumerate. PASSWORDPREPROMPT is set to ON."
end
end
if tn:find("***") then
secprod = "TopSecret/ACF2"
end
tn:send_pf(3)
tn:disconnect()
return tso, secprod, "Could not get to TSO. Try --script-args=tso-enum.commands='logon applid(tso)'. Aborting."
end
-- Filter iterator for unpwdb
-- TSO is limited to 7 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 "tso"
local tsotst, secprod, err = tso_test(host, port, commands)
if tsotst then
local options = { key1 = commands }
stdnse.debug("Starting TSO 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("TSO 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