10

My bookmarklet can be called from any website and basically allows the user to insert a row into his collection from afar - if he is logged in.

Now I want to enable CSRF protection for my site and since a bookmarklet is basically non-forged cross site request, I thought about how I could tell it apart from forged ones.
It's not a high-security environment, but I'm also interested in principle.

I thought I had a way to do it figured out, but then realised that it had problems galore.

Original idea

  • generate a random key that is included in the bookmarklet-link. The key's hash is saved in the database. The random key allows access ONLY to the privilege of insertion into this collection and can't be used anywhere else.
  • the bookmarklet loads a longer script from my server, so I could supply a CSRF prevention token this way
  • require the user to be logged in

Problems

  • If I have the bookmarklet key, do I need counter-CSRF tokens?
  • Is there any way that I could protect the bookmarklet key if the user clicks his bookmarklet on a malicious website?
  • I don't want username and password to be stored in the bookmarklet link, because anybody who has access to the computer would get the password as well then, so I decided on the random key.
    • but if I store only the hash, I cannot generate the same bookmarklet link twice, so when the user wants a bookmarklet in another browser/computer he tediously has to import the link from the old one or break support for the old one.
    • but I shouldn't store the cleartext key, because someone who gains access to the database could use this key to insert rows into accounts that don't belong to him.
    • Possible solution I could ask the user to provide his password anytime he creates the bookmarklet and hash the password many times and put that hash in the URL and put the hash of that hash my database. But of course this opens much worse security holes.
      • I could do this with something like "Mother's maiden name" instead
      • I cannot use bcrypt for hashing because of the random salt, right? What hash function would be right? Or would you dismiss the whole idea?
  • If I leave the bookmarklet key out, a malicious website could simply embed the bookmarklet and extract a valid CSRF token from it, right?

Better ideas? Or can't you have CSR without the F?


Edit, specified use case

One thing I simply didn't think about, is the usage of an iframe as suggested by Sripathi Krishnan.

I had not specified my use case, so yes, an iframe is a valid solution to the aforementioned problem.

However, actually my bookmarklet at the moment does do some basic interaction with the website at runtime (meaning the form is there already and the user can change his selection in the website DOM which should change the form). I'm ready to dismiss this functionality for my use case, if it turns out, there's no reasonably-secure way to tell apart forged from non-forged cross-site requests - but I'm still interested on a theoretical level.

Ruben
  • 3,452
  • 31
  • 47

2 Answers2

6

You can't make cross-site requests without eliminating the forgery part. But for your use case, I don't think you need cross-site requests.

Lets assume your service allows users to bookmark any pages he wishes. The job of the bookmarklet would be to save {url, title} into the database. At the same time, you want to prevent a malicious website automatically saving urls for a user who is logged in.

Here's what I would do to solve this -

  1. Create a page on your domain that has a standard html form with regular CSRF protection. This page takes in the parameters {url, pagetitle}, but will only save this tuple when the user explicitly clicks the "save" button.
  2. The bookmarklet's job is to load the iframe
  3. Once the iframe loads, its a normal same-origin request

This is more or less what Google reader does as part of its bookmarklet. Here is the code for its bookmarklet - notice it doesn't have any tokens


javascript:
var b=document.body;
var GR________bookmarklet_domain='http://www.google.com';
if(b&&!document.xmlVersion) {
  void(z=document.createElement('script'));
  void(z.src='http://www.google.com/reader/ui/link-bookmarklet.js');
  void(b.appendChild(z));
 }
 else{}

EDIT : You can still support interactions with the iframe approach. The bookmarklet executes in context of the website, so it has access to the DOM. You can interact however you want with the website. When you are ready to save, open up the Iframe. The iframe will be a sort-of confirmation screen with just one save button.

The trick is to delay creation of the iframe. You only create the iframe when the user is ready to save.

Sripathi Krishnan
  • 30,948
  • 4
  • 76
  • 83
  • Oh. Yeah, this obviously solves the problem and I didn't think of it, once I started down the other train of thought. However, I did not specify my use case as not requiring dynamic interaction with the website's content. Isn't this impossible if I'm stuck in an iframe - or phrased differently - how does Google get the website's title? – Ruben Mar 01 '11 at 18:43
  • Dynamic interaction is possible. Your bookmarklet has access to the website's DOM, so it can pick up any information it wants. The trick is to include all the data as part of the iframe URL. Once the iframe is created and loaded, you cannot communicate any further, but that shouldn't be a concern. – Sripathi Krishnan Mar 01 '11 at 18:47
  • It could be. In your Google Reader use case, it is not a problem. But I cannot transmit the whole DOM via querystring. My use case involves selecting two chunks of text on the website, editing them in the iFrame. I could kill the *dynamically-gets-chunk* aspect and rely on copy-paste, but my spark of interest in legit-CSR is not yet quenched. Will edit post. – Ruben Mar 01 '11 at 18:54
  • You are not restricted to GET / Query String. The bookmarklet can create a dynamic form with the entire HTML, and then POST it to your server. The target of the form should be the iframe that you display. – Sripathi Krishnan Mar 01 '11 at 19:01
  • Oh, yeah. If the website changes its content via Ajax after the iFrame was created, one would have to reload the iframe though, right? So there is a hypothetical (in my case even real, albeit not important) functionality that is lost when you do it the iframe way. – Ruben Mar 01 '11 at 19:09
  • Actually, alls not lost. Your bookmarklet is still running code in context of the website, so it can track changes if it cares to do so. When it figures out something has changed, it can reload the IFrame with the new content. You can always change the Iframe URL, but you cannot read the url or the DOM. – Sripathi Krishnan Mar 01 '11 at 19:14
1

If the bookmarklet is being stored in a web browser then the idea of including a secret key which hashed in the database is a good idea. This will prevent CSRF, although it is not ideal because in a sense this is a session id that doesn't time out. It should be clear that this token is only being used for this bookmarklet action. Despite this limitation it is unlikely that you will experience an attack. Adding HTTPS will make this stronger because it will be harder to spill this token.

If the bookmarklet is originating from a 3rd party site, then there is nothing you can do. This is the definition of CSRF.

rook
  • 66,304
  • 38
  • 162
  • 239
  • Yes, the bookmarklet is mine, and will be stored in the webbrowser. However, unlike a permanent session id, it can be read by the website where it is called and I'm trying to come up with a countermeasure (but I'm not sure if that isn't impossible on principle). I realise this is an unlikely scenario, especially in my case. I just wonder. That the permanence is not ideal is clear, that's why I thought of having to be logged in as an auxiliary requirement. **&** I wonder how to obtain said permanence, when I only store the hash. – Ruben Mar 01 '11 at 17:59