local rmi = require "rmi"
local shortport = require "shortport"
local string = require "string"
local stdnse = require "stdnse"
local vulns = require "vulns"

description = [[
Tests whether Java rmiregistry allows class loading.  The default
configuration of rmiregistry allows loading classes from remote URLs,
which can lead to remote code execution. The vendor (Oracle/Sun)
classifies this as a design feature.


Based on original Metasploit module by mihi.

References:
* https://github.com/rapid7/metasploit-framework/blob/master/modules/exploits/multi/misc/java_rmi_server.rb
]];

---
-- @usage
-- nmap --script=rmi-vuln-classloader -p 1099 <target>
--
-- @output
-- PORT     STATE SERVICE
-- 1099/tcp open  rmiregistry
-- | rmi-vuln-classloader:
-- |   VULNERABLE:
-- |   RMI registry default configuration remote code execution vulnerability
-- |     State: VULNERABLE
-- |     Description:
-- |               Default configuration of RMI registry allows loading classes from remote URLs which can lead to remote code executeion.
-- |
-- |     References:
-- |_      https://github.com/rapid7/metasploit-framework/blob/master/modules/exploits/multi/misc/java_rmi_server.rb

author = "Aleksandar Nikolic";
license = "Same as Nmap--See https://nmap.org/book/man-legal.html";
categories = {
  "intrusive",
  "vuln"
};


portrule = shortport.port_or_service({1098, 1099, 1090, 8901, 8902, 8903}, {"java-rmi", "rmiregistry"})

action = function (host, port)
  local registry = rmi.Registry:new(host, port);
  registry:_handshake();
  local rmiArgs = rmi.Arguments:new();
  local argsRaw = "75" ..  --TC_ARRAY
   "72" ..  -- TC_CLASSDESC
   "0018" ..  -- string len
   "5B4C6A6176612E726D692E7365727665722E4F626A49443B" ..  -- class name "[Ljava.rmi.server.ObjID;"
   "871300B8D02C647E" ..  -- serial id
   "02" ..  -- FLAGS (serializable)
   "0000" ..  -- FIELD COUNT
   "70787000000000" ..  --TC_NULL TC_BLOCKEND TC_NULL
   "77080000000000000000" ..  -- TC_BLOCKDATA
   "73" ..  -- TC_OBJECT
   "72" ..  -- TC_CLASSDESC
   "0005" ..  -- string len
   "64756D6D79" ..  -- class name "dummy"
   "A16544BA26F9C2F4" ..  -- serial id
   "02" ..  -- FLAGS (serializable)
   "0000" ..  -- FIELD COUNT
   "74" ..  -- TC_STRING
   "0010" ..  -- string len
   "66696C653A2E2F64756D6D792E6A6172" ..  -- annotation "file:./dummy.jar"
   "78" ..  -- TC_ENDBLOCKDATA
   "70" ..  -- TC_NULL
   "7701000A"; -- TC_BLOCKDATA
  local rmi_vuln = {
    title = "RMI registry default configuration remote code execution vulnerability",

    description = [[
Default configuration of RMI registry allows loading classes from remote URLs which can lead to remote code execution.
]],
    references = {
      'https://github.com/rapid7/metasploit-framework/blob/master/modules/exploits/multi/misc/java_rmi_server.rb',
    },
    exploit_results = {},
  };

  local report = vulns.Report:new(SCRIPT_NAME, host, port);
  rmi_vuln.state = vulns.STATE.NOT_VULN;

  rmiArgs:addRaw(stdnse.fromhex( argsRaw));

  -- reference: java/rmi/dgc/DGCImpl_Stub.java and java/rmi/dgc/DGCImpl_Skel.java
  -- we are calling DGC's (its objectId is 2) method with opnum 0
  -- DCG's hashcode is f6b6898d8bf28643 hex or -669196253586618813 dec
  local status, j_array = registry.out:writeMethodCall(registry.out, 2, "f6b6898d8bf28643", 0, rmiArgs);
  local status, retByte = registry.out.dis:readByte();
  if not status then
    return false, "No return data received from server";
  end

  if 0x51 ~= retByte then
    -- 0x51 : Returndata
    return false, "No return data received from server";
  end
  -- Need to make sure we get a good chunk of data. It's going to be a java
  -- stack trace. But if we don't get enough, I guess we can check with
  -- whatever we get.
  registry.out.dis:canRead(256)
  local data = registry.out.dis.bReader.readBuffer;

  if string.find(data, "RMI class loader disabled") == nil then
    rmi_vuln.state = vulns.STATE.VULN;
    return report:make_output(rmi_vuln);
  end

  return report:make_output(rmi_vuln);
end;
