Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / IIS

Server-side fix for the Universal PDF XSS Vulnerability

4.50/5 (5 votes)
24 Apr 20075 min read 1   291  
This article describes a server-side fix for the recently discovered vulnerability in the PDF reader plugin by Adobe.

Introduction

Early this year, two guys broke the news of a critical XSS vulnerability found in the popular software Adobe Acrobat Reader. Being free and de facto, the PDF reader coupled with the abundance of pdfs online, this vulnerability has the potential to wreck havoc in a big way.

Essentially, the vulnerability provides yet another way for an attacker to execute JavaScript in another user's browser when the user follows a specially crafted URL to a PDF file. By adding JavaScript commands to parameters in the URL, attackers can steal cookies and sensitive data, change the appearance or behavior of the site, and redirect users off-site in phishing attacks.

This vulnerability has been much talked about and there are plenty of resources online that you can look for if you want the details on how it works and what kind of damage it can bring. Here are a few links for reference:

Not long after the story broke, Adobe and the popular browser makers have issued fixes/updates for their software. Unfortunately, we all know that much time (years maybe?) is required for these updates to propagate to the masses, hence it is still crucial for webmasters to put up some form of defence from the server side. As a webmaster myself, I was tasked to fix this up on my company websites. The following article describes the fix which I've implemented. Source code and binaries (for the lazy) are provided above.

How it works

The core of the issue lies with the opening of the PDF in the Acrobat Reader's browser plugin; The plugin offers an ill-conceived "feature" that launches custom JavaScript code that is passed via the URL.

Solution #1

The most obvious way to fix this problem is to always get the user to download the file instead of opening it in the plugin. To do that, we simply need to change MIME type for PDF files and add a custom header to force the download.

Content-Type: application/octet-stream
Content-Disposition: attachment; filename=xxx.pdf

Both of these headers instruct the browser to download the file instead of opening it in the vulnerable Adobe plugin.

Here are instructions for adding these headers to the server response for PDF documents in Apache and IIS.

Apache Configuration

Add these lines to the httpd.conf file inside the <directory>tags.

AddType application/octet-stream .pdf

<Files *.pdf>
    Header add Content-Disposition "attachment"
</Files>

IIS Configuration

Change the MIME type to "application/octet-stream":

  1. Start Computer Management. (Right click My Computer, select Manage)
  2. Right click on the Services and Applications > Internet Information Services (IIS Manager)
  3. Select Properties > MIME Types
  4. Look for .pdf. The MIME type should be application/pdf. Change this to application/octet-stream
  5. Click OK twice

Add the Content-Disposition header (this needs to be done on each directory or for each PDF file individually):

  1. In the IIS Management tool (not in Windows Explorer), select a directory with PDF content or an individual PDF file
  2. Right-click on the directory or file
  3. Select Properties
  4. Click the HTTP Headers tab
  5. In the Custom HTTP Headers section, click Add
  6. A dialog appears. In the Custom-header name field enter Content-disposition. In the Custom-header value field, enter attachment
  7. Click OK twice
  8. Restart IIS for the changes to take effect

Do note that this is not a foolproof solution, but changing the HTTP response headers will go a long way toward reducing the risk to users of your site. Although this works in current versions of the more popular browsers, it is possible that some browsers could ignore these headers and open the PDF with the Adobe plugin anyway.

Solution #2

If forcing the user to download the PDF is not your cup of tea, and you have to let your users view the PDF in place in the browser, then here is a solution that lets you have your cake and eat it too. It is however for IIS only since this is after all a .NET site.

About the code

The idea behind is to implement a filter on the web/app server that will authenticate the request and strips any JavaScript from the URL before streaming the file to the user. If we cannot authenticate the request, we will force the user to download the file.

The snippets of code below show the key functionalities:

C#
public class PDFXSSFilter : System.Web.IHttpHandler
{
    ...

    public void ProcessRequest(HttpContext context)
    {
                ...
                tokenvalue = request.QueryString["p"];
                ...

            // Check if the token value is defined in the URL
            if (tokenvalue == null)
            {
                // If not, calculate the secure value
                string x = context.Server.UrlEncode
            (EncryptData(request.ServerVariables["REMOTE_ADDR"]));

                // Now build the redirect URL
                string redirectURL = 
            request.Url.AbsolutePath + "?p=" + x + "#a";

                // Now redirect them to the calculated URL
                context.Response.Redirect(redirectURL, false);
            }
            else
            {
                // Remove the javascript behind first
                tokenvalue = tokenvalue.Split("#".ToCharArray(), 2)[0];
                // Decode the token next
                tokenvalue = 
            context.Server.UrlDecode(tokenvalue).Replace(' ','+');
                // Check if token data is valid
                if (ValidateData(tokenvalue, 
            request.ServerVariables["REMOTE_ADDR"], context))
                {
                    // If so, transmit as PDF
                    context.Response.ContentType = "application/pdf";
                }
                else
                {
                    //Otherwise, transmit as an octet-stream 
            //(i.e., force download)
                    context.Response.ContentType = "application/octet-stream";
                    context.Response.AddHeader("Content-Disposition", 
                    "attachment; filename=" + pdfFileName);
                }

                //Transmit the file regardless
                context.Response.TransmitFile
            (context.Server.MapPath(request.FilePath));
            }
        }

        ...
    }
}

Using the code

First thing you need to do is to compile the code and drop it into the /bin directory of the ASP.NET application. For those who are lazy, you can grab the binaries in the link above.

Next, configure IIS to call the ASP.NET application for the .pdf extension:

  1. Open the IIS Admin tool
  2. Right click on the site/virtual directory you wish to implement this on
  3. Select the Home Directory tab (or Virtual Directory depending on the type of site)
  4. Under the Application settings section, click on the Configuration button
  5. Click Add to create a new extension type
  6. Under Executable, click on Browse and look for the appropriate aspnet_isapi.dll (must be v2.0 and up)
  7. For Extension, enter .pdf
  8. Under Verbs, you can leave this as All verbs - if performance is an issue, you may try limiting this to GET and POST
  9. Click OK
  10. Click OK to close the Application Configuration window
  11. Click OK to close the site properties

Finally, you will need to make the following changes to the web.config file:

  1. In the <system.web> section, look for the <httpHandlers> section. If one does not exist add it now.
  2. Add the entry for the PDFXSSFilter to the httpHandlers section. Your configuration may look something like this:
    XML
    <httpHandlers>
       <add verb="*" path="*.pdf" type="PDFXSSFilter,PDFXSSFilter" />
    </httpHandlers>
  3. Now add an entry for the TokenEncryptionKey to the <appSettings> section. Make sure to change this key for your site or it will defeat the purpose of adding this code.
  4. Now add an entry for the TokenTimeout to the <appSettings> section - this allows you to specify the timeout value of the token. Your configuration may look something like this:
    XML
    <appSettings>
       <add key="TokenTimeout" value="10" />
       <add key="TokenEncryptionKey" value="ABCDEF" />
    </appSettings>

Acknowledgement

Solution #2 presented in this article is based on code by Mike Metzger.

History

  • 24th April, 2007 - First public release

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here