1.背景

Steam的聊天客户端是一个攻击起来特别有趣的系统,因为它是使用一组具有强大安全特性的现代技术构建的。

它主要是基于React框架,而关于React,它具有任何现代Javascript应用程序框架的一些最强大的安全特性,并且避免使用不安全dangerously的函数系列。

虽然部署了内容安全策略,但有unsafe-inline。这是一个小小的不便,但前进了一个有趣的步骤。

与大多数使用Web技术的桌面应用程序不同,聊天客户端运行在Chrome Embedded Framework的自定义高度锁定版本中。在大多数类似电子的系统中,通过window对象向Javascript VM授予特权访问权限。聊天客户端采用有趣且可能更安全的运行方式,实质上是常规网页的权限,只允许通过PostMessage执行特权操作,以及与父进程通信的环回WebSocket。
WebSocket带有一个非常难以通过网络剖析的自定义二进制协议,并通过一些常见错误,我还没有找到Chrome Dev Tools的根本原因如果断点在页面加载时崩溃(我认为它是某种竞争,当WebWorkers处于活动状态并且Javascript资源很庞大时发生的竞争。)

DOM重型应用程序的趋势对我来说很有意思,因为安全行业中的许多人仍然严重依赖于在这些情况下无法准确反映应用程序状态的HTTP代理。

2.技术

2A.React安全问题

由于Steam聊天客户端是基于React构建的,因此XSS的可行方式要少得多。我有几种寻找方式:
· React没有特殊地编码任何标签的属性。具有DOM操作属性的属性是危险的。

通常可以看到<a href>属性由用户输入生成,其中javascript: input uri在单击时会产生XSS。对javascript: URI的手动对策仍然很差,并且经常使用不打算在防御中使用的URL解析器。

style通过字符串连接生成包含用户输入的标签并不罕见,其中图像(例如作为背景URL)可以通过Referer标头从URL注入IP地址信息和令牌。即使在最佳的上下文感知XSS库中,CSS清理也不是真正的东西。对于使用selector[value=string]或定义字体的基于CSS的攻击,使用或定义每个字符的HTTP请求以有条件地加载资源和泄露数据,在信息安全圈之外几乎完全是未知的。

React不会尝试提供其他不安全的Javacript功能的强化版本,也不会禁用它们。通常看到React应用程序用于document.location = xxx更改浏览器的位置,该浏览器也容易受到Javascript URI引发的攻击。

同样,对HTTP API的请求也不会从React获得增强的安全性。使用拼接到未正确编码的URL的用户输入来请求数据仍然很常见。React开发人员喜欢使用花哨的REST语法来生成请求路径"/user/" + encodeURIComponent(username) + "profile",即使据我所知,在vanilla Javascript中编码URL路径也没有安全的方法。即使../编码到哪里..%2F,几乎所有的Web服务器都忽略了%2F和之间的词汇差异/。

· 某些协议,如OEMBED,通过返回HTML设计是不安全的。要使用这些API,必须使用包括此在内的React应用程序dangerouslySetInnerHTML。经常可以看到React被引用来获取生成的元素和直接调用的innerHTML的句柄,这可以避免测试人员为“不安全”而抱怨。

返回HTML的协议,甚至那些仍然不会定期返回的协议Content-Type: text/html,这意味着如果受害者导航到API结果,例如通过提交HTML表单,如果存在XSS的话,即使客户端将安全地处理输出,浏览器也不会。

2B.高级DevTools功能

我想介绍一些我的infosec朋友使用不多DevTools功能,这对于我发现bug很有帮助。

2b I.控制台抽屉


当你打开devtools按escape键时,一个抽屉会从底部拉上来。从这里,您可以在浏览源代码或网络日志的同时访问一些非常强大的特性。


这最棒的特点就是拉出的控制台。当一个断点被触发并暂停执行时,你可以在这里执行你想要的任何代码,它将在调试器所在的当前行的上下文中执行。这对于修改和检查在多个抽象级别上运行的代码是绝对必不可少的。

2b II.代码搜索,相当不错的打印


DevTools包含一个非常强大的搜索功能,可搜索加载到当前窗口中的每个资源。它可以通过底部抽屉访问(单击三个点)。如果你在DOM中找到一个元素并想知道它是如何生成的,你可以在这里搜索它并跳转到它所提到的位置。

在那里,您可能想要点击左下角的漂亮打印按钮{}和ctrl-f来查找您可能感兴趣的文件中的任何内容。

2b III.打破事件


你有一些预期会发生的事件是很常见的,比如XHR或postMessage,但你不知道处理程序的定义在哪里。别担心!如果滚动到Sources最右侧面板的底部,则可以为XHR / fetch和事件侦听器设置断点。

2b IV.调用堆栈


在“Sources”面板中,一旦一个断点触发,您将得到右边断点之前的完整调用堆栈。当然,你在大多数语言中都能得到这个。

假设您知道一个用户操作最终调用XHR,但是您希望找到XHR请求的高级构造。如果您设置XHR / fetch断点,那么您最终将深入到某种通常提供很少上下文的库中。

实际上,现在您可以通过单击每个调用、查看作用域中的代码、变量和它所在的文件来后退一步遍历调用堆栈,直到找到一些看起来专门为这个应用程序编写的内容为止。我发现在现代的小型应用程序中,这对于逃避库调用是不可缺少的。

2b V.注入代码


虽然chrome devtools确实能够在网页中动态编辑Javascript代码,但这不适用于缩小代码,因为不能在打印精美的文件上这样做。不过,你可以使用一个特殊的技巧。

如果右键单击行号,可以使用“插入条件断点”。条件断点是完全特色的javascript,在语句出现时会中断true。console.log始终返回undefined,因此如果您想在程序运行时检查多个值,您可以注入console.log调用以将其值打印到控制台,这是类似但效果较差的“监视”功能无法实现的。

在有心跳的系统中,断点通常会导致断开连接。使用条件断点可以允许您在不停止执行和使用控制台的情况下添加代码。

2b VI.网络搜索


当然你从某个地方获得了一些数据,但不知道在哪里?您可以单击网络面板中的放大镜图标来搜索完整的请求,响应及其标题。

2b VII.网络过滤器表达式

在发送大量XHR请求的应用程序中,特别是那些定期轮询的应用程序,可能很快就无法导航网络面板中的所有请求。您可以使用筛选器表达式将请求缩小到您认为重要的请求。

2b VIII.曲向复制

使用devtools可以做很多事情,但是你通常不能绕过像同源策略这样的web安全原语,而且重制和自定义请求并不容易。您可以单击network面板中的任何请求,然后转到“曲向复制”以获得该请求的精确复制,您可以对其进行迭代以打乱请求表单。

3.方法

在典型的上一代聊天应用程序中,安全问题最有可能出现在从输入文本生成HTML的位置。毕竟,解析语言不仅是一个超级难题,而且特别难以向用户提供HTML功能的强大功能,而不会无意中允许他们通过操纵这些功能来控制浏览器。

- 3A.侦察

我首先意识到的是,部署在Steam桌面应用程序中的应用程序与 https://steamcommunity.com/chat 上的在线应用程序是一样的,这使得将DevTools注入测试流变得容易得多。

在那之后,我花了一点时间与DevTools的空闲的网络面板聊天,发现我们没有受到XHR轮询的攻击。这可能意味着我们使用的是WebSocket。打开网络面板刷新页面(只有打开WebSocket连接时才能看到它们),我浏览了一下WS面板,注意到客户端在完全不可理解的二进制框架上进行通信。

注意到聊天系统支持难以置信的安全实施的嵌入式内容,我开始对“OEMBED”和其他非常容易获得XSS的通用嵌入系统进行代码搜索。

此时,我发现该应用程序是一个React应用程序,并切换到,至少部分使用React Chrome扩展来检查DOM。这个扩展非常容易跳到生成元素的代码,而且因为React组件将它们所依赖的所有信息都表示为道具(可以在扩展中查看),所以就更容易掌握应用程序结构。

我搜索了一下dangerously,innerHTML等等,并设置所有的断点。我试图通过跟踪调用堆栈来触发这些函数。dangerously是真的被称为可以由聊天服务器发送的某些“oembed元素”。

我在加载OEMBED内容(如YouTube)时触发的xhr和其他事件上设置断点。当它们被触发时,我退回调用堆栈,以访问对用户输入进行清理并通过二进制WebSocket将其发送到聊天服务器的函数。

- 3B.实现XSS

事实证明这有令人惊讶的结果。我最终会为每个发送的消息触发两次断点:一次是当用户发出一个发送请求,客户端将一个假定的服务器响应写入DOM时 - 第二次当它被服务器返回的实际响应覆盖时。在某些情况下,这些渲染可能会有很大差异,特别是在使用OEMBED时。

如果我在聊天中发送Vimeo链接,则客户端在发送消息时最初将其呈现为HTML链接。然后,当服务器更新聊天室时,它将用BBCode(是的,bbcode!)[OEMBED]标签替换该链接,该标签基本上只是原始HTML。

我立即跳过了这一点,并在调用后使用断点来清理BBCode的用户输入,我尝试发送包含恶意HTML的OEMBED标记,希望它们会被服务器反应回来。但是,服务器会在响应之前完全剥离这些标记。

在对代码进行一些扫描之后,我找到了将BBCode标签与其相应的React组件匹配的表,并将我看到的每个BBCode标签与混合的结果一起发送。大多数标签被服务器剥离,许多标签显然需要属性参数[imgur alt=]等,但我不知道如何使用。

我转而尝试记录所有富文本命令的BBCode表示形式,比如滚动随机数或显示图像,但收效甚微。我发现了一些我可以发送的标签,它们可能会被删除,包括[url=xxx]、[code]和[image]。图像被严重锁定,我发现它几乎不可能以一种恶意的方式使用它。[code]只是安全生成[code]标记,但[url=xxx]是合法地链接到任何地方,包括javascript: URI链接。

我立即打开这份报告,说我已经实现了XSS,这通常会导致这类客户端出现RCE。结果比这复杂多了。

当然,我已经实现了XSS,我可以真正地扰乱web浏览器用户,但是javascript: URI实际上被被巧妙地剥离了,或者没有被Steam客户端浏览器使用。我开始尝试其他方法,因为我知道我可以创建任何URL。

- 3C.Steam URI

基于现有技术,我从使用steam:// uri开始,这是steam客户端独有的,可以做很多不好的事情。许多年前,浏览器内的steam:// uri通过安装、运行游戏并将精心制作的日志导入startup文件夹来实现远程代码执行。

我取得了一点小小的成功。在聊天客户机中,steam:// uri 是在有特权的浏览器中执行的,而它们通常无法访问。在出现 steam:// 安全问题之后,如果我 steam://open/440 通过网络浏览器打开了你的网页,那么阀门会增加一个提示 - 如果我在网络浏览器中发送你,它会让steam确认你真的想打开那个游戏,但这些链接在Steam聊天客户端不会导致此类确认。

我尝试着制作了各种各样在人们的计算机上打开游戏的链接,重置他们的配置,关闭steam或打开steam系统,就像steam控制台一样,通过运行 steam://-console 或其他东西。我不记得实际的URL是什么了。

- 3D.滥用OEMBED

经过多次尝试和头脑风暴后,我再次改变了方向,并试图专门针对OEMBED。安全的OEMBED系统很难实现,因此人们通常只使用像Embedly这样的服务。Embedly的安全性来自于对iframe sandboxing和白名单的很好的使用。然而,由于我们不是在一个普通的浏览器中,嵌入会给您带来不同的特权。如果你嵌入一个电子桌面应用程序浏览器,而iframe不在一个特殊的<WebView>中,你仍然可以通过iframe的窗口对象访问所有危险的电子api,即使iframe在浏览器中是安全的。

为了滥用这一点,我需要(1)通过嵌入得到白名单(不可能),(2)在白名单的嵌入中找到javascript注入。幸运的是,我想到了,codepen.io是被嵌入的白名单,而代码页,也就是Javascript注入服务。

在过去,这样的环境中的工作往往是我为FireBug注入脚本,但是这通常是一件痛苦的事,因为有些东西不能很好地工作。@mandatory建议我使用远程chrome控制台。他向我推荐了一些我完全忘记名字的软件,它可以让你通过注入一些脚本来使用chrome开发工具远程控制台。

- 3E.远程控制台

一旦我用我的远程控制台加载Steam我的codepen.io应用程序,我开始寻找Steam Web Helper环境的特性。我从转储Object.keys(window)开始,并在一个普通的Chrome浏览器上运行差异。这就产生了一些东西,其中大部分是无用的。我可以在页面上加载一些样式以及浏览器中通常不可能加载的其他样式时挂起事件,但这并不是真正的安全问题。

由于聊天客户端与父窗口通信以执行特权操作,比如提取好友列表,所以我尝试使用postMessage命令来执行window.top.postMessage(),该命令用于诱使客户端做一些坏事。OEMBED系统生成的沙盒环境似乎阻止了对window.top的访问。

此时,我已经开始使用远程控制台,通过发出open(“steam://xxx”)来快速测试steam:// uri的效果。我没有发现更多,但是它促使我开始对Steam Web Helper二进制文件进行更多的剖析。我首先在Steam文件夹中为我知道存在的Steam协议uri运行一个二进制grep,然后使用vim搜索包含这些内容的字符串表。这让我想到了一些有趣的、没有文档的uri,它们是Steam Web Helper特有的。

- 3F.通过一个开放的窗口

两个特别有趣的URI包括我希望或许具有一定级别权限的Chrome Dev Tools URI以及在应用程序二进制文件中steam://openexternalforpid出现的URI steam://openexternalforpid/%s/%s。 当我打开它时,它会打开一个像素宽的黑色窗口,想打开多少次就打开多少次。从openexternalforpid字符串可以明显看出,它需要两个参数,但是我完全不知道如何计算它们。

经过多次猜测,我将这些openexternalforpid东西传递给了我的朋友@XMPPWocky,一个非常棒的二进制逆向工程师,我曾和他一起找到过非常严重的steam bug。但是他从在Symantec里拯救世界所挤出的一小点时间根本不够用来搞这个。

我想到了执行这个Javascript的环境。打开窗口通常是专门为嵌入式浏览器实现的,并且打开的窗口对开启窗口具有不同的权限是很常见的。我试着抓取 open('steam-chrome-dev-tools://something').contentWindow或URL,看看能不能抓取一个privilaged devtools窗口,得到有趣的结果。

新窗口确实有一些通常无法访问的特殊功能。我可以读取用户光标所在的位置,最大化窗口,最小化它和一堆其他垃圾,这些都没有让我在经过数小时测试后更接近远程代码执行。

- 3G.协议之外

在我对Steam:// 协议的测试中,我注意到一些有趣的事情:每当我输入错误时,Windows就会打开一个对话框,说它不知道如何打开文件类型sream:之类的。这对我来说很有趣。

像Steam这样的自定义协议是由大量不同的软件实现的。它们通常非常糟糕,其安全性依赖于浏览器提示打开此应用程序。当每个人都使用Skype的时候,我在给人们发送skype://call 的url中发现了一些乐趣,它打开了对回调Skype号码的呼叫,这个号码正好与你所说的相呼应。

但就像,现在人们实际上没有Skype,所以我想知道在我的系统上可能实现的其他定制协议。这对于windows内部来说是一个绝对迷人的兔子洞。我花了几个小时浏览论坛和参考文档,描述如何添加协议以及哪些协议是由windows注册的。事实证明,有很多。Windows甚至有自定义协议,用于打开地图定位用户要查看的位置。

然后我更加深入了。自定义协议实际上是在HKEY_CURRENT_CLASSES之类的Windows注册表中实现的。如何讲述这个系统的结构真是令人着迷。不仅在这个目录中有每个协议(例如,在Windows上打开http:// 链接时会打开什么),而且这个文件夹实际上包含Windows中每个文件类型的文件类型关联,就像打开.txt文件时记事本是如何打开的一样。

http:协议的文件夹和其他文件夹就在.png的文件夹旁边,它们遵循相同的语法,描述如何将参数转换为程序调用。完全不敢相信,我按下win+R,输入.txt:你好……它打开了记事本。自定义协议和文件类型关联是一回事。

在那之后,我搜索了整个类,每次都在寻找一些我可能会发现更聪明的方法有用的东西。我直奔.bat文件类型,该文件可以运行任意Windows命令,然后我在Run中试用了它。结果它使得Windows资源管理器崩溃了。

我仿佛更机智了,开始搜索使用第0个参数并通过程序打开它的文件类型,因为通过阅读协议是如何打开的,很明显,如果第0个参数被称为$0,那么http:// 这样的协议本质上就是open_webbrowser.exe $0,其中$0是URL。

我遇到了一些非常奇怪的景象,我想和大家一起分享。有一个计算器协议。我不知道为什么,但确实有。如果你想要显示并弹出一个计算器,你可以建立一个calculator:类似的链接,就像click me!,当点击它将打开受害者计算机上的计算器。

我花了好几个小时搜罗这个该死的数据库,发现了一些潜在的可利用协议。一个是jarfile:,它执行你给它的jarfile。它实际上是.jar文件类型的绑定。另一个是JSEFile:它是一个windows xp时代的系统,可以像程序一样用VB脚本运行HTML页面。比如史前的电子什么的。

它......没有用。问题是$0包含完整的URI。如果我链接到jarfile:c:/windows/whatever.exe, 实际调用就好了c:/Program Files/Java/Java.exe jarfile:c:/windows/whatever.exe, 好吧......它试图找到一个名为'jarfile:c:'的目录,这显然不存在。

我休息了一下并提交了另一个标签,说明我通过这种方式找到了另一个有趣的方法,我可以在他们的计算机上打开任何程序,但或许不是我想要的参数。我很确定这是RCE的一种方式。至少我可以提交一个推出的例子calculator.exe,这是所有酷孩子都做的,对吧?

我几乎立刻恍然大悟。目录遍历。如果它正在寻找一个不存在的名为'jarfile:c:' 的目录,我们可以注入一个 ../ 说“go one directory back”并否定不存在的目录。这实际上是一个相当大的成功。我可以发送就像 jarfile:................\Users\Username\Downloads\drive-by-download.jar 然后合法运行受害者的计算机上的一个jar文件。这既令人兴奋又令人失望,因为这意味着我无法远程加载jar文件。我需要让用户不加载它。

- 3H. openexternalforpid

这一次,我打开了Steam控制台(打开steam://console)。 我没有读它,但它打印了很多有用的信息,比如,特别是steam:/ /它正在运行的调用。我不小心碰到了控制台,凭借绝对的运气,我看到了一些东西。当我发送时jarfile: xxx,Steam Web Helper在内部发送steam://openexternalforpid/10400/jarfile:xxx。 这太大了。

我立即从所有这些毫无意义的自定义协议切换到使用幻数10400和cmd调用openexternalforpid。你猜怎么着?远程,编码,执行,任务完成。

由于此链接表单不是javascript:// 链接,因此它仍然受到Steam Chat的青睐。要么我可以发送我的codepen.io嵌入或我可以发送我的[link]标签来获取远程代码:)

4.结论

这真是太有趣了,我学到了很多。我总是在寻找bug,在这些bug中,一组低严重程度的简单错误会级联成一个严重程度很高的大bug,这就是一个完美的例子。

源链接

Hacking more

...