ISPsystem漏洞分析

一、前言

ISPsystem是一款知名软件,web界面非常友好,可以用于web服务器、专用服务器、VPS以及账单管理场景。ISPsystem系列产品被全世界数百家托管服务器商所使用,包括1Cloud、King Servers以及Ru-Center。ISPsystem产品目前已有超过10,000个安装实例:

图1. ISPmanager安装量估计

最知名的ISPsystem产品包括:

  • ISPmanager:用于Web托管以及Linux服务器的控制面板(与cPanel)类似;
  • BILLmanager:一体化Web托管计费软件;
  • DCImanager:专用服务器配置工具集;
  • VMmanager:服务器虚拟化管理软件,有两种形态,分别适用于OpenVZ以及KVM虚拟化方案。

在本文中,我们将与大家分享在ISPsystem软件中找到一个严重漏洞的过程,攻击者可以利用该漏洞劫持已登录用户的会话,控制该用户的网站、虚拟机、账单数据等。由于所有ISPsystem产品使用的都是同一个内核,因此都会受到该漏洞影响。

幸运的是,ISPsystem快速响应了我们的漏洞报告,在5.178.2版本中修复了该漏洞。

 

二、漏洞分析

环境配置

ISPsystem允许用户免费下载和安装相关软件,为了使用这些软件,我们需要购买许可证,但也可以获取功能受限的试用版许可证。在这次研究中,我们下载和安装了用于VPS管理的ISPsystem面板:

图2. 安装VMmangager

该脚本可以设置所需的环境,包括MYSQL数据库服务器以及HTTP服务器。默认情况下,HTTP服务器运行在1500 TCP端口。

图3. VMmanager HTTP服务器进程

完成必要的安装步骤后,我们可以通过浏览器访问控制面板,登录界面如下:

图4. VMmanager面板登录界面

现在我们可以在本地进行测试,不会影响托管上提供的任何系统。

认证过程

来看一下认证过程。该系统使用如下HTTP POST请求来执行认证过程:

图5. VMmanager HTTP认证请求

成功认证后,服务器会设置会话cookie,浏览器会保存独特的一个字符串。会话cookie使系统能够识别用户,无需每次都请求用户名及密码。会话cookie跟具体产品名有关,由两部分组成:产品名(比如vmmgr或者vemgr)以及ses5。在我们的测试案例中,会话cookie名为vmmgrses5,还有另一个cookie(vmmgrlang5),用来选择用户接口所使用的模板和语言。

图6. VMmanager成功认证响应包

如上图所示,cookie值是十六进制字符组成的一个字符串,大小为6字节(字符串始终包含12个字符,字符集为[0-9a-z])。cookie有1年的过期时间。

因此,攻击者只需要正确选取6字节值就能劫持另一个用户的有效会话,整个会话标识符空间中只包含256^6种组合。

预测会话标识符

实际上,对于远程暴力破解攻击来说,256^6仍然是一个非常庞大的攻击空间。因此,我们觉得看一下是否能够进一步缩小枚举空间。

为了完成该任务,我们需要澄清会话cookie的生成算法。

目标软件的业务逻辑采用C++语言实现。实际上,服务器会采用C++代码来执行各种操作,比如数据库操作、用户认证以及用户会话管理等,这些操作执行在ihttpd进程的上下文中。

由于我们需要澄清会话cookie的生成方式,因此我们在二进制文件中搜索ses5字符串,该字符串存在于如下文件中:

/usr/local/mgr5/lib/libispapi.so
/usr/local/mgr5/lib/libmgr.so
/usr/local/mgr5/lib/libispcgi.so
/usr/local/mgr5/lib/libostemplate.so
/usr/local/mgr5/libexec/ihttpd.so

简单分析如上文件后,我们发现密码认证过程由libispapi.so库负责处理:

在认证过程中,目标服务会创建isp_api::Session类的一个新对象:

图7. 反编译后的部分认证代码:创建Session类的一个实例

最后,系统会调用isp_api::Authen::Data::generate_id例程来生成会话标识符。实际上,在该例程中,我们可以看到如下代码:

图8. 生成会话cookie值

前面我们提到过,会话标识符的长度为6字节。这个值也会作为参数,从libmgr.so库传递给str::Random例程。最后我们需要寻找的就是str::Random的具体实现。我们不要把该函数与std::rand这个库函数混淆起来,这两个函数的具体实现完全不同。

为了生成随机的字节,str::Random方法中用到了rand()函数:

图9. str::Random部分实现代码:生成随机序列

为了生成长度为N的随机字符串,rand()函数会被调用N次。处理结果会被赋值到char类型的一个变量。然后,rand()函数生成的变量会被裁剪成8比特长度。此时,我们应当注意一点,rand()函数并不会生成真正的随机数。相反,该函数实现的是一个伪随机数生成算法

其中很重要的一点:伪随机生成器所生成的序列完全由其初始状态所决定,也就是所谓的”种子”。因此,相同种子会生成相同的伪随机数序列。str::Random方法生成会话cookie的过程如下所示:

图10. 使用str::Random生成会话cookie

在我们的测试案例中,系统只有在生成若干次伪随机值(少于400次)之后才会设置新的种子,此外种子还是一个32位整数。来看一下负责设置随机种子的str::Random函数的具体实现代码:

图11. str::Random部分实现代码:设置随机种子

在如上伪代码中,g_rnd_reset_counter变量用来确定伪随机数生成器所使用的种子是否需要更新。g_rnd_reset_counter的初始值为rand()%255 + 128。也就是说,该变量可以取128382之间的值。每次str::Random被调用时,就会从g_rnd_reset_counter变量中减去已生成随机字符串的长度值。当该变量小于或等于0时,代码就会重置生成器的种子。

根据str::Random的具体实现,我们有可能推测出用来初始化已知会话cookie的伪随机生成器所使用的最后一个种子值。为了完成该任务,我们可以遍历所有可能的种子值,搜索伪随机生成器所生成的短序列会话cookie。

图12. 种子搜索过程

如果我们知道会话cookie,就可以找到生成器所使用的种子值,然后预测使用该种子的生成器所使用的所有序列。

因此,当我们获得会话cookie后,如果另一个用户登录系统,我们就可以使用暴力破解攻击,最多尝试382次后就能获取该用户的会话cookie。还有其他类似场景,如果用户用户在我们获取会话cookie之前刚好登录也可以。这些攻击场景如下图所示:

图14. 伪随机会话标识符预测过程

可能的攻击场景

在攻击场景中,攻击者可能执行如下操作:

1、在某个时间段“T”,使用合法的用户名和密码登录系统,然后保存系统分配的会话cookie值;

2、使用rand函数,从02^32遍历所有种子,生成大小为382字节的一系列数组,搜索前面已保存的会话cookie序列(需要注意的是,这里需要使用目标系统所使用的同一个rand函数);

3、从382字节数组中提取出所有的6字节子序列,从中找到已知的会话cookie;

4、尝试使用已提取的所有6字节字串,使用该会话cookie登录系统;

5、如果在攻击过程中有另一个用户登录,攻击者就可以劫持该用户会话。

时间段“T”应该尽可能小,以确保我们至少能获得生成器所使用的每个新种子所对应的一个会话cookie。“T”的最佳取值取决于攻击时所处的具体时间以及活跃的用户数。我们还需要考虑其他位置是否也会调用rand()函数。因此,如果活跃用户过多,“T”值应当适量减少。

在测试案例中,使用6字节序列查找种子在16核CPU上最多需要20分钟,并且我们可以通过硬件规模控制所需的查找时间。我们也可以预先生成所有2^32个序列,将其存放到数据库中。我们需要1.5TB空间才能存放生成的所有数据。因此,我们可以通过多种方法,在已知序列的情况下实时确定目标种子值。获取种子以及序列后,我们需要将所有6字节子序列应用到可能的会话cookie中。

我们也可以通过序列开头位置计算出已知会话cookie的偏移量。如果没有登录用户,这些偏移之间距离恒定。如果有其他用户登录,则距离就会相应增加。

攻击PoC

我们使用Python和urllib2库实现第一个攻击步骤。在代码中,我们使用如下参数来登录目标面板:

图14. Exploit Poc:创建登录请求,设置HTTPS链接(Python)

vemgrses5 cookie获取会话标识符:

图15. Exploit PoC:获取会话cookie值(Python)

最开始我们执行该脚本来确定当只有一次登录操作并且没有其他活跃用户时,系统对rand()的调用频次。

为了找到脚本获取的会话标识符所对应的种子值,我们可以使用如下C代码:

图16. Exploit PoC:生成特定种子所对应的随机数序列(C语言,Linux)

如上代码可以设置种子值,生成伪随机序列,以便后续查找已知的会话cookie。我们在代码中使用了srandom_r()以及random_r()函数,而没有使用srand以及rand,以便代码能够在多线程并行运行。

我们得到的结果如下表所示:

图17. 在生成序列中找到会话标识符以及相应的偏移值

在上表中,“Session cookie”栏为每次成功登录后服务器返回的vmmgrses5标识符,“Found seed”栏为我们找到的与会话cookie对应的种子值。所有会话cookie都属于同一个种子生成的伪随机序列,“Offset”栏为距伪随机序列起始处的偏移值,最后一栏为随机序列中相邻会话标识符的距离。

我们可以看到,在生成的序列中我们获取到的会话cookie(6字节数组)偏移之间的距离始终是14。这意味着对于每次成功登录操作,rand()通常只会被调用14次:6次用来生成会话cookie字符串,8次在其他地方调用。因此,如果距离大于14,那么就有另一个活跃的用户存在。

在脚本执行期间,我们使用浏览器尝试登录,目标服务器会我们的会话分配的cookie为vemgrses5=9e723afa5922

图18. VMmanager服务器返回的HTTP响应头

这个会话cookie其实也是可以预测的。

对于该脚本获取的会话cookie,我们在实验中得到了如下结果:

图19. Demo:已知会话标识符的种子查找结果

从10:57:56到10:58:17时间段之间的偏移间距一直在增加,这个时间段与用户的活跃时间段一致。因此,通过检查间距值,我们就可以判断目标网站上其他用户的活跃情况。

让我们观察0x747777E4种子所生成的伪随机序列(该时间段内使用的是该种子):

图20. Demo:可预测的伪随机序列中包含我们想找的标识符

如上所示,用户会话cookie存在于生成的序列中。通过脚本获取的已知值在上图中用绿色高亮标出,我们预测的值用红色高亮标出。

在攻击过程中,我们要做的最后一件事就是暴力破解出已知值间距超过14的所有6字节子串:

图21. Demo:暴力破解攻击

在本例中,我们只需要检查66个值,就能找到另一个用户会话cookie对应的正确值。

最后,由于默认配置的服务器并没有匹配会话cookie所对应的IP地址,因此我们可以使用窃取的会话ID,通过如下请求来入侵另一个用户的会话:

图22. Demo:劫持会话的请求

整个攻击过程可参考该视频

从视频中我们可知,攻击者不需要太多资源就能轻松发起攻击。

受影响的ISPsystem产品列表如下:

ISPsystem ISPmanager
ISPsystem BILLmanager
ISPsystem DCImanager
ISPsystem VMmanager
ISPsystem DNSmanager
ISPsystem IPmanager
ISPsystem COREmanager

如果您正在使用如上ISPsystem产品,对应的核心版本低于5.178.2,我们建议您尽快升级至最新版。

(完)