一、靶场原理分析
(1)理论分析
漏洞类型: DOM 型 XSS。攻击的全部逻辑都发生在受害者的浏览器中,与服务器无关。
数据来源 (Source):
location.hash
。location.hash
是 URL 中#
(井号/哈希符号) 及其后面的部分。例如,在https://example.com/page#section1
中,location.hash
的值就是#section1
。它的主要用途是在页面内部实现导航(锚点链接),并且改变 hash 值不会导致整个页面重新加载。
危险函数 (Sink): jQuery 的
$
选择器函数。$()
函数通常用来选取页面元素,例如$('#header')
会选中 id 为header
的元素。漏洞关键点:在某些(尤其是旧版本的)jQuery 中,如果你给
$
函数传入一个包含 HTML 标签的字符串,比如$('<div>Hello</div>')
,它不会去选择元素,而是会尝试创建这个 HTML 元素。如果创建的这个 HTML 元素带有可以执行脚本的事件处理器(如
onerror
),脚本就会被执行。这个靶场的代码很可能就是这样:$(location.hash)
。
触发机制 (Trigger):
hashchange
事件。这意味着有漏洞的代码并不是在页面加载时立即执行的,而是在 URL 的 hash 值发生改变时才会被触发。
例如,用户从
.../page
跳转到.../page#section1
,或者从.../page#section1
跳转到.../page#section2
,都会触发hashchange
事件。
总结一下漏洞流程: 网站首页有一段 JavaScript 代码,它会监听 URL hash 的变化。当 hash 改变时,它会获取新的 hash 值,并将其直接传递给 jQuery 的 $
选择器函数,试图滚动到页面上对应的博客文章。由于 $
函数可以解析并创建 HTML,攻击者可以通过构造一个恶意的 hash 值来注入并执行任意 JavaScript。
这段代码实现页面内“自动滚动到指定文章功能”的脚本,但它包含了一个典型的基于 DOM 的跨站脚本(DOM-XSS)漏洞。
(2)靶场分析
1.代码详解
<script>
// 1. 当 URL 的 hash 部分发生变化时,执行一个函数
$(window).on('hashchange', function(){
// 2. 构造一个 jQuery 选择器来查找文章标题
var post = $('section.blog-list h2:contains("' + decodeURIComponent(window.location.hash.slice(1)) + '")');
// 3. 如果找到了对应的文章
if (post.get(0)) {
// 4. 就滚动到那个文章的位置
post.get(0).scrollIntoView();
}
});
</script>
第 1 行: $(window).on('hashchange', function(){ ... });
这是一个事件监听器。
hashchange
事件:当浏览器地址栏中#
号后面的部分发生改变时,这个事件就会被触发。例如,从.../index.html
变为.../index.html#welcome-post
。功能: 监听 URL
hash
的变化,一旦变化就执行括号里的代码。
第 2 行: var post = ...
这是最核心、也是漏洞所在的一行。我们从里到外看:
window.location.hash
: 获取 URL 中从#
开始的部分 (例如:#welcome-post
)。.slice(1)
: 去掉第一个字符(也就是#
号),得到welcome-post
。decodeURIComponent(...)
: 将 URL 编码的字符解码回原始字符(例如,%20
会变回空格)。这就是不安全的数据来源 (Source)。'section.blog-list h2:contains(" ... ")'
: 这是一个 jQuery 选择器字符串。它的意思是:“请找到 class 为
blog-list
的<section>
元素内部的、包含了特定文本的<h2>
标签。”整个这行代码的作用是,将从 URL 中获取的、用户可控的文本,直接拼接成一个 jQuery 选择器,然后用这个选择器去页面上寻找元素。这就是危险操作 (Sink)。
第 3 & 4 行: if (post.get(0)) { ... }
post.get(0)
: 检查 jQuery 是否真的找到了至少一个匹配的元素。scrollIntoView()
: 如果找到了,就调用这个浏览器原生函数,将页面滚动到该元素,使其在用户视野内可见。
2.总结与漏洞分析
代码的正常功能
当用户访问 https://[网址]/#Some-Post-Title 时,这段代码会找到页面上标题为 “Some Post Title” 的 <h2> 标签,并自动把页面滚动到这个标题的位置,方便用户阅读。
漏洞所在
代码完全信任了来自 location.hash 的用户输入,并将其直接拼接到 jQuery 选择器字符串中。攻击者可以构造一个恶意的 hash 值,这个值在拼接后不再是一个有效的选择器,而是一段可以执行的 HTML 代码。
例如,一个攻击载荷可能是:
#")<img src=x οnerrοr=print()>
当这个 hash 被代码处理时,第 2 行构造出的选择器字符串会变成:
$('section.blog-list h2:contains("")<img src=x οnerrοr=print()>")')
jQuery 在解析这个混乱的字符串时,会先执行前面的选择器 ...:contains("")
,然后它会看到后面的 <img ...>
标签,并尝试创建这个 HTML 元素。创建过程中,图片因 src=x
加载失败,从而触发 onerror
事件,执行了恶意的 print()
函数,导致 XSS 攻击成功。
直接拼接到URL后确实可以,但是通常用户不会自己去改到这个参数,所以需要攻击者在自己服务器上修改 hash ,通过钓鱼邮件、社交工程、论坛发帖等方式,将这个恶意页面 URL 发给受害者,受害者访问后,浏览器加载恶意页面,这也是本题通关的方式。
注意:
此时我们尝试在url后面修改 hash (需要注意进入网页时并没有 hash ,所以在第一次在url后面加上#hash 不会触发hasnchange事件,也就是不会滚动到指定位置;而在我们第二次改动 hash 时就会触发事件)。因此当我们随便在#后面加一个值,然后第二次在#后面加入我们想要滚动的位置,就会滚动到指定位置。
二、解决靶场详细步骤
我们的目标是在 hash
中放入一段能够调用 print()
函数的 HTML 代码。
关键点:和之前的靶场类似,直接用 <script>
标签可能因为安全限制而无法执行。因此,最好的方法是使用带有事件处理器的 HTML 标签。
第一步:构造攻击载荷 (Payload)
我们将使用 <img>
标签和它的 onerror
事件,因为这个方法非常可靠。
Payload:<img src=x onerror="print()">
<img>
: 创建一个图像标签。src=x
: 尝试加载一个名为x
的图片。这显然是一个无效的地址,所以图片加载一定会失败。onerror="print()"
: 当图片加载失败时,就会触发onerror
事件,从而执行我们预设的print()
函数。
第二步:构建攻击 URL
你需要将上面构造的 payload 附加到靶场主页 URL 的 #
后面。
最终的攻击 URL:https://[靶场URL]/#<img src=x onerror="print()">
第三步:实施攻击
直接在浏览器中测试: 将上述 URL 粘贴到你自己的浏览器地址栏并访问。如果页面立即弹出了打印窗口,说明你的 payload 是有效的,上面漏洞漏洞所在也证明。
交付给受害者 (完成靶场):
进入 PortSwigger 的靶场中的 “Go to exploit server” (进入攻击服务器) 。
在攻击服务器的
body
部分,你需要创建一个能将受害者浏览器重定向到你恶意 URL 的方式。最简单的方法是使用一个iframe
。在攻击服务器的 body 输入框中填入以下代码。其中,<iframe> 标签是HTML 中用来在当前页面嵌入另一个网页的标签。这里 src="https://YOUR-LAB-ID.web-security-academy.net/#" 指定了这个内嵌页面的初始 URL。这个 URL 带有一个空的 hash(#),相当于打开了目标页面,但 hash 为空;this 指向当前的 iframe 元素,给 iframe 的 src 属性追加字符串 '<img src=x οnerrοr=print()>' ;onload 是当 <iframe> 加载完内容后会执行的事件处理器(JavaScript 代码)。
<iframe src="https://YOUR-LAB-ID.web-security-academy.net/#" onload="this.src+='<img src=x onerror=print()>'"></iframe>
点击 “Deliver exploit to victim” (向受害者交付漏洞利用)。靶场的模拟受害者会访问你的攻击页面,加载这个
iframe
,从而触发漏洞并调用print()
函数,最终判定你成功解决了这个靶场。