前言
前几日出了百度云Windows客户端YunDetectService.exe服务的10000端口的命令注入致RCE漏洞后,今日得闲,看了下MAC客户端是否存在同样的服务,发现也存在同样的服务,就粗略的看了下,献丑了。
正文
在没有打开百度网盘之前,通过lsof -i:10000 命令没有发现任何服务,打开百度网盘后,发现了存在监听10000端口的netdisk_service服务,因此粗略的审计一番。
然后通过系统自带的获得监视器找到了netdisk_service服务

使用ghidra打开/Applications/BaiduNetdisk.app/Contents/Frameworks/netdisk_service文件发现了如下几个API相关的初始化


METHOD_GET_VERSION为"GetVersion"METHOD_GET_PCCODE为"GetPCCode"METHOD_DOWNLOAD_SHARE为"DownloadShareItems"METHOD_DOWNLOAD_SELFOWN为"DownloadSelfOwnItems"METHOD_OPEN_SAFEBOX为"OpenSafebox"METHOD_WEB_UPLOAD为"WebUpload"METHOD_HTTPS_DOWNLOAD为"/downloadpc"(这看起来像一个URL路径)METHOD_HTTPS_WEB_GET_LOGIN_TOKEN为"WebGetLoginToken"
在本地使用curl测试获取登录token
curl 'http://127.0.0.1:10000?method=WebGetLoginToken'
可以成功获取到当前已经登录的百度云账户用户名(展示名displayname)、头像连接(portrait)以及最重要的authToken

拿到authToken就可以直接登录百度云盘等涉及百度账户的验证环境。
同样想看下OpenSafebox是否也存在类似Windows客户端的命令注入漏洞,

因此可利用交叉引用(XREF)从数据(方法名)找到使用它的代码(分发函数),再从分发函数找到真正的处理函数。
如上图所示的XREF有很多,其中一个引用来自get_request_response函数,双击即可跟进函数的具体实现处理方式等,或者从ghidra的反编译窗口里找if语句调用的process_open_safebox_request函数,同样可双击跟进函数内的具体实现方式。

双击跟进后就来到了OpenSafebox请求的分发点

process_open_safebox_request(HttpsRequestProcessor *this, int *param_1, string *param_2)
this: 指向HttpsRequestProcessor对象的指针,包含了请求的所有上下文信息。int *param_1: 输出参数,用于设置返回的 HTTP 状态码 (例如 200, 400)。string *param_2: 输出参数,用于设置返回给客户端的响应体内容。
然后根据不同的结果响应不同的状态码以及内容

请求参数解读如下
local_40 = (string)0x4; // 字符串长度 4
local_3f = 0x6b75; // ASCII "uk"
local_3d = 0; // 字符串结束符
pHVar2 = (HttpsRequestProcessor *)std::__tree<>::find<>((__tree<> *)(this + 0x80),&local_40);
- 这几行代码首先在栈上构造了一个
std::string对象,内容是"uk"。 - 然后,它调用
std::__tree<>::find<>在一个树形结构(通常是std::map的底层实现)中查找这个"uk"键。 this + 0x80指向的正是存储了所有请求参数的map。
下面属于对uk参数的校验
if (pHVar2 == this + 0x88) {
_internal_log(0x40,"HttpsRequestProcessor::process_open_safebox_request uk not found");
uVar3 = 1;
}
this + 0x88是参数map的end()迭代器。如果find的结果等于end(),说明没有找到。- 如果找不到名为
"uk"的参数,程序会记录一条日志 "uk not found" 并返回错误。
关键处理如下
else {
// 从map中提取"uk"参数的值
std::string::string(&local_40,(string *)(pHVar2 + 0x38));
// 将"uk"的值传递给真正的处理函数
iVar1 = open_safebox(&local_40);
// 根据open_safebox的返回值设置HTTP响应
if (iVar1 == 0) { // 成功
*param_1 = 200;
}
else { // 失败
*param_1 = 400;
}
// ...
uVar3 = 0;
}
然后我们跟进open_safebox,看下它的实现

完整的反汇编如下
/* baidu::netdisk::service::open_safebox(std::string const&) */
undefined4 baidu::netdisk::service::open_safebox(string *param_1)
{
int iVar1;
undefined4 uVar2;
void *pvVar3;
void *pvVar4;
pair<> *this;
pair<> *ppVar5;
ulong local_88;
undefined8 uStack_80;
void *local_78;
void *local_70;
locale local_60 [8];
locale local_58 [8];
util local_50;
undefined4 local_4f;
undefined1 local_4b;
long local_48;
void *local_40;
undefined1 local_38;
undefined4 *local_30;
local_88 = 0;
uStack_80 = 0;
local_78 = (void *)0x0;
pvVar3 = operator.new(0x20);
pvVar4 = operator.new(0x60);
*(void **)((long)pvVar3 + 8) = pvVar4;
*(undefined8 *)((long)pvVar4 + 0x38) = 0;
*(long *)((long)pvVar4 + 0x40) = (long)pvVar4 + 0x38;
*(long *)((long)pvVar4 + 0x48) = (long)pvVar4 + 0x38;
*(long *)((long)pvVar4 + 0x58) = (long)pvVar4 + 0x50;
*(long *)((long)pvVar4 + 0x50) = (long)pvVar4 + 0x50;
*(undefined8 *)((long)pvVar3 + 0x18) = 0;
local_50 = (util)0x4;
local_4f = CONCAT13(local_4f._3_1_,0x6b75);
local_38 = 0x2e;
local_70 = pvVar3;
local_30 = &local_4f;
boost::property_tree::basic_ptree<>::put<>(&local_88,&local_50,param_1);
if (((byte)local_50 & 1) != 0) {
operator.delete(local_40);
}
local_50 = (util)0x8;
local_4f = 0x65707974;
local_4b = 0;
local_38 = 0x2e;
local_30 = &local_4f;
std::locale::locale(local_58);
std::locale::locale(local_60,local_58);
boost::property_tree::basic_ptree<>::put<>
((basic_ptree<> *)&local_88,&local_50,"open_safebox",local_60);
std::locale::~locale(local_60);
std::locale::~locale(local_58);
if (((byte)local_50 & 1) != 0) {
operator.delete(local_40);
}
base::util::convert_ptree_to_string(&local_50,(basic_ptree *)&local_88);
if (((byte)local_50 & 1) == 0) {
if ((byte)local_50 >> 1 != 0) goto LAB_10007560c;
}
else if (local_48 != 0) {
LAB_10007560c:
iVar1 = send_message((string *)&local_50);
if (iVar1 == 0) {
uVar2 = 0;
}
else {
uVar2 = start_netdisk_process((string *)&local_50);
}
goto joined_r0x000100075660;
}
uVar2 = 1;
_internal_log(0x40,"ASSERT FAIL @ %s(%d)",
"/Users/ferry5/ONLINE_SERVICE/other/ferry/task_workspace/2b7eb7a409efeaf01018ee5eb06 5b8ce/baidu/netdisk/pc-browserengine/source/startupservice/httpsservice/https_util.c pp"
,0xd5);
joined_r0x000100075660:
if (((byte)local_50 & 1) != 0) {
operator.delete(local_40);
}
pvVar3 = local_70;
ppVar5 = *(pair<> **)((long)local_70 + 8);
this = (pair<> *)(*(long *)(ppVar5 + 0x58) + -0x50);
if (*(long *)(ppVar5 + 0x58) == 0) {
this = (pair<> *)0x0;
}
if (this != ppVar5) {
do {
ppVar5 = (pair<> *)(*(long *)(this + 0x58) + -0x50);
if (*(long *)(this + 0x58) == 0) {
ppVar5 = (pair<> *)0x0;
}
std::pair<>::~pair(this);
operator.delete(this);
this = ppVar5;
} while (ppVar5 != *(pair<> **)((long)pvVar3 + 8));
}
operator.delete(ppVar5);
operator.delete(pvVar3);
if ((local_88 & 1) != 0) {
operator.delete(local_78);
}
return uVar2;
}
可以看到开发或者打包百度网盘这个功能的哥们电脑用户名ferry5,以及项目路径/baidu/netdisk/pc-browserengine/ ,为了方便调试而留的
整个函数的功能并没有直接的执行操作,更像是一个中间人,负责传话的。简析如下
// ...
// 1. 添加 "uk" 字段
boost::property_tree::basic_ptree<>::put<>(&local_88, "uk", param_1);
// 2. 添加 "type" 字段
boost::property_tree::basic_ptree<>::put<>
((basic_ptree<> *)&local_88, "type", "open_safebox", local_60);
// ...
- 代码使用了
boost::property_tree库,这是一个常用于处理配置数据(如 JSON、XML)的工具。 - 它创建了一个数据结构,并向其中添加了两个键值对:
- 键
uk,值是param_1(客户端传来的uk值)。 - 键
type,值是固定的字符串"open_safebox"。
- 键
base::util::convert_ptree_to_string(&local_50, (basic_ptree *)&local_88);序列化数据。- 发送消息(传话)
iVar1 = send_message((string *)&local_50);
if (iVar1 == 0) {
uVar2 = 0; // 成功
}
else {
// 如果发送失败,尝试启动主进程再发送
uVar2 = start_netdisk_process((string *)&local_50);
}
- 这是最关键的一步。程序调用
send_message函数,将刚刚生成的包含type和uk的字符串发送给另一个正在运行的进程。 - 如果
send_message失败(可能因为主程序没有运行),它会尝试调用start_netdisk_process来启动主程序,并把消息作为启动参数传递过去。
因此需要继续分析的话,就需要分析百度网盘主程序,看了一眼是个混血儿界面是Electron写的,还要跟踪分析寻找监听和读取名为 "netdisk_unite_message_queue" 的消息队列的逻辑 ,这就触及我的知识盲区了啊!等有能力的师傅去分析或者等我学会了再来分析。
通过本次简单分析并不是毫无收获嘛,我们找到了一个可用于蜜罐的百度网盘API,通过WebGetLoginToken获取已经登录的百度账户的token、头像、用户名等等信息。







GetVersion等api能调用,网页也会返回东西
但是WebGetLoginToken就返回空白了,什么数据都没有
请问师傅是什么版本的啊