无法停止的劫持:跨站WebSocket劫持(CSWSH)深度解析
| 作者:Jack Hyland
Jack Hyland自大学毕业后一直从事信息安全领域工作,业余时间专注于深入学习新技术。目前主要从事开源项目创建与贡献,以及企业网络和基础设施的安全评估。
WebSocket协议概述
WebSocket协议于2011年通过RFC 6455标准化,实现了客户端与Web服务器之间通过单一持久连接进行全双工通信,解决了HTTP协议在双向通信方面的长期限制。例如,若要实时接收体育比赛更新,传统HTTP需要浏览器每秒向服务器发送请求查询比分变化,而WebSocket允许服务器在更新发生时立即推送给浏览器,消除了轮询需求,降低了带宽使用和用户延迟。
传统HTTP连接与WebSocket连接的区别如下图所示。注意握手完成后,WebSocket会话保持连接且双向通信,而传统HTTP事务在请求完成后连接终止。
漏洞利用原理
本文将演示如何利用WebSocket协议的握手步骤,使恶意网页能够使用受害者的cookie劫持WebSocket连接。在实际渗透测试中,每次遇到具有重要功能的WebSocket应用时,此漏洞都存在,影响范围从权限提升到远程代码执行。
关于WebSocket攻击的更多概述,请参考我们之前的博客文章《如何攻击WebSocket和Socket.io》。
检测与理解WebSocket
使用Burp Suite Academy的免费在线实验室进行演示。首先需要确定测试的Web应用是否使用WebSocket,最佳方法是通过Burp Suite捕获流量并点击每个页面。通常只有网站的一小部分使用WebSocket,因此需要仔细搜索。
在Burp Suite中打开Proxy -> WebSockets history标签查看捕获的WebSocket流量。如下图所示,/chat端点似乎通过WebSocket发送和接收数据。
连接建立过程
WebSocket连接最初通过HTTP建立。浏览到/chat端点返回HTML文档,其中引用JavaScript文件:
|
|
加载后,JavaScript打开新的WebSocket连接,并引用聊天表单的action属性获取URL:
|
|
HTML片段显示引用的聊天表单的action属性。红色高亮的URL被上面的WebSocket代码使用。注意wss://代表WebSocket安全,告诉WebSocket客户端库使用TLS连接WebSocket服务器。如果服务器通过未加密的WebSocket(如ws://)发送重要数据,这本身就是一个发现。
|
|
JavaScript执行后,浏览器发出以下HTTP请求,在客户端和服务器之间形成握手。服务器返回101 Switching Protocol响应,停止所有HTTP通信并切换到WSS。
注意Sec-WebSocket-Key请求头与TLS加密或密码学无关,这是一个常见误解。它提供挑战-响应机制,确保接收WebSocket连接请求的服务器是支持WebSocket的服务器,如RFC 6455第11.3.1节所述。
下图显示连接建立后服务器发送给客户端的JSON消息。
WebSocket的"特性"
WebSocket规范中一个文档记录较少的"特性"是它不受同源策略(SOP)约束,即使在HTTP握手期间也是如此。RFC 6455第10.2节指出:
不打算处理来自任何网页输入而仅针对特定站点的服务器,应验证|Origin|字段是否为它们期望的来源。如果指示的来源对服务器不可接受,则应使用包含HTTP 403 Forbidden状态码的回复响应WebSocket握手。
对于不熟悉SOP的人来说,这是Web浏览器实现的非常严格的默认安全功能,防止一个来源(域)的脚本向不同来源发出请求。这防止了blackhillsinfosec.com通过CSRF从您的chase.com银行账户中窃取资金。
虽然RFC摘录没有明确说明不强制执行SOP,但它将Origin头验证留给Web服务器负责(这是一回事)。为什么?我不知道。这意味着您浏览的互联网上的任何网站默认都可以访问浏览器有权访问的所有WebSocket连接。由于大多数开发人员不知道这一事实,我发现此漏洞经常存在。
下图简要说明了SOP(向SecurityZines.com团队致敬!):
跨源资源共享(CORS)通过允许网页请求和共享来自不同域的资源(例如数据或图像)来放宽SOP。服务器上的CORS头定义了允许哪些来源访问资源,在增强安全性的同时实现受控的跨源交互。
跨站WebSocket劫持
跨站WebSocket劫持(CSWSH)发生在服务器不验证HTTP握手中的origin头且应用使用将samesite标志设置为None的身份验证cookie时。通过诱使用户访问恶意站点,攻击者可以在受害者浏览器中建立和操纵WebSocket流量。
本质上,我们需要测试应用开发人员是否编写了代码来验证服务器端的Origin头。第一步是将WebSocket请求发送到repeater标签。可以通过右键单击或按CTRL + R完成。
在Burp Suite中转到Repeater标签(CTRL + SHIFT + R)并找到发送的WebSocket请求。接下来,单击形状像铅笔的编辑连接按钮。注意铅笔旁边的切换开关为蓝色,表示WebSocket连接处于活动状态。如果是灰色,则连接已断开。可以切换开关尝试重新建立WebSocket连接。
这将打开一个新窗口,显示Burp Suite拦截的所有活动和非活动WebSocket连接。单击活动连接,然后单击"Clone"。如果所有连接都已断开,请选择一个然后单击"Reconnect"。
接下来,欺骗origin头值为您的恶意域而不是PortSwigger域,然后单击"Connect"。
如果服务器不返回403未授权响应并允许您发送和接收数据,则服务器可能易受CSWSH攻击。下图显示了通过具有欺骗来源的WebSocket发送消息"BHIS CSWSH"的步骤。框#3显示通过套接字发送的消息历史记录,而框#4显示服务器的响应。
要证明您使用的连接是使用欺骗来源建立的,请注意与框#1中显示的数字匹配的WebSocket ID。接下来,单击框#2中显示的铅笔图标,然后单击"clone"。最后,验证框#3中显示的origin头值是否与您先前欺骗的值匹配。
注意WebSocket ID旁边和连接窗口顶部的域不会更改。我们只欺骗连接来源的origin,而不是请求发送的位置。
CSWSH工作的最后一个要求是发送的cookie必须将samesite标志设置为None。这允许浏览器在跨站点请求中发送网站的cookie;自2020年起,如果省略,Chrome和Firefox都默认此值为Lax。可以通过按CTRL + SHFT + I打开开发人员工具并单击存储标签内的cookie部分,或使用Firefox上的Cookie Quick Manager来检查值。如下所示,网站的会话cookie将samesite标志设置为None,这正是我们需要的。
您还可以从请求中删除cookie,查看是否需要身份验证来建立WebSocket。如果不需要,则根据上下文,您可能有一个发现需要报告。
利用漏洞
既然已确认服务器易受CSWSH攻击,我们可以开始制作漏洞利用程序。我们的目标是制作PortSwigger实验室的一对一恶意克隆,为攻击者提供对用户WebSocket连接的读写访问权限。
漏洞利用的成功取决于受害者使用的浏览器。据我所知,如果受害者使用Firefox,由于Total Cookie Protection,无法利用CSWSH。但是,所有其他浏览器,包括Chrome和Chromium衍生物,都是公平游戏。
典型的攻击场景如下:
- 攻击者发现易受攻击的WebSocket条件。
- 攻击者开发本地POC,模拟社会工程攻击。
- 攻击者在攻击者控制的站点上托管恶意POC。
- 攻击者诱使受害者访问攻击者控制的站点。
- 恶意站点劫持受害者的经过身份验证的WebSocket连接。
- 攻击者窃取受害者数据或发送未经授权的WebSocket命令。
要跟随操作,请将以下代码复制到文本编辑器,并将红色高亮的子域更改为匹配您的PortSwigger实验室实例。将代码保存到名为test.html的文件中,并在Web浏览器中本地打开 - 这将作为我们的恶意网页。这提供了PortSwigger实验室的克隆,攻击者具有完全读写访问权限。末尾蓝色高亮的代码加载chat.js资源,该资源在恶意网站的上下文中打开到PortSwigger实验室的WebSocket连接。
|
|
现在我们已经保存了恶意网站,可以将其发送给我们的受害者,受害者应在易受攻击的网站上具有预先建立的会话。为简单起见,我们将假装是受害者并在Chrome或基于Chromium的浏览器中打开网页。如下所示,即使用户在https://0a4600e703cc3f7b867e3026000f00da.web-security-academy.net的上下文之外,用户的聊天历史也会自动填充。攻击者的网页完全控制受害者的经过身份验证的WebSocket连接。
如果我们拦截流量,可以看到受害者的cookie自动附加到恶意网站向PortSwigger实验室服务器发出的请求中。浏览器允许这样做,因为会话cookie的samesite标志设置为None。此外,我们看到Origin头设置为null;这是因为网页未托管在具有域名的Web服务器上,而是我们硬盘上的文件。重要的是要注意,Origin头无法通过JavaScript修改,被浏览器视为"禁止的头名称"。
解决实验室
要解决PortSwigger实验室,请单击实验室网页顶部的"Go to exploit server"按钮,并将以下代码输入消息正文。实验室中的此过程模拟社会工程场景,其中具有会话的受害者在其浏览器中呈现我们的恶意JavaScript。确保修改红色高亮的子域以匹配您的PortSwigger实验室实例。接下来,单击"Store",然后单击"Deliver exploit to victim"。
一旦受害者的浏览器呈现以下JavaScript,他们的WebSocket连接到实验室实例将被劫持,发送READY命令,并将返回的聊天历史记录base64编码在GET参数值中发送到漏洞利用服务器。
|
|
完成所有操作后,单击"Access log"并按CTRL + F搜索"msg"。这将显示被窃取的base64编码消息,由受害者发送和接收。可以继续解码每个消息,但第三个消息将包含用户名和密码。
转到Burp Suite中的decoder标签并粘贴base64编码的字符串。接下来,将其解码为base64,然后解码为HTML以获取纯文本JSON。如下所示,用户名为carlos,密码是长的随机字符串。请注意,此密码对您的实验室无效,因此您必须重现每个步骤才能获得积分。
在实验室的主页上单击"My account"提交上一步中检索到的用户名和密码。这将解决实验室!
防御措施
要正确防御此攻击,WebSocket服务器应阻止HTTP握手期间具有严格允许列表之外origin值的任何请求。此外,用户会话cookie应设置为samesite等于Lax或Strict。
真实攻击场景
我在渗透测试中多次看到此漏洞,每次都可以将CSWSH与其他发现结合,导致报告的影响更高。以下是一些经验:
- 一个应用有两个门户,一个用于志愿者,第二个用于政府工作人员。两个应用的动态功能完全依赖WebSocket。恶意页面能够劫持政府工作人员的WebSocket并执行管理任务。
- 在管理门户中发现CSWSH漏洞以及WebSocket内的远程代码执行漏洞。这使得可以制作漏洞利用链,其中恶意站点将劫持受害者的WebSocket,然后通过WebSocket建立反向shell返回到攻击者的基础设施。