环境:
后台使用的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请求,去获取资源 toBankId=11&money=1000">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,返回给client;
- client将token存在localstorage/sessionstorage中。每次提交表单。都须要携带token;
- 服务端获取请求。假设没有token,则忽略请求;
出现的问题:
- 服务端须要限制登陆次数
解决方法:
client添加登陆间隔。请求一次后,等待x秒才干再次请求
服务端做cas验证
- 服务端须要保存用户的token,及过期时间;
解决方法:
能够将token 保存在Memcache,数据库中,redis
- client存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/