全书4篇,第一篇包含了对安全的看法和态度,思考问题的方式以及做事的方法,理解了这些,就能明白在涉及到的解决方案在抉择时的取舍。第二篇讲客户端脚本安全。
浏览器安全
浏览器才是互联网最大的入口,很多人用户上网的工具都是浏览器。
同源策略
同源策略是一种约定,web 都是构建在同源策略的基础上的,浏览器只是针对同源策略做了主要的实现。浏览器的同源策略,限制了来自不同源的文档或脚本,对当前文档读取或设置某些属性。重点在于对当前域的保护。
浏览器同源策略主要限制跨域读操作。不同源的文档、脚本,无法获取当前域的 dom、cookie。跨域的 xhr 请求无法访问当前域的资源。
并没有限制加载资源的标签进行跨域请求。这些带有src
属性的标签每次加载的时候,实际上浏览器会发送一个get请求
。与 xhr 不同的是,通过 src 属性加载的资源,没法用 js 来读取或设置其中的内容。这些标签加载的资源都是由浏览器负责解析的。
随着需求的多样化,不可避免需要进行跨域的请求。官方提出了针对 xhr 的跨域方案,cors。cors 通过目标域返回的 http 首部来授权是否允许跨域。因为 http 头部是没法通过 js 访问控制的,这是 cors 方案的安全基础。
ie8存在的一个 css 跨域漏洞。
<!-- www.a.com/a.html -->
<body>
{} body{
font-family:
aaaaaaaaaaaaa
bbbbbbbbbbbbbbb
</body>
<!-- www.b.com/b.html -->
<style>
@import url(http://www.a.com/a.html);
</style>
<script>
setTimeout(function () {
var t = document.body.currentStyle.fontFamily;
alert(t);
}, 2000);
</script>
在 b.html 里通过 @import 加载了另一个域的 a.html 作为 css 文件,渲染到了当前页面的 dom 中,通过 currentStyle 能访问到 a.html 内部的内容,还可以修改。这就绕过了同源策略,成了一个跨域漏洞。<br />这个问题出自 ie 的 css 解析的过程,ie 将 font-family 后面的内容当作了 value,从而可以读取到跨域文档的页面内容。
浏览器沙箱
在网页中插入一段恶意代码,利用浏览器漏洞执行这段代码的攻击方式,黑话叫做“挂马”。
对了抵御挂马这个主要威胁。浏览器厂商研发出了针对性的技术,比如密切结合操作系统的保存技术,dep、aslr、safeseh。
并且从单进程改进到多进程架构。将浏览器的多个功能模块分开,谷歌浏览器是第一个采用多进程架构的浏览器,主要分为浏览器进程、渲染进行、插件进程、扩展进程。每个进程间严格隔离。渲染引擎就运行在沙箱中,网页代码与其他进程间通信需要通过 ipc 通道,在通信的过程中进行安全检查。
沙箱可以起到隔离资源的作用,主要目的是让不可信任的代码运行在一个特定的环境中,限制不可信任代码访问沙箱这个区域外的资源。
除了同源策略、沙箱,浏览器还推出了其他的一些安全功能,恶意地址拦截、csp,内容安全策略。
恶意地址拦截
为了应对 挂马网站、钓鱼网站。浏览器厂商推出了恶意地址拦截功能。
浏览器周期性的从服务器获取一份最新的恶意网址黑名单,当用户访问的网址在黑名单里面时,浏览器弹出警告页面。
xss
cross site script,跨站脚本。一般指黑客通过 html注入 的方式将恶意代码插入到页面中,在用户浏览页面的时候,让浏览器执行恶意代码的攻击方式。
一开始这种攻击方式是在跨域的场景,但是现在跨不跨域已经不重要了。xss 是web安全长期的头号大敌,因为 xss 破坏力强,产生的场景复杂,很难一次解决。针对不同场景的 xss,需要区分对待。可以分为3类,反射型、存储型、dom型。
反射型 xss,只是简单的将用户输入的数据反射给浏览器,需要用户执行一些操作,才能执行恶意代码。
存储型 xss,将用户输入的数据存储在服务端。破坏力很强。
dom型 xss,从效果上来说,dom型很像反射型,但是这种类型是通过修改页面的 dom 节点形成的 xss。
xss payload
xss 攻击成功的话,会在用户浏览的页面植入恶意脚本,可以是 js 脚本或者 flash 脚本。所以其他 js 脚本能完成的任务,xss payload 也行,xss payload 就是一个脚本。
一种常见的 xss 脚本就是获取浏览器的 cookie 对象,进行 cookie 劫持攻击。cookie 的 httponly 属性可以防止 cookie 劫持。
攻击者先加载一个跨域的远程脚本,xss 脚本就写在这个远程脚本中,脚本中创建一个 img 元素,设置 src 属性,将 cookie 作为参数发送给远程服务器。如果这个 cookie 没有绑定客户端信息,那么攻击者在拿到用户的 cookie 之后,不用输入账号密码就可以登录用户的账户了。
构造 get 请求、post 请求。
一个web应用,其实只要 get、post 就可以完成大部分的操作。比如删除一篇博客只需要 get 请求,携带上 id 参数,那么可以通过 xss 植入一段脚本,在脚本中创建一个图片,设置 src 为删除 url,指定文章 id,诱导用户执行这个脚本就可以完成删除操作了。
还可以通过构造 form 表单并自动提交或者 xhr api 发送 post 请求。构造 form 表单的话也有两种方式,使用 dom api 或者拼接 html 赋值给 innerHtml 属性。
这些方式都是脚本自动执行的,缺少与用户交互的过程。
在构造表单的时候,如果需要用户输入验证码,或者输入旧密码,那么稍稍提升了实施 xss 攻击的复杂度。就是说还是可以 xss 的。比如说验证码,可以通过 xss payload 获取验证码的图片发送到远程服务器,让服务器来获取验证码,并将验证码发送给 xss payload,绕过验证码。
为了获的旧密码,需要与“钓鱼”结合。利用js 伪造一个登录框,将用户输入的账号密码发送给黑客的服务器。
通过分析浏览器不同版本之间的细微差别,可以准确判断出浏览器的版本。浏览器的不同版本拥有自己独有的对象。通过 user-agent 来识别是不准确的,因为这个对象可以自定义,或者被屏蔽。
还可以识别用户安装了哪些软件,访问过哪些url,获取用户真实的ip地址。
xss 终极形态,xss worm,xss 蠕虫。2005年,19岁的 Samy 发起了对 MySapce.com 的 xss 蠕虫攻击。在几小时内感染了100万用户。MySapce 过滤了很多危险的标签,危险的属性,然而放过了 style 属性,通过 style 属性,还是可以构造 xss 的。其次,MySapce 过滤了“javascript、onreadystatechange”等敏感字符。然而Samy利用“拆分法”绕过了这些限制。最后通过 ajax 构造 post 请求,在每个用户的自我介绍里加上了自己的名字,并且复制蠕虫自身进行了传播。
xss 蠕虫的破坏力和影响力很强。想发起 xss 蠕虫需要一定的条件,需要与用户交互,并且存储型 xss 比较容易发起 xss 蠕虫。
xss 构造技巧
利用字符编码,绕过长度限制,使用 base 标签,window.name。todo 待笔记。
xss 防御
主要的方式有检查输入输出、转义、对富文本。
首当其冲,给 cookie 设置 httponly 属性。严格来说 httponly 是防止 xss 攻击成功后可能发生的 cookie 劫持攻击。并不是所有 cookie 都需要设置这个属性,仅用于认证的关键 cookie。早期 apace 服务器的 trace 方法,可以在响应报文中获取到请求的头部,这就包括了 cookie。
输入检查
一些攻击会利用特殊的字符,这些特殊的字符正常的用户一般都用不到。比如输入用户名、电话、有点、生日这些信息都特定的格式规范。
不仅要在客户端进行检查,在服务端也需要进行检查。黑客可能绕过客户端的输入,直接请求服务器。
xss filter 是比较经典的输入检查实现。不仅可以检查特殊字符,还可以匹配敏感字符,比如<script>、javascript
。但是这种实现有两个问题,对输出的语境不能完全理解,可能改变用户数据的语义。如果页面中是一个变量,用户提交的数据作为变量插入到页面中,这时候 xss filter 可能会漏报。另一种情况是 xss filter 发现特殊字符、敏感字符会进行过滤或编码,这就很有可能改变用户原本的意思。用户输入的特殊数据可能被用到语境不同的地方,使用单一的过滤、编码,会出现歧义。
输出检查
除了富文本的输出,在变量输出到页面的时候,进行编码或转义。在后端拼接页面,有针对 html 、js 专门的编码函数,HtmlEncode、JavascriptEncode,还有专门的 XMLEncode、JSONEncode。还有一些模板引擎配置了默认的转义规则,然而这样也很难防治。
xss 攻击的本质还是 html 注入,用户的恶意数据被当作 html 代码的一部分执行了,从而混淆了原来的语义,产生了新的语义。
页面可能 xss 的地方
用变量来表示用户数据,填充到 html 中的话,看看页面中有哪些地方需要进行哪些防御。标签、标签属性、script 标签、css 文件、style 标签、style 标签的属性、用到 url 的地方。
在标签、标签属性中用到的话,防御的方法就是对变量使用 HtmlEncode。在 script 标签、事件中用到的话,就用 JavascriptEncode。在 css、style、style 属性中形成的 xss 非常多样,一般来说,尽可能禁止在 css 文件、style 标签、style 标签的属性中使用变量。如果用到了,得用 encodeForCSS 函数。在用到变量做 url 的属性上,可以用 URLEncode 函数转义。如果 url 能完全被变量控制,那么应该检查协议头,解决javscript:、data:
这些伪协议,这些伪协议也可以执行脚本。
富文本的处理
富文本简单来说就是用户自定义的 html 代码,富文本的效果都是 html 代码来实现的。因为富文本拥有完整的 html 代码,不会出现东拼西凑的情况。只需要 xss filter 找出富文本这段 html 代码中所有可能执行脚本的地方。
简单的富文本输入,可以在输入侧进行 xss filter,前端对输入进行规范检查,后端也基于白名单再此过滤,保证入库的数据是可信任的。
复杂的富文本输入,后端输入侧对用户数据进行转义,出库的时候反转义,保证入库的数据是可信任的。输出给前端的时候,再由前端对展示的内容进行 xss filter。这儿是基于黑名单,有 js-xss 库可用。
htmlparser 可以解析出富文本代码中的标签、标签属性、事件,危险的标签、事件都应该被禁止。标签检查时应该用白名单,css 的处理很麻烦。如果不能禁止用户自定义样式,那么就需要一个专门的 css 解析器对样式代码进行解析检查了。
dom xss 的防御
dom xss 是一种比较特别的 xss 漏洞。与前两种类型的 xss 不同的地方就在于前两种 xss 可以通过前面的输入检查、输出检查防御掉大部分。前两种 xss 都是从服务端输出给前端html页面,而 dom xss 是从 js 输出数据到 html 页面。
相当于 dom xss 会进行两次 xss,一次是从服务器输出数据到页面的代码中,第二次是调用 api 将数据写入到页面中。
所以正确防御 dom xss 的方法是做两次转义,从服务端输出数据到页面中的时候,执行相应的 xss filter,在调用 js api 将数据写入页面的时候再进行一次转义。第二次转义的时候需要区分输出的场景,如果输出为属性或 script 标签,应该进行 js 转义,如果输出到标签或属性,应该 html 转义。
而且除了服务器输出变量给脚本,还有一些情况也可能形成 dom xss,输入框、window.location、widow.name、document.referer、document.cookie、localStorage、xhr 返回的数据。
csrf
cookie 分两种,会话cookie 和持久cookie。会话cookie 的限制是domain和path组成的cookie作用域限制。不受同源策略限制,只要目标域在作用域内,就可以携带上会话cookie。