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/nje-node-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/nje-node-brute.nse')
-rw-r--r-- | scripts/nje-node-brute.nse | 175 |
1 files changed, 175 insertions, 0 deletions
diff --git a/scripts/nje-node-brute.nse b/scripts/nje-node-brute.nse new file mode 100644 index 0000000..3f10a8a --- /dev/null +++ b/scripts/nje-node-brute.nse @@ -0,0 +1,175 @@ +local io = require "io" +local string = require "string" +local stringaux = require "stringaux" +local table = require "table" +local nmap = require "nmap" +local stdnse = require "stdnse" +local shortport = require "shortport" +local brute = require "brute" +local creds = require "creds" +local unpwdb = require "unpwdb" +local drda = require "drda" +local comm = require "comm" + +description = [[ +z/OS JES Network Job Entry (NJE) target node name brute force. + +NJE node communication is made up of an OHOST and an RHOST. Both fields +must be present when conducting the handshake. This script attemtps to +determine the target systems NJE node name. + +To initiate NJE the client sends a 33 byte record containing the type of +record, the hostname (RHOST), IP address (RIP), target (OHOST), +target IP (OIP) and a 1 byte response value (R) as outlined below: + +<code> +0 1 2 3 4 5 6 7 8 9 A B C D E F ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| TYPE | RHOST | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| RIP | OHOST | OIP | ++-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +| R | ++-+-+ +</code> + +* TYPE: Can either be 'OPEN', 'ACK', or 'NAK', in EBCDIC, padded by spaces to make 8 bytes. This script always send 'OPEN' type. +* RHOST: Node name of the local machine initiating the connection. Set to 'FAKE'. +* RIP: Hex value of the local systems IP address. Set to '0.0.0.0' +* OHOST: The value being enumerated to determine the targets NJE node name. +* OIP: IP address, in hex, of the target system. Set to '0.0.0.0'. +* R: The response. NJE will send an 'R' of 0x01 if the OHOST is wrong or 0x04 if the OHOST is correct. + +By default this script will attempt the brute force a mainframes OHOST. If supplied with +the argument <code>nje-node-brute.ohost</code> this script will attempt the bruteforce +the RHOST, setting OHOST to the value supplied to the argument. + +Since most systems will only have one OHOST name, it is recommended to use the +<code>brute.firstonly</code> script argument. +]] + + +--- +-- @usage +-- nmap -sV --script=nje-node-brute <target> +-- nmap --script=nje-node-brute --script-args=hostlist=nje_names.txt -p 175 <target> +-- +-- @args nje-node-brute.hostlist The filename of a list of node names to try. +-- Defaults to "nselib/data/vhosts-default.lst" +-- +-- @args nje-node-brute.ohost The target mainframe OHOST. Used to bruteforce RHOST. +-- +-- @output +-- PORT STATE SERVICE REASON +-- 175/tcp open nje syn-ack +-- | nje-node-brute: +-- | Node Name: +-- | POTATO:CACTUS - Valid credentials +-- |_ Statistics: Performed 6 guesses in 14 seconds, average tps: 0 +-- +-- @changelog +-- 2015-06-15 - v0.1 - created by Soldier of Fortran +-- 2016-03-22 - v0.2 - Added RHOST Brute forcing. + +author = "Soldier of Fortran" +license = "Same as Nmap--See https://nmap.org/book/man-legal.html" +categories = {"intrusive", "brute"} + +portrule = shortport.port_or_service({175,2252}, "nje") + +local openNJEfmt = "\xd6\xd7\xc5\xd5@@@@%s\0\0\0\0%s\0\0\0\0\0" + +Driver = { + new = function(self, host, port, options) + local o = {} + setmetatable(o, self) + self.__index = self + o.host = host + o.port = port + o.options = options + return o + end, + + connect = function( self ) + -- the high timeout should take delays into consideration + local s, r, opts, _ = comm.tryssl(self.host, self.port, '', { timeout = 50000 } ) + if ( not(s) ) then + stdnse.debug2("Failed to connect") + return false, "Failed to connect to server" + end + self.socket = s + return true + end, + + disconnect = function( self ) + return self.socket:close() + end, + + login = function( self, username, password ) -- Technically we're not 'logging in' we're just using password + -- Generates an NJE 'OPEN' packet with the node name + password = string.upper(password) + stdnse.verbose(2,"Trying... %s", password) + local openNJE + if self.options['ohost'] then + -- One RHOST may have many valid OHOSTs + if password == self.options['ohost'] then return false, brute.Error:new( "RHOST cannot be OHOST" ) end + openNJE = openNJEfmt:format(drda.StringUtil.toEBCDIC(("%-8s"):format(password)), + drda.StringUtil.toEBCDIC(("%-8s"):format(self.options['ohost'])) ) + else + openNJE = openNJEfmt:format(drda.StringUtil.toEBCDIC(("%-8s"):format('FAKE')), + drda.StringUtil.toEBCDIC(("%-8s"):format(password)) ) + end + local status, err = self.socket:send( openNJE ) + if not status then return false, "Failed to send" end + local status, data = self.socket:receive_bytes(33) + if not status then return false, "Failed to receive" end + if ( not self.options['ohost'] and ( data:sub(-1) == "\x04" ) ) or + ( self.options['ohost'] and ( data:sub(-1) == "\0" ) ) then + -- stdnse.verbose(2,"Valid Node Name Found: %s", password) + return true, creds.Account:new((self.options['ohost'] or "Node Name"), password, creds.State.VALID) + end + return false, brute.Error:new( "Invalid Node Name" ) + end, +} + +-- Checks string to see if it follows node naming limitations +local valid_name = function(x) + local patt = "[%w@#%$]" + return (string.len(x) <= 8 and string.match(x,patt)) +end + +function iter(t) + local i, val + return function() + i, val = next(t, i) + return val + end +end + +action = function( host, port ) + -- Oftentimes the LPAR will be one of the subdomain of a system. + local names = host.name and stringaux.strsplit("%.", host.name) or {} + local o_host = stdnse.get_script_args('nje-node-brute.ohost') or nil + local options = {} + if o_host then options = { ohost = o_host:upper() } end + if host.targetname then + host.targetname:gsub("[^.]+", function(n) table.insert(names, n) end) + end + local filename = stdnse.get_script_args('nje-node-brute.hostlist') + filename = (filename and nmap.fetchfile(filename) or filename) or + nmap.fetchfile("nselib/data/vhosts-default.lst") + for l in io.lines(filename) do + if not l:match("#!comment:") then + table.insert(names, l) + end + end + if o_host then stdnse.verbose(2,'RHOST Mode, using OHOST: %s', o_host:upper()) end + local engine = brute.Engine:new(Driver, host, port, options) + local nodes = unpwdb.filter_iterator(iter(names), valid_name) + engine.options:setOption("passonly", true ) + engine:setPasswordIterator(nodes) + engine.options.script_name = SCRIPT_NAME + engine.options:setTitle("Node Name(s)") + local status, result = engine:start() + return result +end |