0day in FortiClient <=

Kevin bio photo By Kevin

Forticlient Local Privilege Escalation to NT AUTHORITY/SYSTEM


We found 3 bugs that we could chain in order to go from a low privileged user to NT AUTHORITY/SYSTEM We tried reaching out to fortinet and work together with fixing the issues, but they almost did not reply for 8 months and released a poorly written patch without informing anyone.

NOTE: Versions down to 5.7 are also affected. It is possible that versions lower are also affected


  • 02-04-2018 - Reported vulnerability
  • 07-04-2018 - They reply that it is the correct email and we can just send it and they remind us of responsible disclosure and tells us that we will get public recognition on their site.
  • 15-04-2018 - Confirmation of received bug report
  • 25-04-2018 - Bugs acknowledged. 3 CVE numbers assigned. CVE-2018-9191, CVE-2018-9192 and CVE-2018-9193 ( These have since been assigned to other vulnerabilities and the CVE’s are currently unknown)
  • 11-05-2018 -> 31-06-2018 Regular ping for news
  • 01-08-2018 - News that I have been assigned to a case with news regarding the vulnerability
  • 22-11-2018 - Inform Fortinet that we haven’t received any news on a solution and we are preparing to release a blogpost
  • 19-12-2018 - We discover Fortinet tried to patch it. We then reverse engineer the patches in place and advise them of our released blogpost
  • 21-12-2018 - Fix scheduled for windows versions 6.0.5/6.2. A full advisory with more details is being worked on at this moment

The bugs

We abuse named pipes to force an upgrade. We can spawn a vulnerability scan and then force forticlient to upgrade software that is not on the machine. This combined with a path traversal bug and then a command injection gives us NT AUTHORITY/SYSTEM

Writing to named pipes and telling forticlient what vulnerabilities we have

Using ioninja we could monitor forticlients communication over named pipes. On my system this named pipe is called FC_{F18F86FD-7503-4564-80CF-B6B199519837}

We tried clicking around in the UI available to all users and quickly saw an interesting packet. This was the way to tell the scheduler to start vulnerability scans on demand. One thing that was odd, was that we could specify paths and command line arguments in the packet.

After some reversing of the protocol I forged this packet one.png (Bytes on the left and ASCII on the right) I figured 60 80 in the start must be some magic bytes for the command. I figured that the hex number 18 ( byte 9 ) specifies the apptype. In our poc we abuse apptype 0x18 == FCVbltScan.exe.

A process (FCDBLog.exe) will read the message over named pipes and then update the registry settings in HKLM\SOFTWARE\Fortinet\FortiClient\FA_Scheduler\000020 It simply takes the message and writes it to the “param” key ( if we change apptype/byte 9 we can write to the other services in the FA_Scheduler ) fcdb2.png

(As can be seen here we simply wrote “-t 1 -p c:\users\lowpriv\Desktop\lasla\evil.tmp -u’” for initiating a patch of all found vulnerabilities in evil.tmp)

After this is done the scheduler.exe (system) process will pick it up and start the service as system.

So the problem here is that we can specify a custom XML file such as evil.tmp and that will be read as vulnerabilities found in the system.

Path traversal in vulnerability findings

NOTE: When speaking about vcm2.exe it is because it is launched by FCVbltScan.exe

The second bug is in the parsing of the file provided in the -p parameter The file specifies the findings of forticlients vulnerabilty scanner. Forticlient uses it to see what patches is has to apply by looking at the VID key in each Install entry in the json file (json file we engineered is seen below)

Normally this VID would be 20440 or something along those lines. An integer field. And then vcm2.exe will look for VID + .json in the scanning directory.

But we found that we could do a path traversal in the VID and point it towards a place we control. This means that we can provide the configuration for the vulnerability installation file.

This can be seen here where we point it towards \users\IEUser\Desktop\lasla\vuln

(vcm2.exe will now look for vuln.json in that directory to look for install instructions) vulnjson3.png (our evil.tmp)

So we can now control exactly what patches to apply to the system.

Command injection in LUA patching scripts

This leads us to the third and final bug. Command injection in LUA

The vuln.json that is specified in the evil.tmp is the install file “configuration”. We took a legit one and modified. This is used to specify what product is to be patched.

All of forticlients lua scripts is stored in a file called flat-db.dat that is an SQLITE file. So the tables have VID’s to match the scripts that it should execute.

What vcm2.exe does is that it reads the relevant LUA script from “flat-db.dat” in the forticlient directory and executes the one that has the VID we specify.

The vuln.json is shown here: another.png

We can however pass some arguments to the lua scripts in the DetectedProducts field. As we can see in the key “DetectedProducts” we specify a “Product”, “Product Path” and “Detected Version” We took the LUA script for patching Mozilla products and we found these lines: another5.png

as we can see filepath = arg[2] is passed down to GetLanguage

The GetLanguage function looks like this:


as we can see on line 50 and 52 it first puts our setupPath into the cmd parameter and then on line 52 it executes it without verifying if we used characters that should not be allowed. So if we look back to our vuln.json above we can see that our “Product Path” is “c:\tes"&calc&"tingfire\firefox.exe” which injects the command calc to the os.capture.

We now have a system calc. This could be a cmd or anything else. another7.png

The patch

After quickly reversing it I saw that they had a simple assertion on the product and detected_version flame1.png They furthermore used the os.rename function to ensure that the file actually existed and since files can’t contain characters to escape a string they were good to go. flame2.png

The bypass

Wait a minute… scroll back to the product patch on line 227… They asserted that it should just be a string? That’s not how command injection works and certainly not how prevention is done correctly. Well then lets just quickly grep after a place where they use os.execute on product as we can use all characters in a string :D

Bingo flame3.png

Our modified vuln.json for reference lol1.png

The result lol2.png

Final notes

When someone is reporting a bug please be responsive and let them test your patches. We offer to use so much time completely free as we enjoy breaking software, so why not take advantage of it and let us help in making your software more secure.

Twitter links:

Me: https://twitter.com/ggisx

Secu: https://twitter.com/secuinc