
RJS leaking vulnerability in multiple Rails applications - homakov
http://homakov.blogspot.com/2013/11/rjs-leaking-vulnerability-in-multiple.html
======
5vforest
Here's a quick summary:

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.

~~~
cykod
Isn't this solved by just requiring the CSRF token on any JS get requests? (In
fact, isn't this just a cross-site request forgery with a different verb than
we're used to?)

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.

~~~
5vforest
Seems to me like this solution, or checking for `request.xhr?` are the best
suggestions so far.

~~~
why-el
This one was mentioned by Homakov on Twitter I think. Though I would love to
have it wrap the responder instead.

------
simonw
The key to this exploit is that JavaScript that is designed to be fetched via
XHR and then eval()d can be used by another site to "steal" data by linking to
it in a script tag.

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.

~~~
homakov
>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.

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.

~~~
jonny_eh
But would this same solution work with this vulnerability? The server sets the
first line of the response js to `while (true) ;` and the js handler that does
the eval just deletes the first line like so:

    
    
         eval( responseJS.replace(/.*\n/, "") )

~~~
simonw
The exploit occurs when an evil third party site links to your script from a
script tag - they can't modify the returned code before it is evaluated. Your
own code running on the same domain (which fetches the code using XHR) can
make the modification and hence execute the script.

------
purephase
I find it interesting that DHH dismissed this out-of-hand given Egor's history
with Rails. In his tweets he highlights some pretty large Rails projects that
impact a lot of users/sites.

I know a few of my own are affected by this.

~~~
homakov
He dismisses removal of it. He just wants to find a painless way to fix
security concern. I don't know such way

~~~
po
It's clear that the author is frustrated though. Maybe it's just me but I feel
like a framework's core team should treat security vulnerabilities (or even
common design patterns that lead to them) as stop-the-world events until they
are resolved.

It's not up to the issue reporter to come up with a fix that satisfies the
core team.

~~~
homakov
Haha i practised pull request fixing zero days for a while. Ppl asked me to
stop

------
cheald
JSON and clientside templating, folks. It's a wee bit more work, but it's a
lot more pure, and that purity pays off in multiple regards, including
security.

~~~
aantix
"wee" is subjective...

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.

~~~
stephenr
Nothing that relies on JS eval() is elegant, by any definition of the word.

------
neya
This is sad, and this is being flagged too, so fast, seriously people? This is
a genuine security issue which needs to be alerted to everyone out there. And
Homakov has also gone so as far as to make some pull requests and he has been
asked to stop (WTF?!). Really I see this more of like arrogance from the
project maintainers' end.

I'm thankful that I don't depend/use on any of these features for my project
though.

Thanks Homakov!!

------
madrobby
This is why `request.xhr?` exists. It will be false when the action is called
with JSONP.

~~~
homakov
+1
[https://twitter.com/homakov/status/406466616897986560](https://twitter.com/homakov/status/406466616897986560)

------
mikeycgto
This type of attack is also relevant:
[http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-
js...](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-
vulnerability.aspx)

~~~
homakov
Relevant, but out of date. It was a bug in browsers, not in the apps anyway

------
stretchwithme
Always hated RJS. Too much toggling my brain.

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 :-)

~~~
gurvinder
How will jQuery solve this, if still using JSONP ?

~~~
stretchwithme
Not sure I know what you mean.

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.

------
taf2
I get redirected to a login page when attempting to access a spree
/admin/products/new.js ... where's the leak or did I need to first have access
to the users session?

~~~
homakov
someone with admin session must visit this page. Are you admin? you check it
on demo spree installation

------
javan
Quick fix for your Rails app:
[https://gist.github.com/javan/7725255](https://gist.github.com/javan/7725255)

------
artellectual
you have to be logged in for the request to work right?. if you are not logged
in / don't have the cookie / session it won't work. so whats the problem? when
your logged in you see all that stuff anyway why does it matter which flavor
its in?

as long as your session is secured how will this work?

~~~
homakov
you make logged in user to visit specially crafted page, where you attack him
with cross site <script> tag. and leak the data.

------
ivanhoe
an easy fix, I think, would be to always wrap the returned code in a function
that would check if the domain matches a predefined whitelist and just return
out of the function if it doesn't. This can be very easily automated.

~~~
TylerE
Not a fix at all. The client can examine the returned code.

~~~
joshstrange
I was pretty sure that if its comming from a different domain that this is not
the case.

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.

~~~
ivanhoe
actually what I had in mind was doing something like:

(function () {

    
    
        if (location.hostname !== 'mydomain.com')
    
            return;
    
        // the real content here, e.g:
        $('#someid').html('sensitive data');

})();

Makes any sense?

~~~
homakov
proved above, can be bypassed

------
andyl
RJS was a great technology in 2006. But now RJS is a dead-end and its time to
let it go.

Replace RJS with jQuery (and the like) and focus on solving modern problems
like concurrency and meteor-style push.

~~~
steveklabnik
RJS was removed in Rails 3.1, IIRC. It hasn't been considered best practice
for some time.

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...

~~~
homakov
In my post RJS implies JS responders. RJS doesnt exist anymore

~~~
oomkiller
Yeah, you might want to clarify that this can affect anyone who renders JS
with erb or whatever, as it reads like only RJS (RIP) is affected.

