你可以理解为:攻击者盗用你的身份,以你的名义发送恶意请求。它被称为“跨站请求伪造”。它能干的事情有很多:当你打开某个网站时,你另一个已经打开的购物网站已经完成支付;以你名义发送邮件,发评论;网络蠕虫;虚拟货币转账…
这看似简单,其中却有一些值得注意的问题。比如:
1. B网站向A服务器发送请求,是否会引起跨域问题?
不会。因为并不是所有的请求都会引起跨域。如 HTML 中的 和
标签就不会。而且它们的 src 中放的链接其实就是一次 get请求。
著名的jsonp就是用了 script 允许跨域的特性实现的
而且,通过 form
表单发送POST请求的方式也会被用来做攻击。笔者曾看过这样的一段代码:
php
session_start();
if(isset($_POST['toBankId']) && isset($_POST['money'])){
buy_stocks($_POST['toBankId'],$_POST['money']);
}
?>
# 服务端为了防止由img导致的get请求轻易地获取数据,改为只接收POST请求,但
<body onload="steal()">
<iframe name="steal" display="none">
<form method="POST" name="transfer" action="http://www.myBank.com/Transfer.php" target="steal">
<input type="hidden" name="toBankId" value="11" />
<input type="hidden" name="money" value="1000" />
form>
iframe>
<script>
function steal(){
iframe=document.frames["steal"];
iframe.document.submit("transfer");
}
script>
body>
隐藏的 iframe
表单提交 —— 如果一个 form
表单的 target
值等于 iframe
的 name
值。那么这个表单数据会在 iframe
中提交,进而跳转。此时,如果这个 iframe
还设置了 display:none;
那么在页面看来并没有什么“不适”。
2. 攻击者能拿到cookie吗?
不能。CSRF攻击是攻击者利用 cookie 欺骗服务器,但攻击者并不能知道 cookie 的内容。而且,对于服务器返回的数据(结果),由于浏览器同源的限制,攻击者也无法解析。他所做的也就是给服务器发送请求,以执行请求中所描述的命令,在服务器端直接修改数据的值。而非窃取数据!
3. 上面说要csrf生效,基本需满足两点:1、登录受信任网站A,并在本地生成Cookie;2、在不登出A的情况下,访问危险网站B。那如果我不满足以上两个条件中的一个,我就不会受到CSRF的攻击?
理论上确实如此,但你不能保证以下情况不会发生:
1. 前端token,后端校验
CSRF 攻击之所以能够成功,是因为攻击者可以伪造用户的请求,该请求中所有的用户验证信息都是存在于 cookie 中,因此攻击者可以在不知道这些验证信息的情况下直接利用用户自己的 cookie 来通过安全验证。
要抵御 CSRF,关键在于在请求中放入攻击者所不能伪造或者拿不到的信息;或者可以在 HTTP 请求中以参数的形式加入一个随机产生的 token,并将token保存到本地(cookie中),然后在服务器端建立一个拦截器来验证这个 token,如果请求中没有 token 或者 token 内容不正确,则认为可能是 CSRF 攻击而拒绝该请求。
简单来说就是:在服务端生成一个随机的token并保存起来并且跟随请求带给前端。而前端拿到后保存到一个隐藏域中,跟随每次请求带到后端:
// 后端代码,这里以node(koa)为例
let csrfToken=parseInt(Math.random()*9999999,10);
ctx.cookies.set('csrfToken',csrfToken);
<input calss="csrfToken" type="hidden" name="csrfToken" value="" />
<script>
document.querySelector(".csrfToken").value=getMyCookie('csrfToken');
function getMyCookie(key){
var val = "";
// 对cookie操作
var cookies = document.cookie;
cookies = cookies.replace(/\s/,"");
var cookie_array = cookies.split(";");
for(i=0;i<cookie_array.length;i++){
// name=mxc
var cookie = cookie_array[i];
var array = cookie.split("=");
if(array[0]==key){
val = array[1];
}
}
return val;
}
script>
在每次请求的回调中都生成一个新的值,然后在下一次请求过来时校验(cookie中的值和参数中的值相同,并且和后端生成的值也相等)。
这样就要求必须同时知道参数中的token和cookie中的token,才能成功发动攻击
2. same-site属性
cookie自带的有一个sami-site属性,可以控制第三方网站是否携带cookies:
写法:
// 以koa为例
ctx.cookies.set(
'cid', //第一个参数,key
'hello world', //第二个参数,value
{
//第三个参数,cookie设置
domain: 'localhost', // 写cookie所在的域名
path: '/index', // 写cookie所在的路径
maxAge: 10 * 60 * 1000, // cookie有效时长(http1.1)
expires: new Date('2017-02-15'), // cookie失效时间(http1.0)
httpOnly: false, // 是否只用于http请求中获取
sameSite: Strict,
overwrite: false // 是否允许重写
}
)
但是这种方式目前还存在兼容性问题,而且受支持程度并不好。
3. 访问来源
其实简单的还有一种方式:通过 document.referrer
判断当前页面是从哪个链接过来的。通常也会随header携带到服务端,服务端也可以获取到:req.headers.referrer
但是referrer可以被伪造,这种方式非常不安全。
当然,还可以通过如Spring中的某些设置或者一些方式去达到防御的目的。笔者在查阅资料途中发现了一篇文章,手段比较偏后端的。感兴趣的可以点我移步这里查看。