1local io = require "io" 2local string = require "string" 3local stringaux = require "stringaux" 4local table = require "table" 5local nmap = require "nmap" 6local stdnse = require "stdnse" 7local shortport = require "shortport" 8local brute = require "brute" 9local creds = require "creds" 10local unpwdb = require "unpwdb" 11local drda = require "drda" 12local comm = require "comm" 13 14description = [[ 15z/OS JES Network Job Entry (NJE) target node name brute force. 16 17NJE node communication is made up of an OHOST and an RHOST. Both fields 18must be present when conducting the handshake. This script attemtps to 19determine the target systems NJE node name. 20 21To initiate NJE the client sends a 33 byte record containing the type of 22record, the hostname (RHOST), IP address (RIP), target (OHOST), 23target IP (OIP) and a 1 byte response value (R) as outlined below: 24 25<code> 260 1 2 3 4 5 6 7 8 9 A B C D E F 27+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 28| TYPE | RHOST | 29+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 30| RIP | OHOST | OIP | 31+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 32| R | 33+-+-+ 34</code> 35 36* TYPE: Can either be 'OPEN', 'ACK', or 'NAK', in EBCDIC, padded by spaces to make 8 bytes. This script always send 'OPEN' type. 37* RHOST: Node name of the local machine initiating the connection. Set to 'FAKE'. 38* RIP: Hex value of the local systems IP address. Set to '0.0.0.0' 39* OHOST: The value being enumerated to determine the targets NJE node name. 40* OIP: IP address, in hex, of the target system. Set to '0.0.0.0'. 41* R: The response. NJE will send an 'R' of 0x01 if the OHOST is wrong or 0x04 if the OHOST is correct. 42 43By default this script will attempt the brute force a mainframes OHOST. If supplied with 44the argument <code>nje-node-brute.ohost</code> this script will attempt the bruteforce 45the RHOST, setting OHOST to the value supplied to the argument. 46 47Since most systems will only have one OHOST name, it is recommended to use the 48<code>brute.firstonly</code> script argument. 49]] 50 51 52--- 53-- @usage 54-- nmap -sV --script=nje-node-brute <target> 55-- nmap --script=nje-node-brute --script-args=hostlist=nje_names.txt -p 175 <target> 56-- 57-- @args nje-node-brute.hostlist The filename of a list of node names to try. 58-- Defaults to "nselib/data/vhosts-default.lst" 59-- 60-- @args nje-node-brute.ohost The target mainframe OHOST. Used to bruteforce RHOST. 61-- 62-- @output 63-- PORT STATE SERVICE REASON 64-- 175/tcp open nje syn-ack 65-- | nje-node-brute: 66-- | Node Name: 67-- | POTATO:CACTUS - Valid credentials 68-- |_ Statistics: Performed 6 guesses in 14 seconds, average tps: 0 69-- 70-- @changelog 71-- 2015-06-15 - v0.1 - created by Soldier of Fortran 72-- 2016-03-22 - v0.2 - Added RHOST Brute forcing. 73 74author = "Soldier of Fortran" 75license = "Same as Nmap--See https://nmap.org/book/man-legal.html" 76categories = {"intrusive", "brute"} 77 78portrule = shortport.port_or_service({175,2252}, "nje") 79 80local openNJEfmt = "\xd6\xd7\xc5\xd5@@@@%s\0\0\0\0%s\0\0\0\0\0" 81 82Driver = { 83 new = function(self, host, port, options) 84 local o = {} 85 setmetatable(o, self) 86 self.__index = self 87 o.host = host 88 o.port = port 89 o.options = options 90 return o 91 end, 92 93 connect = function( self ) 94 -- the high timeout should take delays into consideration 95 local s, r, opts, _ = comm.tryssl(self.host, self.port, '', { timeout = 50000 } ) 96 if ( not(s) ) then 97 stdnse.debug2("Failed to connect") 98 return false, "Failed to connect to server" 99 end 100 self.socket = s 101 return true 102 end, 103 104 disconnect = function( self ) 105 return self.socket:close() 106 end, 107 108 login = function( self, username, password ) -- Technically we're not 'logging in' we're just using password 109 -- Generates an NJE 'OPEN' packet with the node name 110 password = string.upper(password) 111 stdnse.verbose(2,"Trying... %s", password) 112 local openNJE 113 if self.options['ohost'] then 114 -- One RHOST may have many valid OHOSTs 115 if password == self.options['ohost'] then return false, brute.Error:new( "RHOST cannot be OHOST" ) end 116 openNJE = openNJEfmt:format(drda.StringUtil.toEBCDIC(("%-8s"):format(password)), 117 drda.StringUtil.toEBCDIC(("%-8s"):format(self.options['ohost'])) ) 118 else 119 openNJE = openNJEfmt:format(drda.StringUtil.toEBCDIC(("%-8s"):format('FAKE')), 120 drda.StringUtil.toEBCDIC(("%-8s"):format(password)) ) 121 end 122 local status, err = self.socket:send( openNJE ) 123 if not status then return false, "Failed to send" end 124 local status, data = self.socket:receive_bytes(33) 125 if not status then return false, "Failed to receive" end 126 if ( not self.options['ohost'] and ( data:sub(-1) == "\x04" ) ) or 127 ( self.options['ohost'] and ( data:sub(-1) == "\0" ) ) then 128 -- stdnse.verbose(2,"Valid Node Name Found: %s", password) 129 return true, creds.Account:new((self.options['ohost'] or "Node Name"), password, creds.State.VALID) 130 end 131 return false, brute.Error:new( "Invalid Node Name" ) 132 end, 133} 134 135-- Checks string to see if it follows node naming limitations 136local valid_name = function(x) 137 local patt = "[%w@#%$]" 138 return (string.len(x) <= 8 and string.match(x,patt)) 139end 140 141function iter(t) 142 local i, val 143 return function() 144 i, val = next(t, i) 145 return val 146 end 147end 148 149action = function( host, port ) 150 -- Oftentimes the LPAR will be one of the subdomain of a system. 151 local names = host.name and stringaux.strsplit("%.", host.name) or {} 152 local o_host = stdnse.get_script_args('nje-node-brute.ohost') or nil 153 local options = {} 154 if o_host then options = { ohost = o_host:upper() } end 155 if host.targetname then 156 host.targetname:gsub("[^.]+", function(n) table.insert(names, n) end) 157 end 158 local filename = stdnse.get_script_args('nje-node-brute.hostlist') 159 filename = (filename and nmap.fetchfile(filename) or filename) or 160 nmap.fetchfile("nselib/data/vhosts-default.lst") 161 for l in io.lines(filename) do 162 if not l:match("#!comment:") then 163 table.insert(names, l) 164 end 165 end 166 if o_host then stdnse.verbose(2,'RHOST Mode, using OHOST: %s', o_host:upper()) end 167 local engine = brute.Engine:new(Driver, host, port, options) 168 local nodes = unpwdb.filter_iterator(iter(names), valid_name) 169 engine.options:setOption("passonly", true ) 170 engine:setPasswordIterator(nodes) 171 engine.options.script_name = SCRIPT_NAME 172 engine.options:setTitle("Node Name(s)") 173 local status, result = engine:start() 174 return result 175end 176