介绍
如果你刚刚接触这个系列,Doyensec团队正在地中海的一艘游轮上进行安全研究。第一部分我们探讨了IoT ARM设备的利用,而接下来的博客将涵盖Web目标。在本期中,我们尝试利用有史以来最著名的漏洞之一——2001年的SSHNuke,也就是电影《黑客帝国:重装上阵》中Trinity使用的那个漏洞。
历史背景
1998年,Ariel Futoransky和Emiliano Kargieman意识到SSH协议存在根本性缺陷,因为可以注入密文。因此添加了crc32校验和来检测这种攻击。
2001年2月8日,Michal Zalewski在Bugtraq邮件列表中发布了一份名为"SSH守护进程crc32补偿攻击检测器中的远程漏洞"的公告,标记为CAN-2001-0144。
这个"crc32"检测器存在一个独特的内存破坏漏洞,可能导致任意代码执行。直到6月份,TESO Security才发布了关于他们编写的漏洞利用程序泄露的声明,这表明在6月之前没有可靠的公开漏洞利用程序。
动手尝试
在海上没有互联网的情况下,尝试构建20年前的软件是一场噩梦。当我们的一些团队成员致力于此时,我们将漏洞移植到了一个独立的main.c文件中,任何人都可以在任何现代(甚至旧)系统上轻松构建。
漏洞分析
漏洞的核心在于以下源代码:
|
|
这段代码确保h缓冲区及其大小n得到适当管理。由于h和n被声明为static,xmalloc将在第一次调用时初始化h。后续调用测试len是否太大而n无法处理——如果是,则执行xrealloc。
变量n在早期声明为16位值(static u_int16_t),而l是32位(u_int32_t)。因此,如果l大于0xffff,在n = l时可能发生整数溢出。
for循环中的位运算:
|
|
这个神秘的循环是我们设置l的唯一机会。它最初将l设置为n。每次循环运行时,l左移2位(l « 2),这实际上每次迭代都将l乘以4。
我们知道l最初是0x1000,经过一次循环后变为0x4000,再次循环变为0x10000。这个0x10000值转换为u_int16_t时会溢出并结果为0。因此所有可能的n值是0x1000、0x4000和0。
崩溃分析
崩溃发生是因为检查h[0x41414141] != HASH_UNUSED时访问了无效内存:
|
|
如果h[i]是一个可读偏移量?经过一些检查后,我们会到达h[i] = j的位置。注意j是循环中的迭代次数,我们可以通过缓冲区长度控制它。i是我们的0x41414141,我们也可以控制它。因此我们最终在循环中获得了一个写-什么-在哪里的原语。
攻击真实系统
此时我们有了一个正常工作的OpenSSH服务器设置。我们需要通过SSH协议1发送缓冲区。我们找不到能够与这种过时的损坏协议配合使用的SSH Python客户端。预期的解决方案是修补OpenSSH加密部分,使其成为简单的套接字连接。
相反,我们修补了源代码附带的OpenSSH客户端。似乎真正的漏洞利用作者可能采取了类似的方法。
利用挑战
理解这个利用原语有很多弱点很重要。h缓冲区是u_int16_t *。在小端系统上,除非设置j的高位,否则无法向(char *)h + 0写入任意值。要能够设置j的所有高位,需要能够循环0x10000次。
此外,如果想将相同的值写入两个位置,必须在不崩溃的情况下两次调用易受攻击的函数。但是一旦导致静态n为0,它将在下次重新进入时保持为0。这将导致l位移动循环无限循环。
所有这些都会影响你选择实现RCE的路径。Trinity的漏洞利用用新的任意字符串覆盖了root密码。这是通过将记录器指向/etc/passwd完成的吗?与shell代码相比,这样做有什么优势?破坏身份验证流程并将"已认证"位从false翻转为true怎么样?能否覆盖内存中的客户端公钥,使RSA指数为0?有这么多有趣的选项可以尝试。
结论
我们的目标是使修补过的OpenSSH崩溃。考虑到可用的时间和资源,我们超出了自己的期望,通过控制使未修补的OpenSSH崩溃。这归功于团队合作和利用过程中的创造性时间节省。在整个过程中有大量的理论构建,帮助我们避免了时间陷阱。最重要的是,我们玩得很开心。