7 ms·
RJS leaking vulnerability in multiple Rails applications
- andyl 13y agoRJS 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 13y agoRJS 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...
- 5vforest 13y agoHere'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.
- ghiculescu 13y agoEgor talked about it more in his original post, http://homakov.blogspot.com.au/2013/05/do-not-use-rjs-like-techniques.html http://homakov.blogspot.com.au/2013/05/do-not-use-rjs-like-t... 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). [0]: http://yehudakatz.com/2007/05/18/railsconf-talk-recap/ http://yehudakatz.com/2007/05/18/railsconf-talk-recap/
- KayEss 13y agoYour example makes it look like this is doable to any site that allows JSONP responses that contain sensitive data. 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.
- KayEss 13y agoI just managed to confirm that a similar attack is possible against sites using DjangoRestFramework. I won't publish it as the site I used to test against is currently working on patching the vulnerability out.
- tomchristie 13y agoIf you believe you've found a security issue in Django REST framework I suggest raising emailing the security contact as listed here: http://django-rest-framework.org/#security http://django-rest-framework.org/#security 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.
- KayEss 13y agoI did email it pretty much straight away, but I guess it didn't get through.
- cheald 13y agoJSON 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.
- viseztrance 13y agoI beg to differ. You can for instance return plain html and process it from a separate js file.
- aantix 13y ago"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 13y agoNothing that relies on JS eval() is elegant, by any definition of the word.
- Kequc 13y agoClient side templating would be useful as a default feature in Rails, the framework already makes great use of partials. But to get client side templating to work you have to choose one of the new javascript frameworks and duplicate your partials into its' related js directory. 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.
- cheald 13y agoI've been tinkering with a setup internally that uses a modified Hamlbars with Draper to generate serverside HTML and clientside templates from the same source template. It needs a bit of fleshing out, but the idea works.
- ivanhoe 13y agoan 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 13y agoNot a fix at all. The client can examine the returned code.
- joshstrange 13y agoI 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 13y agoCan you show an example how? Not 100% sure, but I believe all major browsers will block the direct access to the content of an external JS file from a different domain? You get just an empty string if accessing it from the DOM (at least in chrome)
- stretchwithme 13y agoAlways 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 13y agoHow will jQuery solve this, if still using JSONP ?
- stretchwithme 13y agoNot 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.
- purephase 13y agoI 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 13y agoHe dismisses removal of it. He just wants to find a painless way to fix security concern. I don't know such way
- KayEss 13y agoI suppose this won't work for RJS, but for other similar JSONP vulnerabilities the correct approach is probably to strip out authentication from JSONP requests -- this could be done at the middleware layer or where authentication happens. Now JSONP would only leak data that you'd give to any other anonymous user.
- homakov 13y agoNot perfect though. Intranet leaks
- po 13y agoIt'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 13y agoHaha i practised pull request fixing zero days for a while. Ppl asked me to stop
- madrobby 13y agoThis is why `request.xhr?` exists. It will be false when the action is called with JSONP.
- homakov 13y ago+1 https://twitter.com/homakov/status/406466616897986560 https://twitter.com/homakov/status/406466616897986560
- artellectual 13y agoyou 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 13y agoyou make logged in user to visit specially crafted page, where you attack him with cross site <script> tag. and leak the data.
- deleted 13y ago[deleted]
- taf2 13y agoI 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 13y agosomeone with admin session must visit this page. Are you admin? you check it on demo spree installation
- mikeycgto 13y agoThis type of attack is also relevant: http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-js...
- homakov 13y agoRelevant, but out of date. It was a bug in browsers, not in the apps anyway
- simonw 13y agoThe 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 13y ago>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 13y agoBut 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/, "") )
- homakov 13y agoYes
- simonw 13y agoThe 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.
- neya 13y agoThis 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!!
- javan 13y agoQuick fix for your Rails app: https://gist.github.com/javan/7725255 https://gist.github.com/javan/7725255