RedTeam Pentesting GmbH - Blog

11 October 2023

Better dSAFER than Sorry - An Attacker's Overview of Ghostscript

Ghostscript is the backbone of document processing for a lot of web apps and programs. If you have never heard of Ghostscript yet, you still have very likely already used it a lot through various programs such as PDF viewers, office suites or document converters. However, since you are reading a security-centric blog, you may have already heard of Ghostscript due to various high-profile vulnerabilities that allowed for powerful attacks against it. Even without vulnerabilities in Ghostscript itself, there are still a lot of pitfalls and misconceptions that can easily result in serious vulnerabilities in programs that rely on Ghostscript.

Join us on a deep dive into how and where Ghostscript is commonly used, what PostScript is and how attackers can abuse it to achieve remote code execution (RCE) and arbitrary file disclosure using practical examples. We will also highlight some obscure facts about the security features of Ghostscript and how the recent bypasses (CVE-2023-36664 and CVE-2023-43115) for these security features works.

Illustration of a ghost emerging from a document on a desk

What is Ghostscript and how is it used?

At its core, Ghostscript is an interpreter for the PostScript and PDF page description languages. It can be used to render or print documents expressed in these languages or convert between different formats. Today, it is often used for common PDF-related functions such as:

  • Merging of multiple PDF files
  • Splitting of PDF files
  • Generating previews and thumbnails of PDFs
  • Extracting text from PDFs

If you wish to use Ghostscript directly, it can be used as a command-line utility through the gs command. To interpret a PostScript file and display the rendered output to a window, it can simply be run as follows:

$ gs file.ps

Alternatively, an output device can be specified. For example, to convert a PDF or PostScript file to a JPEG image the jpeg output device can used:

$ gs -sDEVICE=jpeg -sOutputFile=image.jpg test.ps

However, Ghostscript is rarely used by end users directly, but many software applications utilize it to process PostScript or PDF files. Some examples include LibreOffice, Gimp, ImageMagick, Inkscape (through Cairo), or Evince (through libspectre).

What is PostScript and why is it Interesting for Attackers?

PostScript is a page description language similar to PDF. However, it was introduced way before PDF and is a fully featured programming language. This means besides describing documents or images, it also has some properties which are interesting for attackers: In PostScript, external files can be read, written to, or even executed.

In general, as Postscript is a stack-based language, function calls have to be read in reverse. First, the arguments to the function are pushed onto the stack and then the function is called. For example, to add two numbers using PostScript, the Ghostscript interpreter can be used as follows:

$ gs
GS> 10 20 add

The result of the operation is pushed onto the stack again. To print the current objects on the stack to the console, stack can be used:

GS<1> stack
30

As can be seen, the only object on the stack is the number 30, which is the result of adding the numbers 10 and 20.

Strings are encapsulated by round braces and variables are defined by using the def operator. The def operator takes a name and a value as arguments. Names are preceded with a slash. The following example defines the variable test_variable with the value “Hello World” and puts it on the stack, then prints the stack:

GS> /test_variable (Hello World) def
GS> test_variable
GS><1> stack
Hello World

The value in angled brackets <1> denotes the stack size. To open a file, the file function is used, which takes two strings from the stack:

  • The file name (e.g. /etc/passwd)
  • The file mode (r for reading, and w for writing)

In the following, we will include the argument -dNOSAFER when running the Ghostscript command-line utility to disable some security mechanisms (see next section for the meaning of NOSAFER). Later, we will see that these security mechanisms are not always present or can sometimes even be circumvented.

Using these basics, the knowledge from 30 year old forum posts and Adobe’s Reference Manual, we can open /etc/passwd and read the first 100 bytes:

$ gs -dNOSAFER
GS> /infile (/etc/passwd) (r) file def
GS> /buff 100 string def
GS> infile buff readstring
GS<2> stack
true
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nol

readstring returns a boolean which indicates if there were more bytes in the file than returned to buff, and the content of buff itself.

Similarly, content can be written to a file using writestring:

GS> /outfile (outfile.txt) (w) file def
GS> /outstring (Hello World) def
GS> outfile outstring writestring
GS> outfile closefile

Another interesting function is filenameforall, which iterates recursively over a specified directory and runs a procedure for each found filename:

GS> (test_directory/*) { stack pop } 4096 string filenameforall
test_directory/interesting_folder/also_interesting_file.txt
test_directory/interesting_file.txt

In the shown example a procedure is specified using { stack pop }, which prints the current filename from the stack to the console.

The Ghostscript interpreter also supports executing shell commands by using the %pipe% prefix when opening a file, for example to get the output of the id command:

GS> /buff 100 string def
GS> /cmd_output (%pipe%id) (r) file def
GS> cmd_output buff readstring
GS><2> stack
false
uid=1000(user) gid=1000(user) groups=1000(user),10(wheel)

All these functions are powerful tools when an attacker can run PostScript code. However, in most cases none of the above code snippets will work, due to security restrictions imposed by Ghostscript.

-dSAFER and its Exceptions

In the previous section we used -dNOSAFER when invoking the Ghostscript command-line utility. This command-line flag disables the security features of Ghostscript, which can be explicitly enabled using the flag -dSAFER, for details see the Ghostscript documentation. When -dSAFER is enabled, (almost) no files can be opened in the PostScript context, nor can commands be executed using %pipe%. Using the --permit-file-* flags, specific locations can be allowed however.

For various resources, Ghostscript allows the reading and writing of some directories, which are for the most part not that interesting for attackers. The following list includes the directories with read access in version 10.0.0:

  • %rom%Resource/CIDFSubst/
  • /temp/*
  • /tmp/*
  • %rom%Resource/*
  • %rom%Resource/Init/*
  • %rom%lib/*
  • /usr/local/share/ghostscript/10.00.0/Resource/Init/*
  • /usr/local/share/ghostscript/10.00.0/lib/*
  • /usr/local/share/ghostscript/10.00.0/Resource/Font/*
  • /usr/local/share/ghostscript/fonts/*
  • /usr/local/share/fonts/default/ghostscript/*
  • /usr/local/share/fonts/default/Type1/*
  • /usr/local/share/fonts/default/TrueType/*
  • /usr/lib/DPS/outline/base/*
  • /usr/openwin/lib/X11/fonts/Type1/*
  • /usr/openwin/lib/X11/fonts/TrueType/*
  • %rom%iccprofiles/*

Writing is for the following directories allowed:

  • /temp/*
  • /tmp/*
  • /dev/null

However, note that by default all files in /tmp/ and /temp/ can be read and written to (or more specifically files in the folders specified in the TMP or TEMP environment variables). This is not really documented, and could potentially contain interesting files depending on the system. Imagine for example a web app where users’ uploads are initially stored in /tmp before being processed.

-dSAFER has an interesting history, as it was only somewhat recently set to be enabled by default (since Ghostscript Version 9.50 released in 2019). At the same time the --permit-file-* flags were introduced. Unlike Artifex’s statement that

there has never been an exploit proven to allow hijacking of a user’s system

this meant that in many cases untrusted user input into Ghostscript could lead to remote code execution using %pipe%, as it may not be clear to developers that -dSAFER needs to explicitly be set for old versions of Ghostscript. Many projects can still be found in the wild where the flag is not explicitly enabled. For two examples where the flag was only recently added see Moodle and Mahara. This isn’t to blame the developers of these projects, but to show real world examples of why the default of the flag was dangerous for many use cases in old Ghostscript versions.

Recent bypasses of -dSAFER: CVE-2023-36664 and CVE-2023-43115

But even if -dSAFER is explicitly activated, there have been many exploits to bypass the security mechanisms in the past. One recent example, CVE-2023-36664 used Ghostscript’s wrong handling of path normalization. Normally, path normalization is only needed for regular files, for example to normalize

/home/user/testfolder/../otherfolder/testfile

to

/home/user/otherfolder/testfile

Path normalization is used for validation to see if the specified path is included in the list of allowed paths a user might use. The error was that %pipe% was handled in the same way:

%pipe%id;/../testfile

was normalized to testfile. This usually would not be an issue, as -dSAFER would also disallow reading files in the same directory. However, as mentioned above, there exists a special Ghostscript-internal directory named %rom%lib, which can be read even with -dSAFER enabled. Using this directory,

%pipe%id;/../%rom%lib/test

is normalized to %rom%lib/test which passes the validator, while the actual interpreter runs the non-normalized command.

The most recent example, CVE-2023-43115, used the inkjet device, where the print-server-executable/command can be arbitrarily specified by the user. Before 9.50 this could not be done after -dSAFER had already been set, ideally only via command-line arguments. For example, to write the output of id into a temporary file, the following command can be executed:

$ gs -dSAFER -sDEVICE=ijs -sOutputFile=out.pdf -sIjsServer="id > /tmp/rtpt" example.pdf
GPL Ghostscript 10.01.2 (2023-06-21)
Copyright (C) 2023 Artifex Software, Inc.  All rights reserved.
This software is supplied under the GNU AGPLv3 and comes with NO WARRANTY:
see the file COPYING for details.
GPL Ghostscript 10.01.2: Can't start ijs server "id > /tmp/rtpt"
**** Unable to open the initial device, quitting.
$ cat /tmp/rtpt
uid=1000(vagrant) gid=1000(vagrant) groups=1000(vagrant)

However, due to the changes to how -dSAFER works after version 9.50, the check was not properly ported to the new system, and it could be abused to run arbitrary commands even with -dSAFER enabled. For this, the device can be opened in PostScript using the setpagedevice function:

<< /OutputDevice /ijs /IjsServer (gnome-calculator) >> setpagedevice

More examples of such bypasses are CVE-2017-8291, CVE-2018-16802, CVE-2019-14811, CVE-2019-14812, CVE-2019-14813, CVE-2019-10216, CVE-2021-3781.

Processing PostScript in LibreOffice

As mentioned before, LibreOffice also uses Ghostscript to render PostScript files. This is particularly interesting, as it is used by many end users as a free alternative to the Microsoft Office suite. In addition to that, it is often used by backend systems to perform document conversions by running it in headless mode.

Almost all standard LibreOffice formats (odt, ods, odp, odg, even docx) support the inclusion of “Encapsulated PostScript” (EPS). This can be done using the menu option “Insert -> Image…”. The image will then be rendered by Ghostscript and displayed in the document, as can be seen in the source code. As a small fun fact, notice that instead of -dSAFER, -dPARANOIDSAFER is used, as in older versions (as in before version 7.04 in 2001) -dSAFER only prevented writing and file control operations, but not reading. To prohibit reading, -dPARANOIDSAFER had to be used additionally. After 7.04, it is synonymous with -dSAFER.

Also note that LibreOffice requires EPS files to include some headers in order to render it with Ghostscript:

%!PS-Adobe-3.0 EPSF-3.0
%%Pages: 1
%%BoundingBox:   36   36  576  756
%%LanguageLevel: 1
%%EndComments
%%BeginProlog
%%EndProlog

Here the bounding box can be adjusted to similar dimensions as the underlying LibreOffice document, and if needed to increase the resoulution of the embedded image. For large listings it might also be useful to adjust the LibreOffice document size itself.

Processing PostScript in PDF Documents

Until now, all code and security features were specific to the PostScript portion of Ghostscript, but even in cases where only PDFs are processed PostScript is involved:

The problem is in the interaction of PostScript and PDF specific to older Ghostscript versions. Until version 9.56.1, Ghostscript used a PDF interpreter which was written in PostScript.

One specific interaction between the two languages was the use of PostScript fonts, which are interpreted directly in PostScript. This was first publicised by neex in Ghostinthepdf who embedded PostScript code inside a PDF font object, which upon processing with Ghostscript is executed and the result is printed on the resulting page(s). The tool is easy to use and comes with the short PostScript print_version.ps, which prints the Ghostscript version on the page, if the file was processed with Ghostscript. This is a good initial indicator to find out if a system is vulnerable to this particular attack. To create a PDF document containing print_version.ps, the following command line can be used:

$ ./ghostinthepdf print_version.ps print_version.pdf

To test the behaviour locally, run the following command (you will need an older version of Ghostscript):

$ gs -sDEVICE=pdfwrite -dNOPAUSE -dBATCH -o output.pdf print_version.pdf

The result can be seen in the following, where on the left is the version which has not been processed, and on the right the one which has been processed by Ghostscript:

Screenshot of two documents opened side-by-side in Evince, left one showing the text 'No Ghostscript / error', right one showing 'Ghostscript detected, Revision: 926, Rev date: 20181120'

However, this only works in the old PDF engine, which was written in PostScript and has been replaced with a version written in C, mitigating this particular issue in newer versions of Ghostscript.

Practical PostScript Examples

Without the restrictions of -dSAFER, exploiting Ghostscript can be done simply with %pipe% and running shell commands, such as a reverse shell to channel data back to the attacker. If we have no network access however, we need to extract the command output in another way if possible, for example by printing it to the page. Moreover, if -dSAFER is activated, most of the time we have no other choice than writing our own PostScript code to analyze the underlying system, and relying on having access to the output of the processed file. To accommodate both scenarios, in this section multiple practical examples will be shown, which will ease the initial information gathering, listing of files, printing of files (including command output using the %pipe% prefix), and writing of files.

The scripts in this section can be found in our GitHub repository.

To test these scripts, we used ps2pdf to process them into a PDF file, or Evince to directly open the PostScript or the produced PDF. ps2pdf is bundled with Ghostscript and supports the same flags, so -dNOSAFER or --permit-file-* can be tested.

In all scripts, variables can be specified in the first few lines of the scripts, for example charactercount to specify the amount of characters per line.

Version and -dSAFER Information Gathering: print_version.ps

Before starting an attack, a useful information for an attacker is which Ghostscript version is used and if -dSAFER is enabled. We already mentioned Ghostinthepdf’s print_version.ps script, which displays the used Ghostscript version. We extended the script to include a check for the -dSAFER flag and if it is vulnerable to CVE-2023-36664. The result can be seen in the following screenshot, where the script was included as an encapsulated PostScript image in a LibreOffice .odt file, as well as opened directly using Evince:

Screenshot showing two documents side-by-side, left one is showing a LibreOffice Writer document with the text 'Ghostscript detected, Revision: 10012, Rev date: 20230621, Product: GPL Ghostscript, dSAFER is enabled, CVE-2023-36664 patched', the right document is opened in Evince, showing the sam text as the LibreOffice document

File Listing: list_files.ps

When -dSAFER is activated and thus no shell commands can be executed, the remaining interesting checks for an attacker are files and folders which can be accessed directly in PostScript, i.e. files in /tmp/ and /temp/ or files exposed by the --permit-file-* flags. However, except for the known default locations /tmp/ and /temp/, the files exposed by the --permit-file-* flags are usually not known to the attacker, as the list is cleared after Ghostscript’s initialization.

To get a list of files in the known locations, the following script is similar to the filenameforall snippet above, but includes an error output and uses whole pages to display one file per line. Note however, that filenameforall will return nothing (not even an error) if the directory is not in --permit-file-* nor /tmp/ or /temp/.

The target location can be specified in the variable target_directory.

Screenshot of a document opened in Evince, showing a file listing of numerous files under the path '/tmp/'

File Extraction: print_file.ps

After having found the file we want to extract, we have to think about how we want to extract it. In print_file.ps, we provide multiple functions for printing files onto the page: In order to print the content of files, the functions PrintFileWithNewLines or PrintFileWithOutNewLines can be used, e.g.:

(/etc/passwd) PrintFileWithNewLines

This produces the following output (with -dNOSAFER):

Screenshot of a document opened in Evince displaying the contents file '/etc/passwd'

PrintFileWithoutNewLines is mostly important for reading files containing zero bytes, for example /proc/self/cmdline, as readline (used in PrintFileWithNewLines) crashes when reading unexpected zero bytes. readline will also crash if the lines of the file are too long to fit inside the buffer. You can increase the charactercount variable in that case, or use PrintFileWithoutNewLines as an alternative.

For binary files, some encoding is required for properly extracting the contents. For this, we provide a function for Base64 encoding of binary data (base64Encode), and a helper function to print encoded data on the page (PrintFileBase64).

To use the script, comment out the preferred method of reading the file and adjust the filename near the end of the file. Note that command output can also be used as a file, for example printing the output of the id command can be done using:

(%pipe%id) PrintFileWithNewLines

To extract the file from a processed PDF file which used the PrintFileBase64 function, the libpoppler tool pdftotext or Ghostscript itself can be used:

$ pdftotext processed.pdf - | tr -d '[:space:]' | base64 -d > out.bin
$ gs -q -dNOPAUSE -dBATCH -sDEVICE=txtwrite -sOUTPUTFILE="%stdout" printfile.pdf | tr -d '[:space:]' | base64 -d > out.bin

File Writing: write_file.ps

If we want to manipulate files on the system, the functions in write_file.ps might be interesting. Similar to file extraction, a Base64 decoder was implemented in the base64Decode function to include arbitrary files in the PostScript file itself and write them to a specified path. This is analogue to the file writing example above, but uses write to write all base64-decoded bytes to the file:

/outfile (/tmp/testfile.txt) (w) file def
(SGVsbG8gV29ybGQhCg==) base64Decode { outfile exch write } forall
outfile closefile

To use the script, adjust the Base64-encoded text near the end of the file.

Who is vulnerable

The following table lists the versions of Ghostscript in commonly used LTS distributions (as of 11 October 2023):

Linux Distribution End of Life Ghostscript Version -dSAFER default Ghostinthepdf
Ubuntu 14.04 April 2024 9.26 NO YES
Ubuntu 16.04 April 2026 9.26 NO YES
Ubuntu 18.04 April 2028 9.26 NO YES
Ubuntu 20.04 April 2030 9.50 YES YES
Ubuntu 22.04 April 2032 9.55 YES YES
Debian 10 June 2024 9.27 NO YES
Debian 11 June 2026 (tbd) 9.53.3 YES YES
Debian 12 (tbd) 10.0.0 YES NO
CentOS 7 June 2024 9.25 NO YES
CentOS Stream 8 AppStream May 2024 9.27 NO YES
CentOS Stream 9 AppStream May 2027 9.54 YES YES

As can be seen, many distributions do not even set -dSAFER as default, which means a simple user error, i.e. not using -dSAFER, makes an application susceptible to exploiting. Moreover, most distributions also allow PostScript to be included in PDFs through Ghostinthepdf, widening the attack surface to PDF processing.

Conclusion

Developers and users need to be aware of the security implications of using Ghostscript, specifically older versions of it. Especially when misconfigured older versions of Ghostscript are used, simply processing documents could lead to code execution. Many of the popular distributions ship these older versions of Ghostscript, making them potentially vulnerable to the misconfiguration. Furthermore, even with security parameters set, Ghostscript allows its PostScript interpreter to access the temporary path of the system, which could potentially contain files interesting for attackers.