Skip to content

目录

核心思想

前端安全的核心在于"信任"问题。我们永远不能信任任何来自用户(或第三方)的输入。XSS 和 CSRF 是两种最常见、也最具代表性的 Web 攻击方式,它们都巧妙地利用了网站对用户或浏览器行为的"信任"。


一、XSS (Cross-Site Scripting) 跨站脚本攻击

1. 攻击原理

攻击者将恶意的 JavaScript 脚本注入到网页中,当其他用户访问该网页时,这些恶意脚本就会在用户的浏览器中执行,从而窃取用户信息(如 Cookie)、操作用户行为、或在页面上展示恶意内容。

本质: 恶意代码在受害者的浏览器上"无中生有"地执行了。


2. 攻防演练:一个简单的评论区

【攻】攻击演示

假设一个网站的评论区,后端直接将用户提交的评论内容插入到页面的 HTML 中。

易受攻击的代码 (前端渲染)

javascript
// 从后端获取评论列表
const comments = [
  { author: '小明', text: '这是一条正常的评论。' },
  {
    author: 'Hacker',
    text: '<script>alert("XSS Attack! Your cookie is: " + document.cookie)</script>'
  }
]

// 前端直接将内容渲染到页面
const commentContainer = document.getElementById('comment-list')
comments.forEach(comment => {
  // 危险操作:直接使用 innerHTML 渲染未经过处理的用户输入
  commentContainer.innerHTML += `<div><strong>${comment.author}:</strong> ${comment.text}</div>`
})

攻击效果

当任何用户浏览这个评论区时:

  1. 浏览器在渲染 Hacker 的评论时,会遇到 <script> 标签。
  2. 浏览器会立即执行其中的 JavaScript 代码。
  3. 页面会弹出一个警告框,显示当前用户的 Cookie。攻击者可以将 alert 换成更复杂的代码,将 Cookie 发送到自己的服务器,从而盗用用户身份。

【防】防御策略

核心防御思路是:"数据"永远是"数据",绝不能让它变成"代码"。

策略一:HTML 转义 - 主要防御手段

在将用户输入的内容输出到页面时,对具有特殊含义的 HTML 字符(如 <, >, &, ", ')进行转义。

修复后的防御代码

javascript
function escapeHTML(str) {
  if (!str) return ''
  return str
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#39;')
}

// ... 获取评论列表的代码 ...

comments.forEach(comment => {
  // 安全的做法:先转义,再渲染
  const safeText = escapeHTML(comment.text)
  // 或者使用 textContent 属性,它会自动处理转义
  const div = document.createElement('div')
  const strong = document.createElement('strong')
  strong.textContent = comment.author + ': '
  div.appendChild(strong)
  div.appendChild(document.createTextNode(safeText)) // 使用 createTextNode 也是绝对安全的
  commentContainer.appendChild(div)
})

防御效果

经过转义后,Hacker 的恶意评论在 HTML 源码中会变成:

html
<div>
  <strong>Hacker:</strong> &lt;script&gt;alert("XSS Attack! Your cookie is: " +
  document.cookie)&lt;/script&gt;
</div>

浏览器只会将这段代码作为纯文本显示在页面上,而不会执行它。用户看到的就是<script>alert(...)</script>这段字符串本身。

策略二:内容安全策略 - 深度防御

CSP 通过设置 HTTP 头部,告知浏览器一个"白名单",规定页面只允许加载和执行来自特定来源的脚本。即使攻击者成功注入了脚本,由于脚本来源不在白名单内,浏览器也会拒绝执行。

示例 HTTP 头部

Content-Security-Policy: script-src 'self' https://trusted.cdn.com;

这个策略告诉浏览器:只允许执行来自当前域名和 https://trusted.cdn.com 的脚本。所有内联脚本和来自其他域的脚本都将被阻止。


二、CSRF (Cross-Site Request Forgery) 跨站请求伪造

1. 攻击原理

攻击者诱导一个已经登录的受害者,在不知情的情况下,从一个第三方恶意网站向被攻击网站发送一个伪造的请求(如转账、修改密码等)。由于这个请求是受害者浏览器发出的,会自动带上被攻击网站的 Cookie,因此服务器会误以为这是用户的真实意图并执行。

本质: 攻击者冒用你的身份,发送了一个你并非本意的请求。请求是合法的,但并非出自你的意愿。


2. 攻防演练:一个简单的转账操作

【攻】攻击演示

假设 bank.com 有一个通过 GET 请求就能完成转账的功能。

易受攻击的 API (bank.com)

javascript
// GET /transfer?to=Hacker&amount=1000
// 只要用户登录了 bank.com,访问这个 URL 就会立刻转账。
// 服务器仅通过 Cookie 判断用户身份。

恶意网站的代码

攻击者在自己的网站上放置一个看不见的图片标签。

html
<!-- hacker.com/malicious_page.html -->
<h1>劲爆!点击查看!</h1>
<!-- 用户看不到这个图片,但浏览器会尝试加载它 -->
<img
  src="http://bank.com/transfer?to=Hacker&amount=1000"
  width="1"
  height="1"
  style="display:none;"
/>

攻击效果

  1. 用户登录了 bank.com,Cookie 已保存在浏览器中。
  2. 用户被诱导访问了 hacker.com 的恶意页面。
  3. 浏览器看到 <img> 标签,会立即尝试加载其 src 指向的 URL,也就是向 bank.com 发起一个 GET 请求。
  4. 根据同源策略,浏览器在发送这个跨站请求时,会自动带上 bank.com 域下的 Cookie。
  5. bank.com 的服务器收到请求,验证 Cookie 发现是合法用户,于是执行转账操作。用户在毫不知情的情况下损失了 1000 元。

【防】防御策略

核心防御思路是:增加一个攻击者无法伪造的、与用户会话绑定的"凭证"。

策略一:Anti-CSRF Token (同步令牌模式) - 主要防御手段

  1. 当用户访问表单页面时,服务器生成一个随机的、唯一的 token,并将其嵌入到表单的隐藏字段中。同时,服务器在 session 中也保存这个 token。
  2. 当用户提交表单时,这个 token 会随表单一起发送到服务器。
  3. 服务器比较表单提交的 token 和 session 中保存的 token。如果两者一致,则为合法请求;如果不一致,则拒绝请求。

修复后的防御代码 (以 Node.js/Express 为例)

  1. 渲染表单时,加入 token
javascript
// GET /show_transfer_form
app.get('/transfer', (req, res) => {
  // 生成 token 并存入 session
  req.session.csrfToken = generateRandomToken()
  res.render('transfer_form', { csrfToken: req.session.csrfToken })
})
  1. HTML 表单中嵌入 token
html
<form action="/transfer" method="POST">
  <input type="hidden" name="_csrf" value="<%= csrfToken %>" />
  <input type="text" name="to" placeholder="收款人" />
  <input type="text" name="amount" placeholder="金额" />
  <button type="submit">转账</button>
</form>
  1. 处理请求时,验证 token
javascript
// POST /transfer
app.post('/transfer', (req, res) => {
  if (req.body._csrf !== req.session.csrfToken) {
    return res.status(403).send('Invalid CSRF Token')
  }
  // Token 验证通过,执行转账逻辑...
  // ...
  res.send('转账成功!')
})

防御效果

攻击者在 hacker.com 无法得知 bank.com 在用户 session 中生成的 csrfToken 是什么,因此他们无法伪造一个带有正确 _csrf 值的请求。当伪造请求到达服务器时,会因为 token 验证失败而被拒绝。

策略二:SameSite Cookie 属性 - 釜底抽薪

通过在设置 Cookie 时增加 SameSite 属性,可以告诉浏览器在跨站请求中是否要携带这个 Cookie。

Set-Cookie: session_id=...; HttpOnly; SameSite=Strict
  • SameSite=Strict: 最严格。任何跨站请求(包括从收藏夹打开、地址栏输入)都不会携带 Cookie。CSRF 攻击完全失效。
  • SameSite=Lax: 较为宽松。允许在一些安全的顶级导航 GET 请求中携带 Cookie(如点击链接跳转),但对于 POST、<img><iframe> 等高风险的跨站请求,则不会发送 Cookie。这也能有效防御绝大多数 CSRF 攻击。

这是目前浏览器层面最简单、最有效的 CSRF 防御手段。


总结对比

特征XSS (跨站脚本)CSRF (跨站请求伪造)
攻击目标窃取用户在客户端的信息和权限冒用用户身份,执行服务端的操作
信任利用网站信任了用户输入的内容网站信任了用户的浏览器(的Cookie)
代码执行地受害者浏览器中攻击者无法注入或执行代码
核心防御输入过滤和输出转义,不让数据变代码Token 或 SameSite Cookie,验证请求来源

基于 VitePress 的本地知识库