作者:ZSX
随着前端技术的高速发展,越来越多的软件正在使用浏览器相关技术作为其组成的一部分。和完全使用传统客户端技术开发的软件相比,部分或完全使用网页前端技术开发的软件有着开发成本低、部署成本低的优势。但自然而然地,我们也可以用常用的针对 Web 进行攻击的技术来攻击这一些客户端。由于客户端软件拥有更大的权限,在 Web 上使用场景严重受限的攻击技术,往往能造成更大的危害。
这一种攻击手法真正做到了“一视同仁”,无论是桌面端还是移动端,不管是什么操作系统,只要是使用了浏览器相关技术,且代码存在缺陷的软件,都有潜在被攻击的可能。
本文在桌面端将围绕着 MSHTML、Electron、nwjs、CEF、QtWebKit 等展开讨论,移动端将围绕着直接引用android.webkit.WebView
、使用 Cordova 、 5+Runtime(DCloud)等库展开讨论。
常用的客户端攻击技术,包括缓冲区溢出漏洞等,不在本文的讨论范围中。于是我们还剩下在 Web 里负责最终用户的 XSS / CSRF / MITM 等、负责服务器的 SQL Injection / SSRF / 命令注入 / 文件上传等,同时还能把一些客户端攻击技术与 Web 结合,处理成新的攻击手段。
负责服务器的 Web 攻击技术对客户端大部分都没有什么用途,原因包括:
SQL Injection还是有一定程度上的利用价值的,但客户端的 SQL 注入仍然比较鸡肋。大部分客户端都使用 SQLite 作为其本地关系型数据库,一般只能注入获得用户信息;而使用 Electron 编写的客户端在本地开个 PouchDB 之类的轻量级非关系型数据库也就是一两行的事情。即使注入成功,还可能缺少把信息传出的机制。需要配合其他漏洞使用。
CSRF和客户端更没有多大关系,它走的仍然是服务器的接口。
在 Windows 下,有一种常见的攻击手段叫 DLL Hijacking。绝大部分程序都有代码重用的需求,不能把代码静态编译到 exe 内。为了解决这种问题,就出现了动态链接库(DLL)技术。程序使用 LoadLibrary
将DLL加载进内存,然后执行 DLL 内的代码。自然而然地,如果我们把程序要加载的 DLL 替换为我们自己的 DLL,那我们就可以让程序执行我们想要的任意代码。需要注意的是,这并不是 Windows 的专属问题,Linux 和 macOS 下也可以劫持 .so 文件。
而在 WebApp 中,我们可以注意到,几乎所有的 .html 文件都是外部资源。对这些外部资源进行修改,就达到了和 DLL Hijacking 相同的效果。
Electron 中,资源通常以 asar 格式打包或者不打包,存放在 resources 目录之下。asar 格式类似 tar,只做归档,不提供签名和校验功能。也就是说,我们只要把 resources 目录进行替换,就可以让 Electron App 执行任意代码。
我们举一个非常恶趣味的例子,让 WordPress.com 和 Ghost 的 Native App 的代码互换。
可以注意到,Ghost.exe
的数字签名还是一切正常的状态。
——那边用 CEF 的别笑,你们也一样。只要你们的HTML文件存在本地,就拥有被劫持的可能。
这种攻击对目标用户有什么用呢?当然,它并不直接攻击目标用户。他的用途包括:
lpk.dll
系列病毒便是基于这个原理。如果程序不带数字签名,那也不必花心思来防范这种攻击了,投入产出不成正比。
使用 CEF 和 QtWebKit 的可以在加载每一个HTML之前通过已经签名、且含有校验信息的DLL对其进行校验。
使用 nwjs 的话,因为它的HTML代码是通过 zip 压缩后直接通过copy /b
附加到程序后部的,所以理论上直接打数字签名即可解决。而 Electron 我并没有找到特别好的方法,可以关注:https://github.com/electron/electron/pull/9648
然后,就是 RCE(Remote Code Execution,任意代码执行) 了。我们可以进行 MITM 攻击,或者是利用 XSS 漏洞,还可以利用 Flash 的安全漏洞。针对 Web App 这一形式,如果其使用了<iframe>
或<webview>
我们甚至还可以进行所谓的“任意地址浏览”。
我们姑且不考虑利用浏览器内核本身的漏洞进行攻击的手段。这类漏洞本身数量稀少,而且相当多还需要浏览器其它组件的支持,仅有一个内核也没多大用途。况且这部分通常可以通过浏览器本身的安全通告发现,及时更新即可解决。在这之外,你可能没想到的是,MITM 和 XSS 会是最后的主角。这两种攻击方式一种难度大 + 面对客户端没什么用,一种拿个 Cookie 之后就没什么用了。但我们的战场可是 Web 客户端。如果我们的网页是通过明文 HTTP 协议传输的,便可以通过 MITM 让客户端处理任意代码。如果无法利用 MITM ,那么可以试图寻找 XSS 漏洞来写入任意代码。而在浏览器之外的客户端程序执行任意代码,可能是致命的。
现在有不少客户端的 Web 页面是通过 HTTP 协议从互联网上拉取的,不存储在本地。比如说 Steam。还有一部分客户端采取部分本地代码、部分加载远程网页的形式。反正大家都是浏览器嘛,看看迅雷9,那就真的把自己当成浏览器来处理了。——但无论是 Electron 还是 Cordova,实际上都并不设计为浏览器,因此直接使用它们便会产生相应的安全隐患。使用 CEF、QtWebKit 等,也在一定情况下存在安全问题。
通过 XSS 等手段想办法得到执行任意代码的权限后,我们看看怎么逃离这个客户端吧。
首先是 Electron 和 Nwjs。这两个框架的 JavaScript 代码几乎拥有和 C++ 代码同等的权限,于是我们直接一行代码弹个计算器。
require('child_process').execSync('c:\\windows\\system32\\calc.exe')
除去 XSS 之外,如果一个 Electron 同时负担了浏览器访问网页的职能,在没做安全配置的情况下照样可以一行代码弹个计算器。即使正确配置了nodeIntegration
恐怕也无济于事,今年的 BlackHat 大会发布了这么一篇文章,即是讲述如何绕过该功能实现 RCE 的(CVE-2017-12581
):https://www.blackhat.com/docs/us-17/thursday/us-17-Carettoni-Electronegativity-A-Study-Of-Electron-Security.pdf
其次是国内 DCloud 出的 5+Runtime(https://dcloud.io/runtime.html ),自带Native.js
(https://ask.dcloud.net.cn/docs/#//ask.dcloud.net.cn/article/88 ),对其进行攻击效果也拔群。
而未正确配置的 CEF 和 QtWebKit,往往也可以造成任意代码执行。一个典型的例子,目前在网络上流传的在 CEF 里启用 Flash PPAPI 插件的代码,几乎无一例外关闭了web_security
,同时打开了no_sandbox
。很多开发者为了自己读写本地资源方便,也都关闭了它,如图。
在低版本的 CEF 中,如果关闭了 web_security
,只需要一行代码即可打开计算器(在默认浏览器被设置成 IE 的情况下)
window.open('file://c:/windows/system32/calc.exe')
而在高版本的 CEF 中,无法打开计算器了。出于不知道什么原因,也无法读写文件(https://bugs.chromium.org/p/chromium/issues/detail?id=40787 ,可以确认的是 QtWebKit 也不行),就不能使用通用的方案处理了。
——但别忘了 Flash 的存在。我们可以想办法看看这个程序有没有加载 Flash。
针对使用多进程的 CEF,直接看启动参数即可。
单进程的程序,就需要打开一个带有 Flash 的页面后,观察其 DLL 加载情况。或者直接开 IDA 逆向一波。
拿到 Flash 版本后,查查该版本 Flash 有啥 Bug,一波带走即可。
至于MSHTML?感谢 COM 组件。
(new ActiveXObject('WScript.Shell')).Run('calc.exe');
通用的攻击手段实在不多,如果多的话,像 QQ 和微信早就被日穿了。所以,我们需要针对每个应用寻找有效的攻击代码。
以 Cordova 为例。cordova-plugin-inappbrowser
基本上是大家都必装的插件。于是,我们调用系统浏览器,利用 URI Scheme 弹个B站客户端先。
window.open('bilibili://bangumi/season/6463', '_system')
想读通讯录?看看有没有navigator.contacts
;读写文件?看看window.requestFileSystem
。
从上面的例子可以发现,我们的重点就是,
那具体应该怎么找呢?
首先先试试能不能打开浏览器开发者工具。针对 Android App,可以通过 Chrome 浏览器进行远程调试。大部分使用了 WebView 的 App 发布后都没有关闭远程调试这一功能,这为我们的攻击带来了极大的方便。
随手打开一个B站客户端看看
如果能打开开发者工具,那要找特殊 API 就非常容易了;不能打开,也不是没有办法。
document.write(Object.keys(window).join('\n'))
通过这种方法可以把 window 下的键值全部输出到屏幕上,下一步一步一步慢慢找。一般来说,浏览器可以调用的特殊 API 在window
、navigator
、external
三个位置上都可以找到。找到可以利用的函数后,可以接着全文搜索函数名查找引用,模仿着直接调用即可。可以利用的功能包括“打开文件”(在 Windows 下一般使用ShellExecute
实现,因.exe
、.scr
的文件关联为其自身,故可打开任意程序)、“打开程序”、“打开子窗口”等。
如果找不到可以利用的函数,或者在直接调用时做了一定程度上的过滤,则应当去找任意一个函数的源码,研究其如何与 Native 交互。
可以用
document.write(someFunction.toString())
把函数源码输出到屏幕上(有开发者工具直接进 Source 看就好了)。
接着,我们需要配合逆向服用。
如果其显示的是[native code]
,说明这是一个直接暴露给 JavaScript 的 API。对于 Android App 的 WebView,可以直接逆向为 .jar 后搜索addJavaScriptInterface
函数,看到浏览器到底给 JavaScript 暴露了哪些东西。对于 CEF 等框架,可以从 Strings 搜索关键词直接一步一步 X 上去找到最终执行的函数,或者直接附加进程调试。
如果其显示的是 JavaScript 代码,则可一步一步跟踪上去直到顶层,看他的通信方式。目前比较流行的通信方式是自定义 URL Scheme 方式。其原理如下:
location.href
尝试打开地址my-custom-scheme://my-custom-data
shouldOverrideUrlLoading
函数,由其来决定该地址应当如何处理;CEF 如果之前使用CefRegisterSchemeHandlerFactory
注册过自定义 Scheme,则会交由其处理。evaluateJavaScript
或者直接让框架访问javascript:xxxxxxx
调用某个页面内的回调函数,完成通信。所以对 Android 进行攻击的话,寻找那几个函数即可查到所有通讯方式。另外 Android 通常还会使用一些库包装 Scheme 处理,也不妨找找它们的特征,如:https://github.com/lzyzsd/jsbridge
React Native如果使用了<webview>
也会受到相应的影响。但如果没有的话一般不用考虑,除非代码中存在eval
等可能任意执行 JavaScript 的地方。对 NativeScript 这种可任意调用 Java 库的库,还是存在一定危险性的。
举一个比较有趣的知识+例子。在 IE8 之前,前端处理 JSON 通常使用以下代码:
var json = eval('(' + data + ')')
当然现在的前端都已经用上了JSON.parse
了,不过说不定有人坚持使用呢 :)
1.使用 HTTPS,在有条件的情况下校验证书,避免中间人攻击。
2.写入和输出数据时做好过滤,不信任所有的用户提交的数据。项目最初选型时就应该使用 React、Vuejs 等前端框架,避免自行拼接 HTML 出现问题。
3.在正式发布的软件上,关闭 WebView 的调试模式,Android 上:
WebView.setWebContentsDebuggingEnabled(false);
4.对 Native 和 JavaScript 交互的代码进行白名单处理,当检测到域名非白名单域名时,拒绝 JavaScript 调用接口的请求。
5.在条件允许的情况下开启浏览器沙箱,同时杜绝disable-web-security
行为,使用单个功能的命令行参数解决。
6.必须配置好CSP,预防真的出现 XSS 后的进一步利用。