文件目录:

一、CSRF流程:
- 登录受信任网站A,并在本地生成Cookie
- 在不退出A的情况下,访问危险网站B
二、CSRF 代码复现
文件里包含如下文件index.html 、 login.php 、adduser.php 、catuser.php 、WebB.php、user.txt
index.html
<!DOCTYPE html>
<html>
<head><meta charset="utf-8">
<title>CSRF演示</title>
</head>
<body>
<form name="LoginForm" method="post" actinotallow="login.php" notallow="return InputCheck(this)">
<p>
<label for="username" class="label">用户名:</label>
<input id="username" name="username" type="text" class="input" />
<p/>
<p>
<label for="password" class="label">密 码:</label>
<input id="password" name="password" type="password" class="input" />
<p/>
<p>
<input type="submit" name="submit" value=" 确 定 " class="left" />
</p>
</form>
</body>
</html>
login.php
<?php
header("Content-Type: text/html;charset=utf-8");
session_start();
$csrf_token = md5(uniqid());
$_SESSION['csrf_token'] = $csrf_token;
if($_GET['action'] == "logout") {
unset($_SESSION['username']);
echo '注销登录成功!点击此处 <a href="index.html">登录</a>';
exit;
}
if(!isset($_POST['submit'])){
exit("非法访问!");
}
$username = $_POST['username'];
$password = $_POST['password'];
if($username == 'admin' && $password == 'admin') {
$_SESSION['username'] = $username;
$_SESSION['password'] = $password;
echo $username, '欢迎您!<br />登录成功获得如下功能: <br /><a href="adduser.php">1.添加用户</a><br/>';
echo '<a href="catuser.php">2.查看用户</a><br/>';
echo '<a href="login.php?actinotallow=logout">3.注销</a>登录<br/>';
} else {
exit('登录失败!点击此处 <a href="index.html">返回</a>');
}
?>
adduser.php
<?php
header("Content-Type: text/html;charset=utf-8");
session_start();
if($_SESSION['username'] != 'admin') {
echo 'Error!';
header("Location:index.html");
exit;
}
$log=fopen("user.txt","a");
fwrite($log,'username:csrf'."\r\n");
fwrite($log,'password:csrf'."\r\n");
echo '<br />';
fclose($log);
//echo '<a href="login.php">点击返回</a><br/>';
?>
catuser.php
<?php
header("Content-Type: text/html;charset=utf-8");
session_start();
if($_SESSION['username'] != 'admin') {
echo 'Error!';
header("Location:index.html");
exit;
}
if(file_exists("user.txt"))
{
$read= fopen("user.txt",'r');
while(!feof($read))
{
echo fgets($read)."</br>";
}
fclose($read);
}
?>
WebB.php
<!DOCTYPE html>
<html>
<head><meta charset="utf-8">
<title>Hack</title>
</head>
<body>
<a href="http://localhost/csrf/adduser.php">恶意链接</a>
</body>
</html>
最后创建一个空的user.txt
三、触发:
模拟网站后台管理的页面,用户名、密码都是admin

登录成功后如下:

一般来说后台管理员都具有添加用户的功能,所以登录成功之后点击添加用户(这里为了方便演示,并没有让管理员自定义账户名、密码的功能,而是添加默认的账户(csrf)和密码(csrf),成功后页面不会回显,但实际已经添加成功,我们点击" 2.查看用户 "按钮,查看是否添加默认账户(csrf)和密码(csrf)

这时候如果我们记录下刚刚添加用户的网页地址,是否无论是哪个用户,只要访问这个地址就能添加用户呢?
为了验证这个想法,接下来我们点击注销登录(清楚cookie信息):
尝试在浏览器输入之前添加用户的页面地址:/adduser.php
,尝试直接添加用户

但是并没有成功,页面也自动跳转到登录页面,为什么呢?
因为adduser.php页面需要验证session信息才能执行相应操作。
所以就有人想到:“既然我们自己不能成功访问这个页面,能否在管理员不知道的情况下,欺骗他访问这个页面呢?”。 我们再次使用admin用户登陆,并新建标签页。 这时假设管理员继续访问网站 B(可以与合法网站在不同服务器上) ,在新标签页里输入url

页面B上面存在事先写好的恶意链接,并诱使我们点击(比如做成图片连接):
再查看用户时,发现后台已经不知不觉添加上了用户:

在服务器端防御CSRF主要有四种策略:
根据HTTP协议,在HTTP头中有一个字段叫Referer,它记录了该HTTP请求的来源地址
在通常情况下,访问一个安全受限页面的请求必须来自于同一个网站
比如某银行的转账是通过用户访问http://bank.test/test?page=10&userID=101&mnotallow=10000页面完成,用户必须先登录bank.test,然后通过点击页面上的按钮来触发转账事件
当用户提交请求时,该转账请求的Referer值就会是转账按钮所在页面的URL(本例中,通常是以bank. test域名开头的地址)
而如果者要对银行网站实施CSRF,他只能在自己的网站构造请求,当用户通过攻者的网站发送请求到银行时,该请求的Referer是指向者的网站。因此,要防御CSRF,银行网站只需要对于每一个转账请求验证其Referer值,如果是以bank. test开头的域名,则说明该请求是来自银行网站自己的请求,是合法的。如果Referer是其他网站的话,就有可能是CSRF,则拒绝该请求
CSRF之所以能够成功,是因为可以伪造用户的请求,该请求中所有的用户验证信息都存在于Cookie中,因此者可以在不知道这些验证信息的情况下直接利用用户自己的Cookie来通过安全验证
由此可知,抵御CSRF的关键在于:在请求中放入者所不能伪造的信息,并且该信息不存在于Cookie之中
鉴于此,系统开发者可以在HTTP请求中以参数的形式加入一个随机产生的token,并在服务器端建立一个拦截器来验证这个token,如果请求中没有token或者token内容不正确,则认为可能是CSRF而拒绝该请求
自定义属性的方法也是使用token并进行验证,和前一种方法不同的是,这里并不是把token以参数的形式置于HTTP请求之中,而是把它放到HTTP头中自定义的属性里
通过XMLHttpRequest这个类,可以一次性给所有该类请求加上csrftoken这个HTTP头属性,并把token值放入其中。这样解决了前一种方法在请求中加入token的不便,同时,通过这个类请求的地址不会被记录到浏览器的地址栏,也不用担心token会通过Referer泄露到其他网站