RJS leaking vulnerability in multiple Rails applications
homakov.blogspot.com
homakov.blogspot.com
There's a Rails design pattern where you send an AJAX request to your controller, and it returns a bunch of Javascript that gets eval'd by the page you're on. This is used for say, infinite scroll. You'll click the 'load more' button, the server will render a partial containing your new records, and return something like:
$('#itemsList').append('<li>My new item</li><li>another new item</li>')
So guess what? This is susceptible to XSS-ish attacks.Let's imagine that instead of a simple list item, this request was returning some sensitive information or a CSRF token. (Egor points to various examples of this.) Normally, executing a GET request from an unauthorized domain will not be allowed because of CORS. This is what prevents me from instructing the users of my website to make an AJAX request to gmail.com, allowing me to see all of their emails.
But "wait", you say, "the content type of this request is application/javascript."
So open developer tools and type this: (using jQuery for conciseness)
$.ajax({
url: 'https://basecamp.com/[BASECAMP_PROJECT_ID]/progress.js',
dataType: 'jsonp'
})
And boom, you have a bunch of sensitive information about your Basecamp project, accessible via JSONP request from any domain.I know it's only generally checked on posts, but turning it on for any xhr calls seems like it would solve any potential data leakage.
Also, very few web applications use basic auth, most use cookies.
He also linked to a post from six years ago[0] which had a good solution the problem at the time: pass data, not code (and process this data on the client side).
Is this RoR specific because RoR will always allow JSON responses to be turned into JSONP, or is there something else at work?
I'm suddenly pleased I've been holding off on doing JSONP as part of Django Slumber.
Having said that, it's worth pointing out that Django REST framework does not return JSONP by default, and although it does for historical reasons include a JSONP renderer, the documentation recommends the use of CORS instead.
you can't turn any JSON into JSONP. No, JSONP here is result of using RJS templates. It has nothing to do with original JSONP
1) the attacker knows of an authorized user, already logged in (most likely). So, any request sent by that browser to the target site will pass authentication checks.
2) they trick that user into loading a web page under their control by some means (e.g., phishing attack).
3) that web page, under their control, includes a <script> tag with a src attribute referencing target .rjs. It loads the target .rjs Javascript into a gimmicked environment in which the private data is captured. Since the web page is under the attacker's control, same-origin rules allow that captured private data to be sent back to the attacker directly.
To counter this, the target site needs some way to verify that its own Javascript is making the request, not some hostile web site. Thus the talk of CSRF tokens and checking .xhr?. (The latter counts on the browser's same-origin rules to prevent an AJAX request from the hostile web site --- the same-origin rule blocks that, but in most current browsers, there's no way to prevent the attacker from tossing in a <script> tag.)
It's not precisely JSONP. It turns out to be JSONP, unintendedly. This is where it becomes vulnerable
I believe this only a vulnerability if the server responds to jsonp requests in GET requests.
EDIT: ah, got it, invokation via GET
An obvious fix would be to send that JavaScript with a content type other than "application/javascript" or "text/javascript"... but considering some browsers execute JS sent with the wrong content type I wouldn't trust that to actually work.
A technique that definitely does work is to have the first line of the JS prevent execution in some way. When the code is fetched by XHR this line can be stripped out before calling eval(). This cannot be done by a malicious page that includes the code using a script tag.
I seem to remember seeing Google use "while(true);" for this exact purpose in the past.
I believe all browsers will try to execute. Sniffers
>I seem to remember seeing Google use "while(true);" for this exact purpose in the past.
Not this exact, but relevant. That one was JSON-leaking via redefining Array prototype.
eval( responseJS.replace(/.*\n/, "") )I know a few of my own are affected by this.
It's not up to the issue reporter to come up with a fix that satisfies the core team.
I have existing templates that exist server side. I don't want to recreate them client side. Rails JS responses to the rescue.. It's quite elegant and works well with the existing Rails workflow.
The Rails way has you transmitting html. That isn't great in cases where there may be thousands of entries, in this case the Rails way isn't the best.
I'm thankful that I don't depend/use on any of these features for my project though.
Thanks Homakov!!
Switching to jQuery made life so much easier. Once a feature is completed, I feel happy. With RJS, I was just glad that it was over. And, no, I do not want to touch it again :-)
I can say is that with jQuery you just get the JSON or HTML you need. And then you either process it or put it where its supposed to go. The server just creates the JSON or HTML.
An RJS file is a template for generating javascript on the server side with your data. Writing code with a tempting language is more of a pain than just writing that processes JSON or moves HTML.
as long as your session is secured how will this work?
If you do need to, then the trick is to prefix what the script returns with something that will always throw a syntax error or discontinue evaluation, the idea being that your script (same domain) can cut off the prefix and then evaluate the JS, but the attacker relying on a <script> tag cannot.
for(;;);
http://blackbe.lt/safely-handling-json-hijacking-prevention-...For example:
If we have legitsite.com/json-only-with-sensitive-data.js and we load that by adding a script tag to attackersite.com then attackersite.com cannot get the contents of what the legit site returned.
The problem is when you either have JSONP (for which the attacker site could add a callback in the request to execute) or if you return js code that is supposed to run like in this blog post where RoR returns code like this:
$('#someid').html('sensitive data');
...
In this case it's just as bad as having JSONP because the attacker site can create a <div id='someid'></div> then add the script tag then onLoad of the script tag inspect the contents of the div.(function () {
if (location.hostname !== 'mydomain.com')
return;
// the real content here, e.g:
$('#someid').html('sensitive data');
})();Makes any sense?
Replace RJS with jQuery (and the like) and focus on solving modern problems like concurrency and meteor-style push.
That said, an article has been on the front page all day about not using shiny new tech by an author that recommends the use of Rails 2.3 and RJS today, soooo...