XSS
一、什么是XSS?
- Cross-Site Scripting(跨站脚本攻击)为了与CSS作区分故简称 XSS,是一种代码注入攻击 。攻击者通过在目标网站上注入恶意脚本,使之在用户的浏览器上运行。利用这些恶意脚本,攻击者可获取用户的敏感信息如 Cookie、SessionID 等,进而危害数据安全。
- 本质
- 恶意代码未经过滤,与网站正常的代码混在一起;浏览器无法分辨哪些脚本是可信的,导致恶意脚本被执行。
- 可能被注入恶意脚本的内容
- 来自用户的 UGC 信息
- 来自第三方的链接
- URL 参数
- POST 参数
- Referer (可能来自不可信的来源)
- Cookie (可能来自其他子域注入)
二、XSS攻击方式
-
分为三种:存储型 、反射型 、DOM 型
存储型XSS
- 它不需要用户手动触发,所有浏览者访问页面时都会被XSS
- 常见于带有用户保存数据 的网站功能,如论坛发帖、商品评论、用户私信等
反射型XSS
- 需要用户主动打开恶意的 URL 才能生效,攻击者往往会结合多种手段诱导用户点击
- 常见于通过 URL 传递参数 的功能,如网站搜索、跳转等
DOM型XSS
- 属于前端 JavaScript 自身的安全漏洞
区别
特征 | 存储型 XSS | 反射型 XSS | DOM 型 XSS |
---|---|---|---|
存储位置 | 恶意代码存储在服务器数据库 | 恶意代码不存储,仅存在于URL参数 | 恶意代码不存储,存在于URL片段 |
触发条件 | 用户访问被感染的页面 | 用户点击包含恶意参数的链接 | 用户点击包含恶意片段(#)的链接 |
数据流 | 服务器 → 用户浏览器 | 服务器反射参数 → 用户浏览器 | 完全在客户端处理(不经过服务器) |
持久性 | 长期存在(直到数据被删除) | 一次性(仅当用户点击链接时) | 一次性(仅当用户点击链接时) |
攻击范围 | 所有访问该页面的用户 | 仅点击特定链接的用户 | 仅点击特定链接的用户 |
三、常见的XSS攻击场景
社交媒体评论------存储型XSS
- 攻击者在评论区提交恶意脚本(如
<script>sendCookiesToAttacker()</script>
),该评论被存储到服务器 。其他用户访问该页面时,恶意脚本自动加载并窃取其会话Cookie。
html
<!-- 用户评论内容 -->
<div class="comment">
这篇文章写得真好!<script>sendCookiesToAttacker()</script>
</div>
搜索功能------反射型XSS
- 网站搜索功能未过滤输入参数,攻击者构造恶意链接:
https://example.com/search?q=<script>alert('XSS攻击')</script>
,用户点击链接 后,服务器返回的页面包含未过滤的恶意脚本。
单页面应用路由------DOM型XSS
- SPA前端路由根据URL参数动态加载内容,但未对参数过滤。攻击者构造链接:
https://example.com/#/profile?username=<script>alert('XSS攻击')</script>
,用户点击链接 ,前端脚本 将username
参数直接渲染到页面,触发XSS。
四、防御策略
预防存储型XSS
##### 输入过滤
* 对于**明确的输入类型**
* 如数字、URL、电话号码、邮件,使用`escapeHTML()`可以把用户的输入内容进行转义
| 原始字符 | 转义后实体编码 |
|:-----|:--------|
| `<` | `<` |
| `>` | `>` |
| `&` | `&` |
| `"` | `"` |
| `'` | `'` |
```js
// 用户输入:<script>alert('XSS攻击')</script>
const escaped = escapeHTML(userInput);
document.getElementById('content').innerHTML = escaped;
// 输出:<script>alert('XSS攻击')</script>
```
* 更推荐使用成熟且完善的**现有库**
```js
// 使用 DOMPurify(支持更复杂的净化)
import DOMPurify from 'dompurify';
DOMPurify.sanitize(userInput, { ALLOWED_TAGS: [] });
```
* **特殊场景**补充
| 场景 | 额外需要转义的字符 | 方法 |
|:-------|:------------------|:--------------------------|
| URL参数 | `%` `#` | 配合使用 `encodeURIComponent` |
| CSS属性 | `\` `()` | 使用 `CSS.escape()` |
| JSON数据 | `\u2028` `\u2029` | `JSON.stringify` |
-
对于不明确的类型
- 最好不要使用输入过滤,因为当把转义后的数据发送到后端再回到前端,赋值给一个变量 之后,展示出来的效果会变成乱码 (如
5<7
会变成5<7
)
- 最好不要使用输入过滤,因为当把转义后的数据发送到后端再回到前端,赋值给一个变量 之后,展示出来的效果会变成乱码 (如
预防存储型XSS和反射型XSS
##### 纯前端渲染
* 现代框架的自动防护
* **React**:默认转义所有插值内容
```react
function UserContent({ text }) {
return <div>{text}</div>; // 自动转义 `<` `>` 等字符
}
// 用户输入 `<script>alert('XSS攻击')</script>` 会显示为文本,不会执行
```
* **Vue**:模板插值自动编码
```vue
<template>
<div>{{ userInput }}</div> <!-- 自动转换为文本 -->
</template>
```
* **Angular**:插值绑定安全处理
```html
<div>{{ userInput }}</div> <!-- 输出内容自动转义 -->
```
| 安全API | 危险替代品 | 场景 |
|:-----------------------------------------|:---------------------|:--------|
| `textContent` | `innerHTML` | 插入纯文本内容 |
| `setAttribute` | `innerHTML` | 设置元素属性 |
| `document.createElement` + `appendChild` | `insertAdjacentHTML` | 动态创建节点 |
##### 转义HTML
* 使用更完善更细致的转义库`org.owasp.encoder`,以下代码引用自 [org.owasp.encoder 的官方说明](https://link.juejin.cn/?target=https%3A%2F%2Fwww.owasp.org%2Findex.php%2FOWASP_Java_Encoder_Project%23tab%3DUse_the_Java_Encoder_Project "https://link.juejin.cn/?target=https%3A%2F%2Fwww.owasp.org%2Findex.php%2FOWASP_Java_Encoder_Project%23tab%3DUse_the_Java_Encoder_Project")。
```html
<!-- HTML 标签内文字内容 -->
<div><%= Encode.forHtml(UNTRUSTED) %></div>
<!-- HTML 标签属性值 -->
<input value="<%= Encode.forHtml(UNTRUSTED) %>" />
<!-- CSS 属性值 -->
<div style="width:<= Encode.forCssString(UNTRUSTED) %>">
<!-- CSS URL -->
<div style="background:<= Encode.forCssUrl(UNTRUSTED) %>">
<!-- JavaScript 内联代码块 -->
<script>
var msg = "<%= Encode.forJavaScript(UNTRUSTED) %>";
alert(msg);
</script>
<!-- JavaScript 内联代码块内嵌 JSON -->
<script>
var __INITIAL_STATE__ = JSON.parse('<%= Encoder.forJavaScript(data.to_json) %>');
</script>
<!-- HTML 标签内联监听器 -->
<button
onclick="alert('<%= Encode.forJavaScript(UNTRUSTED) %>');">
click me
</button>
<!-- URL 参数 -->
<a href="/search?value=<%= Encode.forUriComponent(UNTRUSTED) %>&order=1#top">
<!-- URL 路径 -->
<a href="/page/<%= Encode.forUriComponent(UNTRUSTED) %>">
<!--
URL.
注意:要根据项目情况进行过滤,禁止掉 "javascript:" 链接、非法 scheme 等
-->
<a href='<%=
urlValidator.isValid(UNTRUSTED) ?
Encode.forHtml(UNTRUSTED) :
"/404"
%>'>
link
</a>
```
预防DOM型XSS
- 使用
.innerHTML
、.outerHTML
、document.write()
时要特别小心,不要把不可信的数据作为 HTML 插到页面上,而应尽量使用.textContent
、.setAttribute()
- DOM 中的内联事件监听器,如
location
、onclick
、onerror
、onload
、onmouseover
等,<a>
标签的href
属性,JavaScript 的eval()
、setTimeout()
、setInterval()
等,都能把字符串作为代码运行
其他预防方法
-
httpOnly: 在 cookie 中设置 HttpOnly 属性后,js脚本将无法读取到 cookie 信息。
js// 后端实现 app.post('/login', (req, res) => { res.cookie('sessionID', 'xxxx', { httpOnly: true, // 关键:禁止JS访问 secure: true, // 仅通过HTTPS传输 sameSite: 'Lax' // 防御CSRF }); res.send('登录成功'); });
-
白名单:
-
输入白名单验证
jsfunction validateInput(input) { // 只允许字母、数字、空格以及特定的标签 const whiteListPattern = /^[a-zA-Z0-9\s<>/bB/iI]*$/; return whiteListPattern.test(input); } const userInput = "<script>alert('xss')</script>"; if (!validateInput(userInput)) { // 处理非法输入 console.log("Invalid input!"); }
-
HTML标签/属性白名单
js// 使用DOMPurify库配置白名单 import DOMPurify from 'dompurify'; const dirtyHTML = '<b class="safe">合法内容</b><script>恶意代码</script>'; const cleanHTML = DOMPurify.sanitize(dirtyHTML, { ALLOWED_TAGS: ['b', 'i', 'em'], // 允许的标签 ALLOWED_ATTR: ['class', 'style'], // 允许的属性 FORBID_TAGS: ['style', 'script'] // 强制禁止的标签 }); console.log(cleanHTML); // 输出:<b class="safe">合法内容</b>
-
-
CSP:
-
基础配置
httpContent-Security-Policy: default-src 'self'; # 默认只允许同源资源 script-src 'self' # 脚本仅限同源 https://trusted.cdn.com 'nonce-abc123'; # 允许带特定nonce的内联脚本 style-src 'self' 'unsafe-inline'; # 允许内联样式(慎用) img-src * data:; # 允许所有图片源(根据需求收紧) font-src 'self'; frame-src 'none'; # 禁止嵌入iframe report-uri /csp-report; # 违规报告地址
-