Cenzic 232 Patent
Paid Advertising
web application security lab

Lessons Learned From Adobe PDF XSS Patching

The last two days were pretty exciting from a web application security perspective. It’s only been the first few days of the year and we have easily found one of the worst holes in it - and then proceeded to make it worse. The Adobe PDF reader XSS was more than just a simple flaw in a commonly used software. The hole was a chance to analyze our own reaction to the problem. There were a lot of false steps here, and I think we should look carefully at a lot of the mis-perceptions and kneejerk reactions.

The first and most common mis-perception was that the anchor tag was sent to the server. That caused a rash of comments like “I’ll just stop it with my web application firewall” or “I’ll block it with an apache rule” etc. That’s dangerous thinking. No one bothered to test to make sure that would work before telling others that that’s what they should do. The only people who were the word of caution were people who already knew that wouldn’t work. That alone scares me.

Next was the “you can detect badguys using mod_rewrite” people. Here are two sample rules:

SetEnvIf Request_URI “\.pdf$” requested_pdf=pdf
Header add Content-Disposition “Attachment” env=requested_pdf

and

RewriteEngine on
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !^http://([-a-z0-9]+\.)?example\.com[NC]
RewriteRule .*\.(pdf)$ http://www.example.com/images/noexternal.gif [R,NC,L]

Be careful using either of those. REQUEST_URI can contain anything: http://example.com/file.pdf?whatever#vectorgoeshere For that example the request URI will be ..pathto..file.pdf?whatever which does not match “\.pdf$”. Likewise the second one has issues, including the fact that referrers are not always present (Zonelabs Zone Alarm Pro, and both Norton Internet Security and Norton Personal Firewall). Also, referrers are spoofable using Flash.

Next was the token crowd. This probably has the most legs in terms of being able to still allow the customer to retain the user experience of viewing the PDF doc on the website. However it has one fatal flaw. Here’s the algorithm:

IF the URL doesn’t contain token_query, then:
calculate X=encrypt_with_key(server_time, client_IP_address)
redirect to file.pdf?token_query=X
ELSE IF the URL contains token_query, and
decrypt(token_query).IP_address==client_IP_address and
decrypt(token_query).time>server_time-10sec
serve the PDF resource as an in-line resource
ELSE
serve the PDF resource as a “save to disk” resource via a proper choice of the Content-Type header (and/or an attachment, via Content-Disposition).

Now let’s ignore the potential issues with Content-disposition browser compatibility for a moment and think about the basic premise that this pseudo code is working on. It is making an assumption that an attacker will never have the same IP address as the user. That’s just not the case. This isn’t as rare as just a few universities and ISPs. This also happens in lots of corporate networks (rogue user on the internal network), it happens with lots of internet cafe’s, it happens with AOL (~5MM users) and it happens with TOR users. So while, yes, I agree it is better than nothing it is hardly a rock solid solution for anyone on a shared IP.

In the end, Adobe issued an alert and told everyone to upgrade (which everyone should always do, but as we all know they rarely will). Browser companies didn’t chime in at all, even though they could easily fix the issue. The network security folks changed the content type. The application guys told us how to stop PDFs from working in browsers. And at the end of the day, where has that left us? There are still a lot of exploitable browsers out there. Like I said, it has been an interesting last few days.

23 Responses to “Lessons Learned From Adobe PDF XSS Patching”

  1. elitemofo Says:

    has anyone mentioned not allowing access to the PDFs directly via a GET request? OR

    Using something like this instead?

    http://www.example.com/displaypdf.php?id=1

  2. RSnake Says:

    You can create a POST using a JavaScript document….click(); event against a form. I’m not sure what you mean by the id, but if the bad guy can see the ID they can force the user to go to the same id.

  3. elitemofo Says:

    id = 1 (server side code says 1 = some PDF internal, either protected dir or database).. how could you then pass the JS code into the DOM?

    You see this a lot with pages counting the number of times a certain file is accessed or preventing direct linking to the PDF etc.

  4. RSnake Says:

    If it outputs the PDF, there’s no difference. If you are talking about it then doing a 301 redirect or something, that won’t matter, because modern browsers carry anchor tags with them after a redirection. I think you aren’t quite getting what a DOM based exploit is. DOM based means it is not interpreted by the server. It is interpreted only by the page it is on. In this case an anchor tag is not visible to the server, only the application (normally JavaScript but in this case PDF) can see or deal with it. As opposed to a reflected XSS exploit where the server must return something based on user input. In this case it doesn’t for the exploit to work.

  5. Sylvan von Stuppe Says:

    Right on - clients do need to upgrade or use something other than Acrobat….

    However, I was one of the people recommending a transaction token. But I’ve never thought of using the client IP address with a token. The idea in a token is the way Struts handles it. On the page where you’re going to get the PDF, you set a really big random number, put it in the session, and put it in a hidden form element on the client. When they submit the form to request the PDF through the bit tunnel, the tunnel checks the form value to make sure it matches the one in the session. If they don’t match, you don’t even send a PDF - you send them back to the request page (or something else).

    This is also a fix to ensure that they requested the PDF from your site. The huge downside to this, however, is that you would no longer be able to download “naked” pdf’s - they would HAVE to go through the bit tunnel, which would require a lot of rewriting and moving the pdf’s to a non-web accessible area.

    So the token idea that has legs oughtn’t depend on client IP address at all - it should depend on a token that’s checked in the session and that the client carries it as well.

  6. anty Says:

    I wrote a short snipped in PHP to test my idea to prevent the flaw server-side. The idea is to force a browser to download the pdf-file.
    Here is my PHP snippet, you can test it here (for a while until I take it down): http://www.anty.at/undone/pdftest/index.php

  7. Jungsonn Says:

    Problem is RSnake, I did test the apache mod_rewrite rule. And - thats the strangest part- it worked when I tested it. ->It should not do that! (now I know) but it really happened, otherwise I would not posted it. Why it happened i do not know, but I was able to redirect pdf# files to a 404. Now, I guess it had something todo with caching, because it should be imposssible.

    But I must admit I forgot it wasn’t being send to the server, can slap myself for that one, but it doesn’t solve a thing.

  8. Kanatoko Says:

    >It is making an assumption that an attacker
    >will never have the same IP address as the user.

    Anti-DNS Pinning can be used to break this assumption.

  9. anty Says:

    @ Jungsonn: I suggested the same thing in my blog, after a day someone told me that it will not work. Suddenly I knew why PHP didn’t show me the full URI either :D - You are not alone.

  10. RSnake Says:

    @Kanatoko - DNS pinning has nothing to do with hiding your IP address, it has to do with making the DNS point to two different locations. So unless you are saying use a hostname instead of an IP address that’s not relevant to this conversation, unless I’m missing something.

    @Jungsonn - I have been thinking a lot about this (how it might have worked at some point). Another way you may have seen this is if you were using a proxy or manually typing in the request through telnet or something. If you weren’t using a browser that knew not to send it, you may have seen the anchor tag because it did in fact get sent. Although this would never happen in the real world, it would have happened in tests since your tests might not have mirrored reality. Just a thought. Who knows?

  11. Jungsonn Says:

    Yeah I guess so, I was busy with different rewrite rules, I think I spend about 4 hours on it. The example I gave before worked for me about 5 times after eachother. So I thought I had the awnser., not knowing this is ignorant. After you comment I checked again and it didn’t work anymore. I used many different ways, and uploading it to my own server i work pretty fast so the time differece between a modification can be onle a few sec. So I assume it was caching.

    Man it’s so stupid! Yeah I used anchors lot in html pages, and knew it isn’t send cause in HTML is nothing send (stateless) I just was focussed at that darn javascript and forgot all about it.

    ^^

  12. Kanatoko Says:

    Please suppose EVE is the attacker, ALICE is the target.

    With Anti-DNS Pinning, EVE can make ALICE to send HTTP request using XHR and *read response headers and body*.

    So EVE’s script can get token_query generated for ALICE’s IP address.

  13. lpilorz Says:

    I think Kanatoko is partially right… AFAIK, using anti-DNS pinning techniques allows reading cross-domain content similarly to mhtml-redirect in IE, but it also gives response headers. It does not send proper cookies, so session-based token would be unreadable, but those IP-connected will be. However, i’m not sure if it will be sufficient to read a token which only appears in a redirect. I’m not able to provide any testcase at the moment.

  14. Kanatoko Says:

    lpilorz
    That’s what I meant to say. Thanks.

    >i’m not sure if it will be sufficient to read a token
    >which only appears in a redirect.

    Location header is readable on IE( + Apache or IIS), with the following XHR code.

    req.open( “HEAD\x09/exists.txt\x09HTTP/1.1\r\nHost:foo\r\n\r\nGET”, ‘http://bar/redirect.cgi’,true);

  15. lpilorz Says:

    Opera response:
    http://my.opera.com/hallvors/blog/2007/01/06/patching-adobe-s-hole

  16. RSnake Says:

    Ahh… now I see what you’re saying, that does make sense. (I was thinking of DNS pinning in the opposite direction but yes, now I see what you’re getting at). Interesting. That would uncover the token nicely. Just another reason IP based tokens aren’t the answer.

  17. Jungsonn Says:

    if anyone has missed this on the forum:

    Try this out:
    http://www.jungsonnstudios.com/blog/?i=55&bin=110111

    It looks like I succeded into preventing it from the server side, and why I think that it works. But, It’s my take on it and my own server. I really hope there is someone that can confirms this, anyway I would like to hear about it so I can fully say that it works.

  18. h0rk.com » How to Write SkyNet AJAX Worms Says:

    […] After the recent universal pdf xss came out, we all learned a few lessons and I found the GNUCitizen page and this interesting piece of writing on the front page. God that attack vector is sexy, and yet I continue to write in my blog while I have the very web site that writes about the theory open in another tab. When will I learn! […]

  19. RSnake Says:

    Kanatoko, your DNS Pinning concept definitely looks like it will work. Here’s the entire attack scenario as I wrote to Bugtraq:

    1) Alice visit’s Cathy’s malicious website www.malicious.com that points to 123.123.123.123 (Cathy’s IP).
    2) Cathy uses an XMLHTTPRequest to tell Alice’s browser to visit
    www.malicious.com in a few seconds and times out the DNS entry immediately.
    3) Alice’s browser connects to www.malicious.com but Cathy has shut down the port. The browser DNS pinning no longer points to 123.123.123.123 and instead it asks Cathy’s bind server where the new IP of www.malicious.com is.
    4) Cathy’s bind server now points to 222.222.222.222 (Bob’s IP).
    5) Alice’s browser now connects to 222.222.222.222 and reads the token from that page (cookie, redirect, or whatever) via XMLHTTPRequest and forwards that information to Cathy’s other website www2.malicious.com.
    6) Cathy reads Alice’s token and then forwards Alice’s browser to Bob’s server (not the IP, but the actual address) with Alice’s token (if the token is a cookie we can use the Flash header forging trick). Alice’s cookie is not yet compromised because she is looking at a different website, and her browser does not send the cookie, yet.
    7) Alice’s connects to Bob’s server with the PDF anchor tag and the
    correct token to view the PDF. Since the token is bound by IP the token works.
    8) Alice executes Cathy’s malicious JavaScript malware in the context of Bob’s web server and sends the cookie to www2.malicious.com where it is logged.

    It’s ugly, but it should work, in theory. Clear as mud?

  20. Kanatoko Says:

    Yes, this is the scenario.
    It should work.

    Thanks :)

  21. maluc Says:

    so .. let me get this straight.. as i never researched the results well.

    anti-dns pinning allows for global read access via XHR ? Looks as thought it’ll require a specialized server-side application .. but i’d be happy to write one if that’s the case.

    second question, can the bind server have the same IP? ie: 123.123.123.123?

  22. Jungsonn Says:

    FYI

    Okay, I got some feedback, and it looks like the proposal of the htaccess works:

    .htaccess:

    AddType application/octet-stream .pdf
    AddType application/octet-stream .PDF

    :D

  23. RSnake Says:

    Maluc, the bind server can have the same IP. The only trick here is that the server must be “down” or the port must be down so that the browser asks for the DNS again. You could theoretically run everything off of one server but that would probably require that you have two webservers running on different ports so you could block one to that IP address using ipchains.