Search code examples
securityweb-applicationscookiescsrf

How to prevent embedded requests to my webpage?


I have a webpage that tracks my users whenever they visit the page. It is a "profile page" whereby if someone visit your "profile page", his visit would be tracked and you would be notified.

This is similar to stackoverflow's profile page which keeps track of profile views (although the details differ). Whenever a unique visitor visits your profile page, the profile views counter will be incremented.

I authenticate users using a randomly generated token stored in their browser's cookies. And this is where I have a big CSRF problem because any random website could simply visit the webpage on behalf of my users, without them knowing. For example, an attacker's webpage could simply have an embedded iframe or image which points to my webpage:

<img src="http://mysite.com/profile-page">

or

<iframe src="http://mysite.com/profile-page"></iframe>

Whenever my users visit the attacker's webpage, they would be visiting someone's profile page without them knowing it (the visit is then logged).

Let me use a concrete example to demonstrate my point:

Many websites these days allows users to post image URLs. The user can post an image like this:

 <img src="https://stackoverflow.com/users/632951/pacerier">

The src of the image tag really isn't an image file, but the browser will still make the request and display an empty image:

enter image description here

This means that the attacker had just successfully made a request to https://stackoverflow.com/users/632951/pacerier on the user's behalf, most likely without the user even knowing it.

As mentioned in Preventing CSRF and XSRF Attacks, we could add some hidden input fields and a form saying "Are you sure you want to request this page?". The disadvantage is that every genuine user that requests the page would now need to click the button Yes, Confirm Request Page before they can visit the webpage, which would severely break usability. I don't want to force users to click Confirm for every page that does tracking.

Are there other more user-friendly ways to prevent CSRF get requests?

How can we tell the browser not to request pages from a domain http://mysite.com if it is an embedded page (browser's url bar is not showing the requested page, which means it's an embedded image, frame, iframe, etc)?


Solution

  • Here's one way that ought to be fairly simple and reliable:

    • Don't count the requests made for the page, but rather requests made by something on the page (e.g. AJAX requests, and/or requests for an image on the page). This will take care of img hacks, since browsers won't actually try to render an HTML page as an image, and so won't run any JavaScript or load any (sub)images on the page.

    • Add an X-Frame-Options: DENY header to the page, so that browsers won't render it in an iframe either. For older browsers (or just as part of a belt-and-suspenders approach), you can also have your JavaScript check that window.parent == window before sending the AJAX confirmation.

    • Finally, to protect the (AJAX / image) requests that you do count from spoofing, include a unique, unpredictable token that is valid only once in each of them. One way to do that is to generate the tokens randomly and save them in a database (or in session storage) until they're received back (or get too old).

      Alternatively, if you don't want to store the tokens in a database, you could calculate a MAC of a timestamp (and e.g. the user's IP address) and send both with the request, discounting any requests with a wrong MAC or a stale timestamp.