Last updated at Tue, 03 Sep 2024 15:40:54 GMT
This post is the fifth in a series, 12 Days of HaXmas, where we take a look at some of more notable advancements in the Metasploit Framework over the course of 2013.
Several weeks ago, Egor Homakov wrote a blog post pointing out a common info leak vulnerability in many Rails apps that utilize Remote JavaScript. The attack vector and implications can be hard to wrap your head around, so in this post I'll explain how the vulnerability occurs and how to exploit it.
What is Remote Javascript?
Remote JavaScript (RJS) was a pattern prescribed by Rails < 2 to implement dynamic web sites. In RJS the user-facing parts of a website (HTML and JS) act as a "dumb client" for the server: when dynamic action is needed, the client calls a JavaScript helper that sends a request to the server. The server then performs the necessary logic and generates and responds with JavaScript code, which is sent back to the client and eval()
'd.
The RJS approach has some advantages, as rails creator dhh points out in a recent blog post. However, suffice it to say that RJS breaks down as soon as you need complex client-side code, and a server API that responds with UI-dependent JavaScript is not very reusable. So Rails mostly has moved away from the RJS approach (JSON APIs and client-heavy stacks are the new direction), but still supports RJS out of the box.
So what's the problem?
Unfortunately, RJS is insecure by default. Imagine a developer on a Rails app that uses RJS is asked to make an Ajax-based login pop-up page. Following the RJS pattern, the developer would write some JavaScript that, when the "Login" link is clicked, asks the remote server what to do. The developer would add a controller action to the Rails app that responds with the JavaScript required to show the login form:
class Dashboard
def login_form
respond_to do |format|
format.js do
render :partial => 'show_login_form'
end
end
end
end
Following the RJS pattern, the show_login_form.js.erb
partial returns some JavaScript code to update the login form container:
$("#login").show().html("<%= escape_javascript(render :partial => 'login/form')")
Which, when rendered, produces code such as:
$("#login").show().html("
Now imagine user Tom is logged into the Rails app (which we'll say is served from railsapp.com). An unrelated website attacker.com might serve Tom the following code:
Sun-City-entertainment-City-feedback@wowarmony.com
永利体育
Sports-in-Sabah-media@davidegalliani.com
BT好搜
中国博彩平台
沙巴官网
沙巴在线平台
阳光印网
南昌晚报数字报
足球外围平台
百问中文
欧洲杯买球
爱粤语
无忧雅思网
21hifi.com音响网
沙巴体育
亚洲体育博彩平台
Crown-Sports-official-website-support@ctienviron.com
博彩平台
昆明理工大学津桥学院
杭州海外旅游
金牌股份
河北食品药品教育网
卡努努
考试家园院校库
拉手网
ZOL中关村在线散热器频道
洛阳搜房网-新房
财经日报
育儿网育儿资讯
热心医生
站点地图
丰财园佛教网
Because tags are allowed to be cross-origin (this is useful for CDNs), Tom's browser happily sends a GET request to railsapp.com, attaching his railsapp.com cookie. The RJS script is generated and returned to Tom, and his browser executes it. By stubbing out the necessary functions in the global scope, attacker.com can easily gain access to the string of HTML that is sent back:
Sun-City-entertainment-City-feedback@wowarmony.com
永利体育
Sports-in-Sabah-media@davidegalliani.com
BT好搜
中国博彩平台
沙巴官网
沙巴在线平台
阳光印网
南昌晚报数字报
足球外围平台
百问中文
欧洲杯买球
爱粤语
无忧雅思网
21hifi.com音响网
沙巴体育
亚洲体育博彩平台
Crown-Sports-official-website-support@ctienviron.com
博彩平台
昆明理工大学津桥学院
杭州海外旅游
金牌股份
河北食品药品教育网
卡努努
考试家园院校库
拉手网
ZOL中关村在线散热器频道
洛阳搜房网-新房
财经日报
育儿网育儿资讯
热心医生
站点地图
丰财园佛教网
And now attacker.com can easily parse out Tom's CSRF auth token and start issuing malicious CSRF requests to railsapp.com. This means that attacker.com can submit any form in railsapp.com. The same technique can be used to leak other information besides auth token, including logged-in status, account name, etc.
As a pentester, how can I spot this bug while auditing a web app?
It is pretty easy to find this vulnerability. Click around a while in the web app and keep Web Inspector's Network tab open. Look for .js requests sent sometime after a page load. Any response to a .js request that includes private info (auth token, user ID, existence of a login session) can be "hijacked" using an exploit similar to the above PoC.
How can I fix this in my web app?
The fix prescribed by Rails is to go through your code and add request.xhr?
checks to every controller action that uses RJS. This is annoying, and is a big pain if you have a large existing code base that needs patching. Since Metasploit Pro was affected by the vulnerability, we needed a patch quick. So I present our solution to the vulnerability - we now check all .js requests to ensure that the REFERER header is present and correct. The only downside here is that your app will break for users behind proxies that strip referers. Additionally, this patch will not work for you if you plan on serving cross-domain JavaScript (e.g. for a hosted JavaScript SDK). If you can stomach that sacrifice, here is a Rails initializer that fixes the security hole. Drop it in ui/config/initializers
of your Rails app:
# This patch adds a before_filter to all controllers that prevents xdomain
# .js requests from being rendered successfully.
module RemoteJavascriptRefererCheck
extend ActiveSupport::Concern
included do
require 'uri'
before_filter :check_rjs_referer, :if => ->(controller) { controller.request.format.js? }
end
# prevent generated rjs scripts from being exfiltrated by remote sites
# see http://homakov.blogspot.com/2013/11/rjs-leaking-vulnerability-in-multiple.html
def check_rjs_referer
referer_uri = begin
URI.parse(request.env["HTTP_REFERER"])
rescue URI::InvalidURIError
nil
end
# if request comes from a cross domain document
if referer_uri.blank? or
(request.host.present? and referer_uri.host != request.host) or
(request.port.present? and referer_uri.port != request.port)
head :unauthorized
end
end
end
# shove the check into the base controller so it gets hit on every route
ApplicationController.class_eval do
include RemoteJavascriptRefererCheck
end
And your server will now return a 500 error to any RJS request that does not contain the correct REFERER. A gist is available here, just download and place in $RAILS_ROOT/config/initializers
.