环境:
后台使用的python - flask
前台使用angular框架
1.一个跨域post的例子:
跨域post有多种实现方式:
1.CORS:http://blog.csdn.net/hfahe/article/details/7730944
2.利用iframe
3.server proxy:https://en.wikipedia.org/wiki/Proxy_server
例子使用的为iframe,想要证明,在没有进行csrf防御时,任意攻击者可以利用javascript发送 post 请求,从而简单的提交或获取数据资料;
1.本地test.html页面
<html>
<head>
<title>POST</title>
<script type="text/javascript" src="jquery-2.1.4.min.js"></script>
</head>
<body>
<input type="button" onclick="test();" value="test"/>
<script type="text/javascript">
function test() {
crossDomainPost({
url: 'http://localhost:5000/test',
param: {a: '1', b: '2'},
onSubmit: function (e) {
console.log(e);
}
})
}
function crossDomainPost(config) {
var def = {
url : '', //提交的地址
param : {}, //提交的参数
delay : 1000, //延迟获取参数的时间,单位为毫秒
onSubmit : function (i) {} //提交成功后的回调函数,参数为跳转的IFRAME
};
config = $.extend({}, def, config);
if (!config.url) {
config.onSubmit({error: 'URL is Empty!'});
return;
}
/****baseMethod****/
/**
* 生成随机的10位字符,且唯一
* @returns {string}
*/
var createGuid = function () {
var guid = "";
for (var i = 1; i <= 10; i++) {
guid += Math.floor(Math.random() * 16.0).toString(16);
}
return guid;
},
/**
* 删除指定的节点
* @param _element 要删除的节点
*/
removeElement = function (_element) {
var _parentElement = _element.parentNode;
if (_parentElement) {
_parentElement.removeChild(_element);
}
}
// Add the iframe with a unique name
var iframe = document.createElement("iframe");
var uniqueString = createGuid();
document.body.appendChild(iframe);
iframe.style.display = "none";
iframe.contentWindow.name = uniqueString;
// construct a form with hidden inputs, targeting the iframe
var form = document.createElement("form");
form.target = uniqueString;
form.action = config.url;
form.method = "POST";
// repeat for each parameter
for (var item in config.param) {
var input = document.createElement("input");
input.type = "hidden";
input.name = item;
input.value = config.param[ item ];
form.appendChild(input);
}
document.body.appendChild(form);
try{
form.submit();
}catch(e){
console.log('error');
consoel.log(e);
}
setTimeout(function () {
config.onSubmit(iframe);
removeElement(form); //移除form
}, config.delay);
}
</script>
</body>
</html>
2.后台接收代码
这里仅接收POST的请求
@app.route('/test' ,methods=['POST'])
def test():
print 'param is :';
print request.form
return jsonify(data='2222')
3.控制台输出:
服务端处理了非站内的请求
4.浏览器输出:
本地test.html获取到返回信息
2.CSRF
跨站请求攻击,简单地说,是攻击者通过一些技术手段欺骗用户的浏览器去访问一个自己曾经认证过的网站并执行一些操作(如发邮件,发消息,甚至财产操作如转账和购买商品)。由于浏览器曾经认证过,所以被访问的网站会认为是真正的用户操作而去执行。这利用了web中用户身份验证的一个漏洞:简单的身份验证只能保证请求发自某个用户的浏览器,却不能保证请求本身是用户自愿发出的。
你这可以这么理解CSRF攻击:攻击者盗用了你的身份,以你的名义发送恶意请求。CSRF能够做的事情包括:以你名义发送邮件,发消息,盗取你的账号,甚至于购买商品,虚拟货币转账……造成的问题包括:个人隐私泄露以及财产安全。
1.原理
从上图可以看出,要完成一次CSRF攻击,受害者必须依次完成两个步骤:
- 登录受信任网站A,并在本地生成Cookie。
- 在不登出A的情况下,访问危险网站B。
看到这里,你也许会说:“如果我不满足以上两个条件中的一个,我就不会受到CSRF的攻击”。是的,确实如此,但你不能保证以下情况不会发生:
1.你不能保证你登录了一个网站后,不再打开一个tab页面并访问另外的网站。
2.你不能保证你关闭浏览器了后,你本地的Cookie立刻过期,你上次的会话已经结束。
3.上图中所谓的攻击网站,可能是一个存在其他漏洞的可信任的经常被人访问的网站。
原理详细:http://www.80sec.com/csrf-securit.html
2.常见的攻击类型:
1.GET类型的CSRF
只需要一个HTTP请求,就可以构造一次简单的CSRF。
例子:
银行网站A:它以GET请求来完成银行转账的操作,如:http://www.mybank.com/Transfer.php?toBankId=11&money=1000
危险网站B:它里面有一段HTML的代码如下:
<img src=http://www.mybank.com/Transfer.php?toBankId=11&money=1000>
首先,你登录了银行网站A,然后访问危险网站B,噢,这时你会发现你的银行账户少了1000块
为什么会这样呢?原因是银行网站A违反了HTTP规范,使用GET请求更新资源。在访问危险网站B的之前,你已经登录了银行网站A,而B中的以GET的方式请求第三方资源(这里的第三方就是指银行网站了,原本这是一个合法的请求,但这里被不法分子利用了),所以你的浏览器会带上你的银行网站A的Cookie发出Get请求,去获取资源http://www.mybank.com/Transfer.php?toBankId=11&money=1000 ,结果银行网站服务器收到请求后,认为这是一个更新资源操作(转账操作),所以就立刻进行转账操作
2.POST类型的CSRF
如上边的跨域POST例子
3.如何防御CSRF
1.提交验证码
在表单中增加一个随机的数字或字母验证码,通过强制用户和应用进行交互,来有效地遏制CSRF攻击。
2.Referer Check
检查如果是非正常页面过来的请求,则极有可能是CSRF攻击。
3.token验证
- 在 HTTP 请求中以参数的形式加入一个随机产生的 token,并在服务器端建立一个拦截器来验证这个 token,如果请求中没有
token 或者 token 内容不正确,则认为可能是 CSRF 攻击而拒绝该请求。 - token需要足够随机
- 敏感的操作应该使用POST,而不是GET,以form表单的形式提交,可以避免token泄露。
4.在 HTTP 头中自定义属性并验证
这种方法也是使用 token 并进行验证,这里并不是把 token 以参数的形式置于 HTTP 请求之中,而是把它放到 HTTP 头中自定义的属性里。通过 XMLHttpRequest 这个类,可以一次性给所有该类请求加上 csrftoken 这个 HTTP 头属性,并把 token 值放入其中。这样解决了上种方法在请求中加入 token 的不便,同时,通过 XMLHttpRequest 请求的地址不会被记录到浏览器的地址栏,也不用担心 token 会透过 Referer 泄露到其他网站中去。
4.关于token
- Token 应该被保存起来(放到 local / session stograge 或者 cookies)
- Tokens 除了像 cookie 一样有有效期,而且你可以有更多的操作方法。一旦 token 过期,只需要重新获取一个。你可以使用一个接口去刷新 token;你甚至可以把 token 原来的发布时间也保存起来,并且强制在两星期后重新登录什么的;如果你需要撤回 tokens(当 token 的生存期比较长的时候这很有必要)那么你需要一个 token 的生成管理器去作检查。
- Local / session storage 不会跨域工作,请使用一个标记 cookie
- 有需要的话,要加密并且签名 token
- 将 JSON Web Tokens 应用到 OAuth 2
使用Token
1.引入csrf
from flask_wtf.csrf import CsrfProtect
csrf = CsrfProtect()
app = Flask(__name__)
csrf.init_app(app)
app.config['SECRET_KEY']='myblog'
2.在站内页面上head中,增加token
<meta name="csrf-token" content="{{ csrf_token() }}">
3.配置angular提交表头
app.config(function ($httpProvider) {
$httpProvider.defaults.headers.common['X-CSRF-Token'] = $('meta[name=csrf-token]').attr('content');
});
4.再次测试跨域post
后台输出:
前台输出:
关于webapp跨域Post使用token思路:
- 移动端登录时,服务端验证表单信息,登陆成功,生成token,返回给客户端;
- 客户端将token存在localstorage/sessionstorage中,每次提交表单,都需要携带token;
- 服务端获取请求,如果没有token,则忽略请求;
出现的问题:
服务端需要限制登陆次数
解决方法:
客户端增加登陆间隔,请求一次后,等待x秒才能再次请求
服务端做cas验证服务端需要保存用户的token,及过期时间;
解决方法:
可以将token 保存在Memcache,数据库中,redis- 客户端存token时,需要对token加密
解决方法:
在存储的时候把token进行对称加密存储,用时解开
将请求URL、时间戳、token三者进行合并加盐签名,服务端校验有效性
当然,以上只防君子,不防小人
参考:
跨域post请求:http://blog.csdn.net/doraeimo/article/details/7329779
跨站请求伪造:http://www.jianshu.com/p/7f33f9c7997b
token:http://alvinzhu.me/blog/2014/08/26/10-things-you-should-know-about-tokens/