如何全面掌控session?且看websocket跨站劫持-亚博电竞手机版
websockets是一个能够给单tcp连接提供全双工信道的html5特性。它的持续性连接功能,使得构建b/s模式的实时应用成为可能。websockets常常用在那些带有聊天功能的web应用上。
下面的图片就非常贴切地阐释了一个apt攻击用到的websockets:
科普:
同源策略(same origin policy):同源是指,域名,协议,端口相同,即浏览器会检查同一浏览器的不同选项卡中,来源相同的脚本才能跨选项卡执行。
origin字段:浏览器在发送post请求的时候可能会加上一个origin字段,这个origin字段主要是用来标识出最初请求是从哪里发起的。如果浏览器不能确定源在哪里,那么在发送的请求里面origin字段的值就为空。
ironwasp:某开源web测试平台,用户可以自定义安全扫描,并且可以自己用python/ruby来定义插件系统。相关介绍见:http://www.freebuf.com/tools/32948.html
zap(zed attack proxy):是一款集成各种工具的渗透测试框架,可以发现在web应用程序中的漏洞,相关介绍见:http://www.freebuf.com/tools/5427.html
websocket的安全评估
最近,我们对一个拥有复杂菜单选项和功能的web应用做了安全评估。该应用中大部分操作都用到了web-sockets,这意味着其大多数行为都不会记录到http代理日志上。
首先,我们打开亚博vip888主页后,网站会加载一个带有js脚本和css文件的静态网页。此后,整个通信交互会转为websockets模式,浏览器和服务端之间会建立websocket连接,从而加载网站里所有可见的html资源。点击链接或者提交form表单时,浏览器会向服务端发送一些websocket消息。服务器处理了这些消息后,会通过websocket进行反馈,而后客户端浏览器会展示新的html内容。
这时当websocket消息在进行交互时,通信数量是非常巨大的。每隔一秒它们之间会有心跳探测包的交互。然而现有的工具达不到我的要求,我不得不给ironwasp添加了一个websocket消息分析装置和一个websocket客户端,这样它才能识别websocket从而尝试fuzz其漏洞。你可以在在这里了解下相关知识。
在测试这个应用时,我发现它存在websocket跨站劫持(cross-site websocket hijacking)漏洞(由christian schneider首创)。当然,我会在给大家介绍测试方法之前,解释下这个漏洞的影响。在测试相关websockets应用之前,我们需要先做下准备。
websocket跨站劫持漏洞实验准备
大家应该明白,同源策略(sop)不会通过浏览器在websockets上强制执行(同一浏览器下,受ssl保护的页面,不会让非ssl的websocket通过),我们测试的应用采用了http cookie作为session认证,websocket通过浏览器发送的消息不会有session id或是随机参数。
这样一来,如果某用户登录了带有漏洞的web应用,然后在同一浏览器还打开了http://attacker.com(攻击者的网站),http://attacker.com可以尝试通过该漏洞应用与应用的服务端建立一个websocket连接,然后通过浏览器发送一个带有效的认证session id的请求包。于是,这个由攻击者网站建立的websocket连接会和应用本身有相同的权限。
由于整个应用都是以websockets为基础运行的,劫持了websocket就相当于劫持了用户的session。所以这个漏洞在本质上和存储型跨站脚本漏洞是一样的。
如果你认为这就很糟了,那当你听到某些情况下websocket跨站脚本,甚至可以在用户系统上实现远程代码执行的时候,会不会更惊讶呢?事见ipython notebook的案例。
测试之前,你首先要做的就是确认该应用是否存在websockets。幸运的是,做这个非常简单,你只需要知道以下三点:
1.websocket的url连接一般是以ws://或者wss://开头的。
2.需要检测建立连接的origin头,该网页可能是通过origin字段建立的websocket链接。
3.浏览器和服务端之间发送的消息中,我们可以从中检查出普通websocket连接的特征。
下面这张图会给你展示:如何通过ironwasp日志得到origin字段的值和websocket的url值。
一旦你得到这些信息,你可以使用一些特殊方法,对跨站websocket劫持漏洞进行检测。我在这里例举三个简单的例子:
使用代理软件(如burpsuite):
这里必须提到的是,burpsuite可以捕获和记录websockets的消息。而zap和ironwasp是我所知道的,可以重放websocket请求的软件。
在burpsuite里,我们不能重放websockets消息,但我们还是可以在有限条件下,检测websocket握手包是否成功。为了进行测试,我们需要分析websocket的升级请求包:它们会通过http或者https发送,因此可以被重放。
以下截图为burpsuite重放器(repeat选项卡)的记录,其显示了websocket连接的有效请求和应答情况:
为了测试这个漏洞,我们需要发送另一个带有重制后的origin头的请求包。如果我们回应包里有“101 web socket protocol handshake”的标志,这就表示websocket已经建立成功了。
如果连接没有建立成功,那就意味着该应用不存在这个漏洞,因为它会拒绝外部的websocket连接。建立成功后,我们就可以开始下一步测试,看看该应用是否有websocket跨站劫持漏洞。这里需要说明一下:即使已经建立连接,也需要其如origin的正常连接一般,确认得到服务端对websocket消息的应答之后,才能证明该应用存在漏洞。这是因为开发者可能会同时启用origin检测与连接权限认证。因此我们的实验中也许会出下面这种情况:建立的连接可以一直保持,但拥有外部来源的origins不会通过认证。
zap可以重放websocket消息,但据我了解,它并不能更改origin头。下面介绍的方法可以给你科普下,如何通过cswsh(websocket跨站劫持)来获得更多的东西。
使用websocket跨站劫持的在线测试工具
打开需要测试的web应用登入其中,然后在同一浏览器中开一个新选项卡,访问http://ironwasp.org/cswsh.html(模拟的黑客网站),输入该websocket的url地址,然后点击网页上的connect按钮。一旦建立连接,你就可以通过这个页面向websocket的服务器发送消息。我们需要通过重放有效session发送过的消息,然后查看服务器的回应包。
如果服务端的回应与前面有效session发送的正常包相同,那就说明该应用可能存在websocket跨站劫持漏洞。
使用ironwasp
ironwasp可以做到更多,即使是最基础的检测也能提供自动化脚本检查。
使用ironwasp的websocket客户端
以上测试origin的方法的使用的服务端是http://ironwasp.org,如果你想要更加灵活地设置origin值,你可以使用ironwasp的客户端功能。它允许你自定义origin值来测试websocket连接。
在以下环境下可能会用到客户端功能:
1.应用允许来自开放的origin的websocket连接
2.应用允许来自localhost和内网ip的origin字段值
这种做法是为了方便开发者和应用的内部测试。通过使用ironwasp的客户端,你可以尝试内网ip或者localhost作为origin是否能够生效。如果可以的话,那没准儿你可以耍一点小手段,在真实环境下利用这个漏洞。比如,如果某个应用允许http:/127.0.0.1:8080作为origin字段,那我们就可以这样做:若受害者正好有个在本地8080端口运行的web应用,而且其存在跨站脚本漏洞。如果满足这些条件,黑客可以先在该web应用上进行跨站攻击,然后再向目标应用服务端建立websocket连接:
使用ironwasp的websocket api进行自动化检测
如果你需要利用localhost或者内网ip进行测试origin头,使用客户端脚本进行自动化检测会让你的行动更加轻松。ironwasp允许你使用python或者ruby进行实现自定义脚本编写。
下面这个脚本可以单独检测origin头里填充的内网ip地址,测试服务端对此是否认可:
import clr clr.addreference("websocketclient.exe") from websocketclient import * def check_conn(origin): print "testing origin - " origin ws = syncwebsockclient() ws.connect("ws://tatgetapp.com/ws", origin, "sessionid=ksdi2923ewe9djsds01212") ws.send("first message to send") msg = ws.read() ws.close() if msg == "message that is part of valid session": print "connection successful!!" return true else: return false def check_nw(): for nws in ["192.168.0.0/16", "172.16.0.0/12", "10.0.0.0/8"]: for ip in tools.nwtoip(nws): if check_conn("http://" ip): break check_nw()