Use case: In a same-domain situation, I want to use jsonp for data transport, simply because it happens earlier in the request than any ajax or iframe transport. Also, I want this data to be cached like a regular js file.
I do not want to expose this data to other domains.
So, I thought about the following tricks to prevent csrf:
- A csrf token sent as a GET parameter with the request.
- Another csrf token that is stored in a variable, before the jsonp executes. So, the jsonp would only call the function if it finds that variable to have the correct value.
This is a double paranoid solution, because I am not sure if any of these two is bullet-proof.
The questions would be:
- Is it safe to send csrf token in a GET param? Or what can I do to make it safe?
- Can someone from a different domain look into the source code of the js, to circumvent t开发者_如何学Crick number (2) ?
To avoid that we start from zero, here is a document I find useful:
https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet (but could be that I missed something in there)Especially interesting: "Disclosure of Token in URL", which I think I have still not fully understood in all its consequences.
EDIT:
Other solutions one could think of: 3. referrer checking. Not safe enough, afaik. Especially, if the js file is cached on the browser. 4. Encryption. Probably not worth the effort..EDIT II:
To clarify: "same-domain situation" just means that the html page and the jsonp are served from the same domain. The client machine can be anywhere.EDIT III:
I want to be as safe as if I would serve regular json or html/xml (to be requested with ajax or iframe), instead of jsonp. As you probably know, json and html/xml is protected by the same-origin policy, while jsonp is not.I want to use csrf tokens to achieve the same protection with jsonp. The csrf tokens will obviously be served with the html page.
EDIT IV:
This is how trick number 2 would look like. Page html:<script type="text/javascript">
var token1 = 'upoihjvpaoijpoj';
</script>
<script type="text/javascript" src="xyz/jsonp.js?token2=o8976987698540"></script>
And then in the xyz/json.js?token2=..:
if (token1 == 'upoihjvpaoijpoj') {
json_callback(data);
}
EDIT V:
This matter is complex enough, so at least I should add a real-world example. I don't remember the exact use case when I posted this question, so I just write sth that does come close.A typical example would be a drop-down menu, where we want to load the menu contents with a separate request, because they are the same on every page. However, the menu might contain some links that should only be displayed to specific logged-in users. Let's just assume it is a life-and-death thing that unauthorized visitors may never see those links.
The first idea would be to load the menu with an XHR request. On server side, we can check the cookie / session thingie to check if the user is logged in, and serve a personalized menu.
But, XHR can only start to load once the rest of the page is already there. Javascript, on the other hand, can start loading as soon as the browser parses the html head. So we could hope for a performance benefit, if we serve the menu data with javascript/jsonp instead of XHR.
Now the CSRF scenario:
- A user logs in to oursite.com, which creates the session cookie.
- The same user does then visit the attacker site evil.com, which contains a script tag to request the (personalized) oursite.com/menudata.js.
- Due to the same-origin policy, scripts on evil.com can not directly inspect the contents of menudata.js. Instead, it will simply execute menudata.js.
menudata.js could look like this:
spawnMenu({...});
Or the "spawnMenu" could be a dynamically chosen string based on the request parameters. If menudata.js executes on evil.com, the attackers running this site could provide a function with that name, and then "phone home" and steal the personalized menu data.
Now the idea (point 2. of the initial question) was to do something like this instead in menudata.js:
if (secret_var === 'opijpoijpoizvnpoiq92823pjnfn') {
spawnMenu({...});
}
This looks quite ok at first. evil.com does not know the secret_var, so when menudata.js executes, it will do nothing.
But then, I heard that there are some nasty tricks where you can "rewrite" quite basic parts of js. Such as, replace the usual way that stuff is cast to string. Maybe even replace comparison operators? This way, a script on evil.com could cheat on our nice little check.
Off the top of my head...
Configure your web server to limit access to your web service to clients within your own network.
Refactor the web service to reject requests from unknown hosts.
Use SSL for your web service and require authentication on access.
Interpose a proxy between clients and your web service to limit access based on network, address, or domain.
EDIT
In reference to your comments:
1 and 2 - Referrers can be spoofed. It is much harder to spoof an IP address.
3 - Yes, and using authentication with SSL makes "man in the middle" attacks harder. (I was tempted to say impossible, but that would be hubris. :-)
4 - I offered this as an option if you didn't have access to or could not configure your web server.
But these are mostly moot since either I misunderstood or you changed the conditions of the question.
Initially you indicated this was "a same domain situation" which I took to mean that clients and web service existed in limited, controllable locations (i.e. within the same network).
Since you expect clients of your web service to be located any where, only option 3 would work.
However, there is already a standard means of limiting access to a web service (or portions thereof.)
For each authorized client, generate a unique "API key" and require any calls to protected functionality to include this token. Calls to protected areas without a valid key are then denied by the web service.
Use SSL (https) to limit the potential of snooping for keys and if (when) it appears someone has broken your security (for instance by sharing their unique key with others) simply revoke that key by removing it from the list of authorized access keys.
RE-EDIT
Finally and after rereading your original question, it appears this won't work for you either since it appears you're requesting the data from within the context of an existing web page which means you'd need to expose the API key in the JavaScript source.
So your original idea might seem to be your best bet if:
- each key was generated using a non-obvious algorithm (like MD5 over random data)
- the key was embedded within the JavaScript source and used to access the protected data via AJAX
- once a key was used, it immediately became invalid and could not be used again
But (and it's an enormous but!),
there is no way to assure the original page request came from an user rather than an automated process designed to spoof your service.
Once the data have been retrieved by a browser, they are exposed to inspection by anyone using a variety of monitoring tools (like firebug).
If the data are then presented within the web page, they can be easily scraped (via copy/paste).
So from a practical standpoint, it really isn't possible to protect data from interception and inspection using the tools you are using.
Seams simple to solve with some pseudo code.
On each request do.
if(!session['csrf-token']) {
session['csrf-token'] = randomString();
}
On the page where you want the cacheable data.
<script src="/some_cacheable_data.js?<%= session['csrf-token'] %>"></script>
In the rendering of the javascript.
if(params['csrf-token'] == sesssion['csrf-token']) {
renderData();
}
If the data is accessible via JSONP there is no way to protect from other third parties. The only solution I can think of is to use an out-of-bounds channel, ie: a direct API call or a secret token of sorts. At best you could expire the CSRF token after each time you access it, but that could lead to a possible denial of service.
As long as your csrf token changes per request (so somebody listening in can't do a replay attack) you should be fine.
Not sure about the sudden downvotes but I've done a lot of security work with Foursquare, Netflix, and (lol) AOL. A CSFR token is not like a cookie. The attacker cannot guess your CSRF token. That is sent to the client during login or is inline in the webpage. It is not automatically sent with all requests like a cookie is so an attacker can't force your browser to send it with an img tag or some other funky business. It is included by your applications front end framework.
精彩评论