伪协议与编码绕过

这篇博客可以接上我的上一篇关于XSS 的分类与利用方式,这次讲的伪协议与编码绕过也是一种代码注入的攻击方式,伪协议提供了另外一种代码注入点,编码绕过是对应的注入方式。

伪协议是什么

说起来伪协议的概念也比较简单,只不过情况种类比较多。

伪协议不同于因特网上所广泛使用的如http,https,ftp等协议,在url中使用,用于执行特定的功能。
如Data伪协议:data:text/html;base64,PHanflajfAFLGLKSJ=,
JavaScript伪协议:javascript:alert(1);**;

1
2
3
<p>	<a href="javascript:alert(5)">javascript伪协议</a></p>
<p> <a href="data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTs8L3NjcmlwdD4=">data 伪协议 base64</a></p>
<p> <a href="data:text/html;base64,PHNjcmlwdD5hbGVydCgiMSIpOzwvc2NyaXB0Pg==">含双引号data 伪协议 base64</a></p>

编码绕过

伪协议其实客观上又提供了额外的代码注入点,那编码绕过就是我们利用这个注入点进行注入的方式。

这种注入方式主要是为了绕过对<script>标签,括号,引号等的过滤

Unicode编码

在讲编码绕过之前,首先提一嘴这个我们耳熟能详的编码方式。

ISO (国际标谁化组织)制定的包括了地球上所有文化、所有字母和符号 的编码,使用两个字节表示一个字符。

Unicode 只是一个符号集,它只规定了符号的二进制代码,却没有规定 这个二进制代码应该如何存储。

具体存储由:UTF-8,UTF-16等实现

  • JavaScript编码:&#x\u都可以用来表示当前是一串unicode编码,比如字符串test,可以表示为,\u0074\u0065\u0073\u0074,或者&#x0074&#x0065&#x0073&#x0074,或者&#x74&#x65&#x73&#x74
  • HTML实体编码:&#表示当前为HTML实体编码,还是以test为例,&#116&#101&#115&#116
  • URL编码:这个比较常见了,就是百分号开头的,如%74%65%73%74

浏览器解码

解析一篇 HTML 文档时主要有三个处理过程:HTML 解析,URL 解析 和 JavaScript 解析。每个解析器负责解码和解析 HTML 文档中它所对 应的部分,且顺序也有所区别

首先我们先来单独看看每种解析器的效果:

  1. HTML编码
1
2
3
4
5
<p>对于a标签,href属性 javascript:alert(1) 编码比较</p>
<p>1 <a href="javascript:alert(1)"> 未编码</a></p>
<p>2 <a h&#x72;ef="javascript:alert(1)">href中的r进行编码</a></p>
<p>3 <a href="javasc&#x72;ipt:alert(1)">javascript中的r进行编码</a></p>
<p>4 <a href="javascript:ale&#x72;t(1)">alert(1)中的r进行编码</a></p>

这几种编码方式中,只有2是不可以正确解析的,因为2这种方式,破坏了html的文档结构。
因为HTML解析器不对HTML的关键字进行解码,这里没有把2识别为一个正常的html标签,后面就更不会了,所以我们可以看到后面两种,无论换成什么编码方式,都无法正确解码。

  1. URL编码
1
2
3
4
5
<p>对于a标签,href属性 javascript:alert(1) 编码比较</p>
<p>1 <a href="javascript:alert(1)"> 未编码</a></p>
<p>2 <a h%72ef="javascript:alert(1)">href中的r进行编码</a></p>
<p>3 <a href="javasc%72ipt:alert(1)">javascript中的r进行编码</a></p>
<p>4 <a href="javascript:ale%72t(1)">alert(1)中的r进行编码</a></p>

这种情况下,1,4是可以绕过的,2没法绕过的原因同上面,这个东西就不会被当作正常的html解析,而3这种,虽然会被解析成a标签,但是是对javascript这个协议类型进行了url编码,协议也属于url一部分,url解析器就是要根据这个关键字进行识别,现在识别不了,所以没法绕过。

  1. JavaScript编码
1
2
3
4
5
6
<p>对于a标签,href属性 javascript:alert(1) 编码比较</p>
<p>1 <a href="javascript:alert(1)"> 未编码</a></p>
<p>2 <a h\u0072ef="javascript:alert(1)">href中的r进行编码</a></p>
<p>3 <a href="javasc\u0072ipt:alert(1)">javascript中的r进行编码</a></p>
<p>4 <a href="javascript:ale\u0072t(1)">alert(1)中的r进行编码</a></p>
<p>5 <a href="javascript:alert\u00281)">alert(1)中的(进行编码</a></p>

这种也是1,4可以绕过,2是对DOM结构进行了破坏,3是对协议进行了破坏,标签可以正常解析,但是协议是错的,5这种破坏了js的解析,也没法正确执行。

综上,在对应的解析阶段,如果是对该阶段的关键字进行了编码导致结构被破坏,那就没法正确解析。

如html解析阶段对href属性进行了编码,就没法正确解析,html解析起不会认为是a标签有个属性叫href,而是认为有个属性叫h&#x72;ef

url解析器到javasc%72ipt:alert(1),就根本不会认为这是个JavaScript伪协议,故不会对其进行解码。

1
2
<p>4	<a href="javascript:ale\u0072t(1)">alert(1)中的r进行编码</a></p>
<p>5 <a href="javascript:alert\u00281)">alert(1)中的(进行编码</a></p>

这两种都能正确走到最后一步的JavaScript解析阶段,然后就会提取js的代码进行解析,这时候,就看js解析器怎么解析了。

如果对url关键字的编码,如javasc&#x72;ipt,没有在html解码阶段被解析正确,那么url解析阶段就不会解析它了。

多层混淆

上面讲的是单独每个解析器,其实我们也提到了,浏览器解析文档的时候是有顺序的,分别是HTML解析器解析出dom树,URL解析器将dom树中的url属性解码,最后是JavaScript解析器解码dom树中被认为是js代码的部分

只要我们按这个顺序反过来,就可以对一段html代码进行二层甚至三层的混淆编码。

比如二层混淆

1
2
3
<p>1	<a href="javascript:ale%5c%75%30%30%37%32t(1)">alert(1)中的r进行js编码,后url编码</a></p>
<p>2 <a href="javascript:ale&#x5c;&#x75;&#x30;&#x30;&#x37;&#x32;t(1)">alert(1)中的r进行js编码,后html编码</a></p>
<p>3 <a href="javascript:ale&#x25;&#x37;&#x32;t(1)">alert(1)中的r进行url编码,后html编码</a></p>

三层混淆

1
2
<p>对于a标签,href属性 javascript:alert(1) 三层编码漏洞触发</p>
<p>三层解码 <a href="javascript:ale&#x25;&#x35;&#x63;&#x25;&#x37;&#x35;&#x25;&#x33;&#x30;&#x25;&#x33;&#x30;&#x25;&#x33;&#x37;&#x25;&#x33;&#x32;t(1)">alert(1)中的r进行js编码,后url编码,再html编码</a></p>

总结

总结来说,我们上一篇博客讲的xss,开发者可以通过检测关键字,如果script等进行过滤。

但是浏览器提供了伪协议的方式,所以我们又多了一种代码注入方式。

这种方式的好处,就是可以让我们对这些关键字进行编码,从而绕过上面的那种检测。

而这需要了解两点,第一点,浏览器的编码解码方式,第二,解码的顺序。

首先是html解码,这个阶段如果对html的关键则进行了编码,是无法正确解析的,如对a标签的href属性进行了html编码,浏览器会认为,你就是想给a标签一个属性叫做h&#x72;ef,但是这个阶段,可以对其他部分进行html的解码,如javasc&#x72;ipt:alert(1),这段代码中的r被html编码方式编码了,可以在这个阶段被解码(也就是说html解码阶段,只会解码那些非html关键字且用html编码方式编码的)。

其次就是URL解码,这个阶段,如果是javasc%72ipt:alert(1)这种,url编码认为你就是有个协议叫做javasc%72ipt,当然就不会被正确解析。

最后就是JavaScript解码,能走到这个阶段,说明,上面两步都得到正确解析,并认为你这里有一段js代码了,那就按照js解析器的方式去解析。

换个角度理解。

html解码阶段是为了看你有什么标签,标签有什么属性,你对这些东西编码了,其实就是换种写法,解析器当然直接按你说的解析成dom树,至于后续是否是浏览器内核可识别的,解析器并不关心。这一步用不到的信息,可以被html解码方式解码。

html解码完后,对于解析为url部分的,再进行url解码,这一步有一个目的就是为了看看你是什么协议,如果你对协议名字进行了编码,解析器也认为你就是这个协议,至于是不是真有这个协议,也不是解析器要负责的。

最后经过url解析器解析后认为是js代码的部分再进行js的解码。

说到底,这三个步骤都是先解析,你说啥就是啥,你说有个属性叫做h&#x72;ef,那就有,剩下的部分按照当前阶段对应的解码方式解码后交给下一个阶段,下个阶段还是先挑出自己负责的部分解析(不解码),再把剩下的部分按照当前对应阶段的解码方式解码。至于最后能不能运行起来,那不是他们关心的。

比如这个东西:<p>3 <a href="javasc%72ipt:ale\u0072t(1)">javascript中的r进行编码</a></p>,在html解码阶段a标签和href属性可以被正常解析,理论上javasc%72ipt这东西也会被html解码,但是这段不是html编码,所以没被正确解码,到了下个阶段,url解析器,就认为这个东西的协议就是javasc%72ipt,所以下一阶段也不会认为后面是一段js了,即使他可以被js解析器正确解析。