文件目录:

php代码审计【10】CSRF代码审计_HTTP

一、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​

php代码审计【10】CSRF代码审计_php_02

登录成功后如下: 

php代码审计【10】CSRF代码审计_php_03

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

php代码审计【10】CSRF代码审计_HTTP_04

这时候如果我们记录下刚刚添加用户的网页地址,是否无论是哪个用户,只要访问这个地址就能添加用户呢?

为了验证这个想法,接下来我们点击注销登录(清楚cookie信息):

尝试在浏览器输入之前添加用户的页面地址:​​/adduser.php​​,尝试直接添加用户 

 

php代码审计【10】CSRF代码审计_php_05

但是并没有成功,页面也自动跳转到登录页面,为什么呢?

因为adduser.php页面需要验证session信息才能执行相应操作。

所以就有人想到:“既然我们自己不能成功访问这个页面,能否在管理员不知道的情况下,欺骗他访问这个页面呢?”。 我们再次使用admin用户登陆,并新建标签页。 这时假设管理员继续访问网站 B(可以与合法网站在不同服务器上) ,在新标签页里输入url

 

php代码审计【10】CSRF代码审计_HTTP_06

页面B上面存在事先写好的恶意链接,并诱使我们点击(比如做成图片连接): 

再查看用户时,发现后台已经不知不觉添加上了用户:

php代码审计【10】CSRF代码审计_HTTP_07

 

在服务器端防御CSRF主要有四种策略:

  • 验证HTTP Referer 字段

      根据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,则拒绝该请求

  • 在请求地址中添加token并验证

      CSRF之所以能够成功,是因为可以伪造用户的请求,该请求中所有的用户验证信息都存在于Cookie中,因此者可以在不知道这些验证信息的情况下直接利用用户自己的Cookie来通过安全验证

      由此可知,抵御CSRF的关键在于:在请求中放入者所不能伪造的信息,并且该信息不存在于Cookie之中

      鉴于此,系统开发者可以在HTTP请求中以参数的形式加入一个随机产生的token,并在服务器端建立一个拦截器来验证这个token,如果请求中没有token或者token内容不正确,则认为可能是CSRF而拒绝该请求

  • 在HTTP头中自定义属性并验证

      自定义属性的方法也是使用token并进行验证,和前一种方法不同的是,这里并不是把token以参数的形式置于HTTP请求之中,而是把它放到HTTP头中自定义的属性里

      通过XMLHttpRequest这个类,可以一次性给所有该类请求加上csrftoken这个HTTP头属性,并把token值放入其中。这样解决了前一种方法在请求中加入token的不便,同时,通过这个类请求的地址不会被记录到浏览器的地址栏,也不用担心token会通过Referer泄露到其他网站

  • 添加验证码并验证