Exploiting LibreOffice (CVE-2024-12425 and CVE-2024-12426)

This is a write-up of CVE-2024-12425 and CVE-2024-12426, two vulnerabilities in LibreOffice which respectively allow an attacker to write to a semi-arbitrary file in the filesystem, and remotely extract values from environment variables and from INI-like files in the filesystem. Both occur upon loading the document, without any user interaction. These vulnerabilities are relevant in a desktop scenario (a victim user opens a malicious document) and in server-side headless scenarios (a victim application uses LibreOffice in a headless manner to convert a malicious document).

For the desktop scenario, we show an example exploit where the file-read primitive is used to steal a reset token from an incoming email, allowing for further escalation of such a targeted attack.

The server-side scenario may also allow for a high-impact attack. Reading and writing files on a server filesystem could allow an attacker to fully compromise the system, but this depends on the surrounding application and technologies.

Overall, we recommend checking whether you are (indirectly) using LibreOffice and updating whenever possible. See the Mitigation section at the bottom of this post for more information.

LibreOffice can read and write various document formats, several of which support embedding of font files into the document. To play with this, the easiest format to use is `.fodt`. This format is functionally the same as `.odt`, however instead of a ZIP file with multiple files it is just a single uncompressed OpenDocument XML file. This makes it much easier to manually tweak it with a text-editor.

In the OpenDocument XML syntax, an embedded font is defined like this:

“`
(… base64-encoded opentype font file here …)
“`

During load-time, the font data is extracted, and it is temporarily stored on disk as a .ttf file. The full path for this temporary file is provided by `EmbeddedFontsHelper::fileUrlForTemporaryFont`:

“`
OUString EmbeddedFontsHelper::fileUrlForTemporaryFont( const OUString& fontName, std::u16string_view extra ) { OUString path = “${$BRAND_BASE_DIR/” LIBO_ETC_FOLDER “/” SAL_CONFIGFILE( “bootstrap”) “::UserInstallation}”; rtl::Bootstrap::expandMacros( path ); path += “/user/temp/embeddedfonts/fromdocs/”; osl::Directory::createPath( path ); OUString filename = fontName; static int uniqueCounter = 0; if( extra == u”?” ) filename += OUString::number( uniqueCounter++ ); else filename += extra; filename += “.ttf”; // TODO is it always ttf? return path + filename; }
“`

When this function is invoked, the `fontName` parameter is given the value of the `svg:font-family` attribute coming from XML. Hence, our example font above will be stored under e.g. `~/.config/libreoffice/4/user/temp/embeddedfonts/fromdocs/MyFont0.ttf`. You might have already spotted the problem here: we can “escape” this temporary directory by including `../` in a font’s (family) name. Because this procedure is ran before the font is validated in any way, we can specify arbitrary base64-encoded data, and write whatever we want to wherever we want on the filesystem. The only restriction is in the final filename: it will end with `0.ttf` (or just `.ttf` via a slightly different flow, where `extra` is specified but empty). Most importantly, because we’ve escaped the temp folder, the file will not be removed as part of LibreOffice’s clean-up logic.

For example, the following snippet will result in a file called `pwned0.ttf` in the user’s home directory when opened in LibreOffice (assuming the default config folder location):

“`
SGVsbG8gd29ybGQgaW5zaWRlIGEgZm9udCBmaWxlIQ==
“`

As demonstrated here:

“`
$ cat ~/pwned0.ttf cat /home/thomas/pwned0.ttf: No such file or directory $ libreoffice –convert-to pdf poc_hello_world.fodt $ cat ~/pwned0.ttf Hello world inside a font file!
“`

Due to the fixed `.ttf` extension, the impact of this vulnerability may be limited, depending on the greater context in which LibreOffice is used. However, there are situations where this results in a high impact, such as:

`init.d-style folders), interpreting any file it sees, independent of its name. This could cause further escalation depending on the software involved.`

In both desktop and headless usages, LibreOffice will attempt to resolve and load external images referenced by a document. This applies to the HTML `img` tag for example, but also to equivalent cases in other formats (e.g. certain objects in `.doc` and `.odt` files). Various URL schemes are supported, including `file` and `http(s)`.

Normally this does not pose much of a security problem, besides a minor risk for server-side request-forgery (SSRF) in the headless use-case and at most a boolean information leak in the desktop use-case (“The victim has opened the document”).

However, it turns out that one of the supported schemes, `vnd.sun.star.expand`, allows an attacker to leak much more information.

This scheme is particularly interesting as it has a variable-substitution functionality reminiscent of Log4j. While seemingly intended for internal usage, it is happily evaluated on URLs in the document content. For example, loading the following HTML snippet will cause LibreOffice to (attempt to) fetch an image from `https://example.com?foo=/home/thomas`:

“`

“`

This is now much worse than a boolean leak (“the user has opened the document”), and turns it into a way to remotely leak the contents of arbitrary environment variables. Standard POSIX variables like `$PWD` and `$HOME` can already leak more about a user than they would likely be comfortable with (just by opening a document on an internet-connected machine), let alone specific scenarios where an attacker knows that a victim stores a certain application-specific secret in an environment variable.

Even worse however, `vnd.sun.star.expand` cannot only read from the system environment, but also from arbitrary INI files. It appears that LibreOffice uses INI files for configuration purposes and hence this scheme is used to dynamically obtain specific configuration values. We even saw an example of this above in the `fileUrlForTemporaryFont` method related to CVE-2024-12425, where a configuration item named `UserInstallation` is extracted from an INI file at a dynamically resolved path.

The syntax is `${filepath:category:key}` for when the key is under an INI category, and `${filepath:key}` or `${filepath::key}` for when the key is at the root.

“`
foo = “Hello world!” [bar] baz = 42
“`

Here, the key `baz` is contained in the category `bar`, while `foo` is at the root.

Obviously this allows an attacker to extract secrets from INI files at known locations, but this might be a relatively niche scenario. More interestingly, it turns out that LibreOffice’s INI parser is very lax and basically just splits the target file into lines, checking every line for the presence of `=`. For each of those lines, if the preceding sub-string equals the requested key, the part after the `=` is returned.

This opens this attack up to not just INI files, but also the much more common TOML format, and `.env`-style configuration files which are commonly used to store secrets.

Much more can be leaked through this when thinking outside the box. For example, even environment variables that were at some point manually passed to a different process can be extracted, if there is a line in the user’s shell history file containing an `=`, starting with a known prefix ( `SECRET= some_command …`):

“`

“`

To show the impact for desktop users, we devised an attack against a use-case that might be somewhat realistic. The victim here has both LibreOffice and Thunderbird installed and manages a WordPress website.

The attacker can take over the victim’s WordPress account through the following steps:

`.odtor` `.docfile.`

The file shared in step two would contain the following payload (shown as an HTML `img` tag for readability):

“`

“`

In this payload we use recursive substitution to perform several steps in a row:

`Profile0section of` `~/.thunderbird/profiles.ini;` `global-messages-db.sqlite, an SQLite file containing all the messages;` `https://victim-blog.com/wp-login.php?action, extracting the value after the` `=;`

This works, as WordPress password-reset links have the following format:

“`
https:///wp-login.php?action=rp&key=deadbeefdeadbeef&Login=Username
“`

The part before the equals-sign is known by the attacker, and the part after contains the desired secret. The fact that this line is part of a binary SQLite database file does not matter because Thunderbird stores email contents in raw form, and WordPress’s password reset emails happen to put the link on its own line.

To mitigate these vulnerabilities, update your installation of LibreOffice to v24.8.4 or higher. If your distribution does not provide the latest LibreOffice version, it might still have released a patch version containing a (backported) fix for this vulnerability.

If you’re unsure if you’re affected, we provide proof-of-concept files for both CVEs with instructions for checking whether your installation is vulnerable.