企业微信iPad协议端强制拉群漏洞深度分析

正常一次最多邀请40人进群

超过40人的拉群,会变成邀请,需要对方同意

新版本修复了漏洞,但还是可以用老版本进行强制拉群

虽然官方也做了版本过低的限制,但还是有办法绕过

要么修改版本号或者登录几天新版本,之后就可以登录老版本

关键是,新版本的修复漏洞的方案并不彻底

深入研究发现更严重的漏洞,最新版本也能强制拉群

而且这个漏洞是Duilib框架层面的,影响的不只是拉群老版本强制拉群漏洞

1、正常情况

点击主界面的添加群成员

会弹出一个选择用户的界面

再去点主界面的添加群成员

选择用户的界面还是同一个

2、异常情况

工作台-客户群-前往查看客户群-添加群成员

点多少次就会出现多少个选择用户的界面

分析漏洞背后的原因

父界面的句柄

企业微信的界面框架是Duilib

弹出子界面之后,还能点击父界面

第一反应是创建子界面没有传父界面的句柄

拦截创建界面的函数Create,发现都有传父界面的句柄

1、正常情况

2、异常情况

好玩的是,正常和异常情况的两个选择用户界面可以同时出现

因为"工作台-客户群-前往查看客户群"这一步,创造新界面的时候没有传父窗口句柄

调用堆栈

既然都传了父界面的句柄,那差异可能是代码逻辑不一样

继续拦截创建界面的函数Create,看调用堆栈有什么差异

1、正常情况

调用堆栈有DispatchMessageW,并且窗口事件是0x403

0x403代表的是自定义消息,说明创建子界面的异步的

2、异常情况

调用堆栈有WndProc,并且窗口事件是0x202

说明是鼠标弹起之后,直接同步地创建子界面

窗口属性

正常情况异步创建子界面,但父界面没响应

异常情况同步创建子界面,但父界面有响应

都有父窗口句柄,说明正常情况额外加了逻辑

用spy++看一下父界面窗口的样式属性

1、正常情况

创建子窗口之前

创建子窗口之后

多了WS_DISABLED,因此父界面没响应

2、异常情况

创建子窗口之前

创建子窗口之后

没有WS_DISABLED,因此父界面有响应

漏洞的原因就定位到了:异常情况,父窗口的样式因为没有WS_DISABLED导致能被点击多次

最新版本利用这个原理,正常情况点击添加群成员,代码去掉WS_DISABLED,可以被点击多次

没有WS_DISABLED是最终结果,好奇代码层面到底是哪里出问题

对SetWindowLong下断点,看下是不是异常情况少了这个逻辑

发现正常情况和异常情况都没有命中SetWindowLong这个函数

影响WS_DISABLED的API还有EnableWindow,继续下断点

确实正常情况会调用EnableWindow,异常情况则不会

EnableWindow对应在Duilib里面是ShowModal

问题就出在:异常情况没有调用ShowModal,导致父窗口没有锁定

但因为异常情况是同步创建子窗口,如果直接调用ShowModal会进入新的消息循环,同一线程的父窗口消息循环会被阻塞,自己维护一下EnableWindow会好一些

不过就像前面说的,正常情况有调用ShowModal,去掉WS_DISABLED还是能强制拉群

想要保证窗口的唯一性,还是得在逻辑代码上加判断,不能只是依赖Duilib框架本身的限制