ModSecurity: Features: PDF Universal XSS Protection
The GNUCITIZEN website gives a pretty concise overview of this probem. The Universal PDF XSS issue was discovered by Stefano Di Paola and Giorgio Fedon and it was presented at the 23C3 security conference. This vulnerability obviously affects the Adobe Acrobat Reader which is a widely used software among business, non-business organizations and individuals. By abusing Acrobat’s open parameter features well protected sites become vulnerable to Cross-site scripting attacks if they host PDF documents. This is pretty bad and unless you update your reader or change the way your browser handles PDF documents, you may get hacked quite badly. This issue is very serious. Fortunately, ModSecurity 2.5 users can easily and effectively mitigate this issue by using the new PDF protection directives.
Example (taken from Amit’s paper):
<HTML><TITLE>Welcome!</TITLE> Hi <SCRIPT> var pos = document.URL.indexOf("name=") + 5; document.write(document.URL.substring(pos,document.URL.length)); </SCRIPT> </HTML>
Normally invoked with:
Does not work equally well when invoked with:
In December 2006 Stefano Di Paola and friends speak about the universal XSS flaw in the Acrobat Reader plug-in on Windows. The world found out when the advisory went out on January 3rd, 2007. (The flaw was already fixed in Reader v8 in early December 2006.) The word spread like fire among security bloggers (pdp) and on the mailing lists. RSnake discovered the attack can be used against PDF files hosted on the local filesystem.
For many people this was the last straw. They acknowledged that the end of the World is near...
Uh-oh, notice the # character!
The potential for damage is there, all right, but where are the exploits?
In many ways this is a simple problem to solve. Just upgrade the client-side software as:
Alternatively, you can configure the browser not to open PDF files at all, however we know many users will not upgrade.
The most important point to understand is this - it is not possible to detect attack on the server. Therefore our only option is to “protect” all PDF files no matter if they are being attacked or not. Proposed mitigation revolves around three ideas:
This can be done via header modification in web server configuration (all files) or application (dynamic files only).
Amit Klein proposed a defence mechanism, which was subsequently discussed and refined on the mailing lists. While searching for a better solution many people noticed that it is possible to overwrite the attack payload using redirection and a harmless fragment identifier.
If we get:
We redirect to:
But how do we tell we’ve already redirected the user? If we don’t we’ll just end up with an endless loop. We can use one-time tokens as flags.
Is now redirected to:
If we generate a completely random token then we’d have to start keeping state on the server (i.e. token repository, garbage collection of expired tokens).
Alternatively, we can store state on the client.
Unfortunately, our solution is not foolproof yet as the attacker can simply generate a number of tokens to use against his victims. We have to associate tokens with clients somehow. It would be nice to use the application session but not all sites have them.
But what happens if the IP address changes (user behind a proxy)?
There are still holes in our solution! If the attacker shares the same IP address as the victim (proxy, NAT) he will be able to obtain tokens to use in attacks.
At best, we can prevent mass-exploitation, however focused attacks remain an issue.
A foolproof protection mechanism would:
And it would only work on:
Not usable as a general purpose protection method.
Most protection mechanisms rely on detecting the PDF extension in the request URI. Let’s have a look at some request types:
To catch the last three cases we have to inspect the outgoing headers:
There is a potential performance issue if we redirect a GET request based on what we see in the response headers.
There is a way to solve this but it is a bit of a strech:
No; all redirections are to a GET.
Well, strictly speaking, there is a way:
But that would be bit too much.
There may be others... Let us know if you find any.
Description: Enables the PDF XSS protection functionality.
Once enabled access to PDF files is tracked. Direct access attempts are redirected to links that contain one-time tokens. Requests with valid tokens are allowed through unmodified. Requests with invalid tokens are also allowed through but with forced download of the PDF files. This implementation uses response headers to detect PDF files and thus can be used with dynamically generated PDF files that do not have the .pdf extension in the request URI.
Description: Configure desired protection method to be used when requests for PDF files are detected.
Possible values are
Description: Defines the secret that will be used to construct one-time tokens.
You should use a reasonably long value for the secret (e.g. 16 characters is good). Once selected the secret should not be changed as as it will break the the tokens that were sent prior to change. But it's not a big deal even if you change it. It will just force dowload of PDF files with tokens that were issued in the last few seconds.
Description: Defines the token timeout.
After token expires it can no longer be used to allow access to PDF file. Request will be allowed through but the PDF will be delivered as attachment. Default:
Description: Defines the name of the token.
The only reason you would want to change the name of the token is if you wanted to hide the fact you are running ModSecurity. It's a good reason but it won't really help as the adversary can look into the algorithm used for PDF protection and figure it out anyway. It does raise the bar slightly so go ahead if you want to. Default:
Let's run a quick test with the following ruleset:
# PDF Protection SecPdfProtect On SecPdfProtectTimeout 10 SecPdfProtectSecret 3790918688a87dc76496a5de6811ac1f SecPdfProtectTokenName PDFPROTECT
These rules enable the PDF proection mechansims. Now, if a client requests a PDF file on the protected site, their original request:
GET /documents/sample.pdf HTTP/1.1 Host: www.example.com User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:126.96.36.199) Gecko/20071127 Firefox/188.8.131.52 Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive: 300 Connection: keep-alive
Is intercepted by the SecPdfProtect directive, and a response similar to the following would be sent:
HTTP/1.1 307 Temporary Redirect Date: Sun, 30 Sep 2007 07:51:19 GMT Location: /documents/sample.pdf?PDFPROTECT=0f0cecf605568c08e7cb99d7cbeff8164d571d7d|1191138689#PDFP Content-Length: 308 Keep-Alive: timeout=5, max=50 Connection: Keep-Alive Content-Type: text/html; charset=iso-8859-1 <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"> <html><head> <title>307 Temporary Redirect</title> </head><body> <h1>Temporary Redirect</h1> <p>The document has moved <a href="/documents/sample.pdf?PDFPROTECT=0f0cecf605568c08e7cb99d7cbeff8164d571d7d|1191138689#PDFP">here</a>.</p> </body></html>
Once the client's browser followed the 307 Temporary Redirect location, ModSecurity would then validate the PDFPROTECT hash data. If the hash was still valid, then the request would be allowed to continue. If the hash value was not valid (either due to a mismatch in the client IP address or that it is outside of the allowed timeout setting) then it would be rejected.
There is no perfect solution - only a trade-off between security, usability, and performance. Isn't everything? Flaws to be aware of: