Plain text considered harmful: A cross-domain exploit
referer:http://balpha.de/2013/02/plain-text-considered-harmful-a-cross-domain-exploit/
Data from around the world
The same origin policy prevents a website's JavaScript from seeing the result of a request made to a different domain. This is essential because that request would send along any cookies stored for that domain. If you happen to be authenticated on the other site, and visit a malicious site, then the evil page could request, say, your account balance summary from the other site.
Note that the same origin policy doesn't necessarily prevent the request per se – it just prevents the response from being accessible. A malicious site can e.g. just redirect your browser, or submit a form, or include an image or an iframe – in all those cases a request is made to your site; the evil site just doesn't see the response. It doesn't have access to the iframe's DOM, painting a cross-domain image to a canvas will taint it, and so on. In some cases, it's possible for a site to say that it's okay to see the data on a different domain, but this isn't the default.
Let's look at <script>
elements now. If such an element's src
attribute is set, then the browser will load the script and execute it in the current page. It will do this regardless of origin (otherwise, you e.g. couldn't serve jQuery from Google's CDN). It's not possible for a site to read the content of a script that was loaded from a different domain, but any side effects of executing the script are very visible – like the jQuery
object suddenly existing in your page.
This cross-domain script loading is what JSONP utilizes. If you have, say, a public API where you're entirely okay with the data being accessible cross-domain, you can enable other sites' JavaScript to use your data by returning JSONP. This essentially means you're returning JavaScript (which, as explained above, will be executed despite the different origin). The consuming site lets you know that it has prepared a function for your script to call, and what that function's name is. You return a JavaScript file that calls this very function with your data.
function theCallback(data) {
alert("The temperature is " + data.temperature_celsius + " °C");
}
var script = document.createElement("script");
script.src = "http://some-weather-service.com/api?city=Boston&callback=theCallback";
document.head.appendChild(script);
The callback
parameter tells the weather service what function to call in its JSONP response, and thus the API's response looks like this:
theCallback({"temperature_celsius":20,"temperature_fahrenheit":68})
which makes your site work as intended.
As an aside: You should only use JSONP APIs from sites that you trust, since you're allowing those sites to send you arbitrary JavaScript that you will happily execute on your own domain.
My precious
So we've established that it's possible to execute JavaScript that comes from a different domain; a site that wants to allow cross-origin access can send valid JavaScript that any site can thus embed. Now let's look at the case where the site doesn't want to allow this; it only wants the data to be accessible to requests coming from its own site.
As far as simple reading of the request's response goes, same-origin takes care of this. But you obviously also want to prevent the above “execute as JavaScript” trick from working. The lowest-hanging fruit is just making sure that your response doesn't look like a function call. However, there is such a thing as a function call in disguise – Phil Haack wrote a blog post and a more detailed follow-up on this topic, which will tell you why you shouldn't return JSON responses whose outer wrapper is an array.
But there's an even more subtle vulnerability which appears when you don't return JSON at all. Here's a real-life example I found (it was fixed in the meantime). Assume your site uses a CSRF token, and you want to make sure that a browser tab always has the up-to-date token, even when it changes. This could be because you cycle the token at regular intervals or just because in a different browser tab your user logged out and logged in again. That's why you have a route that returns the current CSRF token for the active session, so the browser can regularly poll for it.
Same-origin policy prevents other sites from seeing this token in any way, since you neither wrap it in a function call, nor in an array – in fact, you wrap it in nothing at all; you just return the token as a plain text. Your token is a 24-digit hexadecimal number, like b2039487e51a6bfdc7299de0
, and these 24 characters are all that your route returns. Nothing in there that can cause a function to be executed, so even if a malicious site included
<script src="http://your-awesome-site.com/current-csrf-token"></script>
in its HTML, it wouldn't have access to the token.
Right? Nope.
Not as hidden as you'd think
Let's see what happens when a browser executes a “script” that contains nothing more than b2039487e51a6bfdc7299de0
. If you try it out, you'll see that not a lot happens, except that the JavaScript console will show an error:
ReferenceError: b2039487e51a6bfdc7299de0 is not defined
The first thing to note is that this is not a syntax error. Because b2039487e51a6bfdc7299de0
is a valid JavaScript identifier, this 24-character long “script” is syntactically valid JavaScript, and the browser happily runs it. So what happens when the JavaScript engine encounters an identifier? It will look for a variable in the current scope by this name. Since the script is executed in a global context, the current scope is the window
object. The engine checks whether window["b2039487e51a6bfdc7299de0"]
exists, which it obviously doesn't, and thus throws an exception.
This looks like something that the malicious site cannot gain any information from, but unfortunately in Firefox, it can. Firefox supports Proxy objects by default. Chrome also supports them, but only if you enable experimental JavaScript (and with a slightly different syntax than Firefox – point being: this exploit can be made to work in Chrome as well, but currently only for users who have the experimental JS features turned on).
Proxy objects are essentially property accessors on speed. If you're used to Python, think “overriding __getattr__
.” If you're used to C#, think “TryGetMember
on DynamicObject
.” Together with prototype magic, they enable us hook into the “looking for a variable in the current scope” process. If the user visits the following page in Firefox, the JavaScript can identify the token:
<html>
<head>
<script type="text/javascript">
window.onload = function () {
window.__proto__ = new Proxy(window.__proto__, {
has: function (target, name) {
if (/^[a-f0-9]{24}$/.test(name))
alert("Your CSRF token on your-awesome-site.com is " + name);
return name in target;
}
});
var s = document.createElement("script");
s.src = "http://your-awesome-site.com/current-csrf-token";
document.head.appendChild(s);
}
</script>
</head>
<body></body>
</html>
– and an alert is just the least evil thing the site can do with the token.
Observant readers may have noticed that the above only works if the token starts with a letter, since only then is it a valid identifier. But for a random hexadecimal digit that's six out of sixteen, thus a 37.5% chance. That's not too shabby. And of course it depends on what kind of data your plain-text API returns.
If the return values are predictable, and the evil site only cares about a yes-or-no question, it doesn't even need proxy objects. For example, let's say a similar route returns the user name of the currently logged-in user, and all the malicious site wants to know is whether a visitor is logged in as “balpha” on your-awesome-site.com. For that, they would just have to create a property called "balpha"
with a get
accessor on the global object. These are supported by all major browsers.
What's to take away from this? If you respond with plain text data to a GET
request, don't be too sure that the same origin policy will save you. Either wrap your data in a JSON object (not an array!), or only respond to POST
requests with such data (unless you're a REST zealot enthusiast, in which case POST won't always be an option), or both. Other possible mitigations include the while(1)
trick, or at the very least making sure that your secret payload is not syntactically valid JavaScript.
Plain text considered harmful: A cross-domain exploit的更多相关文章
- 关于ajax跨域请求(cross Domain)
Cross Domain AJAX主要就是A.com网站的页面发出一个XMLHttpRequest,这个Request的url是B.com,这样的请求是被禁止的,浏览器处于安全考虑不允许进行跨域访问, ...
- 如何撤销 PhpStorm/Clion 等 JetBrains 产品的 “Mark as Plain Text” 操作 ?
当把某个文件“Mark as Plain Text”时,该文件被当做普通文本,就不会有“代码自动完成提示”功能,如下图所示: 但是呢,右键菜单中貌似没有 相应的撤销 操作, 即使是把它删除,再新建一个 ...
- Jade之Plain Text
Plain Text jade提供了3种得到纯文本的方法. Piped Text 添加纯文本的一个最简单的方法就是在文本最前面加|符号即可. jade: p | It must always be o ...
- Insert Plain Text and Images into RichTextBox at Runtime
Insert Plain Text and Images into RichTextBox at Runtime' https://www.codeproject.com/Articles/4544/ ...
- [cross domain] four approachs to cross domain in javascript
four approachs can cross domain in javascript 1.jsonp 2.document.domain(only in frame and they have ...
- [Regular Expressions] Find Plain Text Patterns
The simplest use of Regular Expressions is to find a plain text pattern. In this lesson we'll look a ...
- How to return plain text from AWS Lambda & API Gateway
With limited experience in AWS Lambda & API Gateway, it's struggling to find the correct way to ...
- 前端开发各种cross之cross domain
作为一个苦逼前端开发工程师,不得不面对各种cross,比如面对五花八门的浏览器我们必须cross browser,面对各种终端,我们必须cross device,在这么多年的前端开发经历中,在不同的域 ...
- text/plain && text/html
text/plain和text/html都是Content-Type; text/plain : 页面以文本形式输出 text/html: 页面以html格式输出
随机推荐
- Kafka高可用环境搭建
Apache Kafka是分布式发布-订阅消息系统,在 kafka官网上对 kafka 的定义:一个分布式发布-订阅消息传递系统. 它最初由LinkedIn公司开发,Linkedin于2010年贡献给 ...
- js提示确认删除吗
<script language="javascript"> function delcfm() { if (!confirm("确认要删除?")) ...
- JAVA Properties配置文件的读写
通常我们就会看到一个配置文件,比如:jdbc.properties,它是以“.properties”格式结尾的.在java中,这种文件的内容以键值对<key,value>存储,通常以“=” ...
- java对象流(一)
注意:字节数组流是可以不用关闭的(字符数组流要不要关闭暂时不清楚). 对象流的读数据和写数据方法分别是writeObject(Object o)和readObject(Object o). Objec ...
- JavaScript 获取输入时的光标位置及场景问题
前言 在输入编辑的业务场景中,可能会需要在光标当前的位置或附近显示提示选项.比如社交评论中的@user功能,要确保提示的用户列表总是出现在@字符右下方,又或者是在自定义编辑器中 autocomplet ...
- 第一个DirectX程序include、lib设置问题
1.fatal error LNK1104: cannot open file "d3d9.lib" 解决方案: (1)项目 -->属性 --> 配置属性 --> ...
- CodeForces 103D Time to Raid Cowavans 分块+dp
先对b从小到大sort,判断b是不是比sqrt(n)大,是的话就直接暴力,不是的话就用dp维护一下 dp[i]表示以nb为等差,i为起点的答案,可以节省nb相同的情况 #include<bits ...
- ural 2015 Zhenya moves from the dormitory(模拟)
2015. Zhenya moves from the dormitory Time limit: 1.0 secondMemory limit: 64 MB After moving from hi ...
- angularjs 简单指令
<!DOCTYPE html> <html data-ng-app="app"> <head> <title>angular js& ...
- 经典SQL语句(转载)
一.基础 1.说明:创建数据库 CREATE DATABASE database-name 2.说明:删除数据库 drop database dbname 3.说明:备份sql server --- ...