内容简介:本文翻译自:OpenSSH用户枚举漏洞(CVE-2018-15473)是由这个漏洞虽然不会生成有效用户名的列表名单,但它允许猜测用户名。
本文翻译自: https://blog.nviso.be/2018/08/21/openssh-user-enumeration-vulnerability-a-close-look/
介绍
OpenSSH用户枚举漏洞(CVE-2018-15473)是由 GitHub commit 公开的。
这个漏洞虽然不会生成有效用户名的列表名单,但它允许猜测用户名。
在这篇博客文章中,我们对这个漏洞进行了更深入的研究,并提出了缓解措施和监控措施。
技术细节
该漏洞表现在OpenSSH的几个认证功能中。我们密切关注Ubuntu OpenSSH实现的公钥认证中的这个漏洞。
通过向OpenSSH服务器发送格式错误的公钥认证消息,可以确定特定用户名的存在。如果用户不存在,则向客户端发送认证失败消息。如果用户存在,解析消息失败将中止通信:连接将在不发送任何消息的情况下关闭。此漏洞在 Python 的PoC脚本中实现。
该漏洞是因为在对消息进行完全解析之前,存在用户名不存在的通信。修复漏洞本质上是简单的:逆向逻辑。首先完全解析消息,然后进行通信。
测试PoC的一种方法是在调试模式下启动OpenSSH服务器:
之后,使用已存在的用户名执行PoC脚本
在服务器端,将会发生错误:
也可以在/var/log/auth.log中找到这个错误:
解析消息失败,导致客户端和服务器之间的连接关闭,而且没有来自服务器的消息:
注意,最后一个报文是粉红色的(即客户端报文),没有后续的蓝色报文(即服务器报文)。
当使用不存在的用户名来执行PoC脚本时:
没有出现“不完整消息”的错误信息:
服务器向客户端发回消息:
在通信结束时注意蓝色服务器报文。
这就是如何利用公钥认证中的漏洞来揭露用户名的有效性。
当然,OpenSSH的行为是在源代码中定义好的。函数 userauth_pubkey
是实现的身份验证功能之一,特别用于通过公钥进行身份验证。验证失败返回0,验证成功则返回1。当收到消息 SSH2_MSG_USERAUTH_REQUEST
(类型为publickey)时就会调用它,之后这个结果会被用来将消息 SSH2_MSG_USERAUTH_FAILURE
或 SSH2_MSG_USERAUTH_SUCCESS
发送回客户端。
该函数的逻辑如下:
如果是未知用户名->0
如果是已知用户名,但密钥不正确->0
如果是已知用户名,并且密钥正确 ->1
一个 很聪明的人 想到的是,在步骤1和步骤2之间可以停止执行函数 userauth_pubkey
。步骤1后,函数 userauth_pubkey
从客户端发送的消息中检索字符串。如果这个失败,由于字符串格式错误,进程将停止并关闭连接,而不发送任何消息。
这种情况可能是由函数 packet_get_string
导致的:
如果用户名存在,则在步骤1之后将从消息中提取字段。
第一个字段为boolean(一个字节),使用 函数packet_get_char()
提取。当认证类型为publickey时,该字段值等于1。接下来是2个字符串:算法和密钥。在SSH消息中,字符串会被编码为 长度-值
键值对。这个字符串由4个字节(即字符串的长度)组成,后面紧跟包含字符串内容的可变字节数。长度会被编码为bigendian(大端)格式,即先存放4字节整数的最高有效字节,然后是较低有效字节。
函数 packet_get_string
从消息中提取并验证字符串, 即检查字符串的长度是否正确。但此功能依赖于其他项:
函数 ssh_packet_get_string
的定义如下:
函数 ssh_packet_get_string
会调用函数 sshpkt_get_string
,如果其返回值不为0,则调用函数 fatal
,而函数 fatal
会记录严重错误的事件,然后终止OpenSSH进程,且不发回任何消息。
现在我们来看看另一个函数链:函数 sshpkt_get_string
调用 sshbuf_get_string
:
sshbuf_get_string
调用 sshbuf_get_string_direct
:
sshbuf_get_string_direct
调用 sshbuf_peek_string_direct
:
最后, sshbuf_peek_string_direct
函数实现字符串的验证:
如果消息中的剩余数据小于4个字节(不能包含字符串的长度),或者消息中的剩余数据小于消息的长度,则返回错误提示信息 SSH_ERR_MESSAGE_INCOMPLETE
(在日志中能找到)字符串。
总结这一系列函数:当 packet_get_string
用于从消息中提取字符串时,如果字符串格式错误,则会发生严重的异常,从而导致OpenSSH进程终止。
这正是PoC Python脚本的触发条件。首先,它与OpenSSH服务器建立加密连接,然后发送格式错误的 SSH2_MSG_USERAUTH_REQUEST
(类型为publickey)消息。这个脚本将 Paramiko
的 add_boolean
函数重新定义为 NULL
函数。 Paramiko
是用于SSH通信的Python模块。通过重新定义 add_boolea
n函数,消息中省略了布尔字段(就在算法和关键字符串字段之前)。
当函数 userauth_pubkey
解析这个格式错误的消息时,首先读取boolean字段。由于该字段实际上是丢失的,因此读取下一个字段的第一个字节(函数 packet_get_char
):算法字符串的4字节长度的最高有效字节。调用下一个函数 packet_get_string
来读取(并验证)算法字符串。由于缺少boolean字段,毋庸置疑,这注定失败。
以下是格式稍好的消息的解析过程:
格式错误的消息是缺少boolean值的,但解析函数并不知道这一点,于是它将字符串的第一个字节解析为boolean字段:看起来就像消息向左移一个字节:
结果就是解析了1907字节长的字符串(0x00000773十六进制),这比消息本身长。因此,函数 ssh_packet_get_string
将调用 fatal
函数,导致OpenSSH进程终止。
漏洞总结
这是一个微妙的错误, 它不是关于缓冲区溢出导致远程代码执行或缺少输入验证的问题。
没有缓冲区溢出,所有输入在使用前都要经过验证。这里的问题是输入验证是在一些功能处理已经发生了之后再发生的:可能出于性能原因,首先检查用户名以查看它是否存在,如果它不存在,那么进一步的输入验证和处理就毫无必要了。
使用已经存在的用户名,将会进行输入验证,并且可以在不发送消息的情况下关闭连接。这可用于导出用户名的存在与否。
这个问题的解决方案很简单:在任何功能处理之前,切换顺序并首先进行所有的输入验证。
在其他身份验证功能中可能会出现相同的错误。一个粗略的,不完整的方法是检查表达式 !authctc-> valid
,如下所示:
在基于主机的身份验证中的确犯了同样的错误(可以在GitHub commit 中看到):
以及Kerberos的身份验证:
并且潜在的SSH1的RSA身份验证(我们没有进一步检查,因为它不再存在于OpenBSD等实现中):
请注意,这种风险万不可轻视!
结论
您在使用OpenSSH的过程中,可以自己动手来减缓这个漏洞。在修补程序可用并部署之前,可以禁用易受攻击的身份验证机制。例如,通过禁用公钥身份验证,PoC脚本将不再有效,因为已经拒绝了格式错误的身份验证请求。
当然,如果您不使用公钥认证,我们只建议您禁用公钥认证。如果您使用它,请不要切换到密码验证,但继续使用公钥验证!这不是远程执行代码漏洞,而是一个信息泄露漏洞。
您还可以检查日志中是否有利用这个漏洞的迹象。致命错误可能是一个迹象。在Ubuntu上使用这个PoC,致命错误是“不完整的消息”。但是,此消息可能略有不同,具体得看您的OpenSSH版本,其他方法也可以生成格式错误的消息,这可能会导致另一个致命错误。例如,可以构造一个字符串长度超过最大允许值的身份验证请求。
在默认配置中,您只会收到此致命错误。例如,客户端的IP地址将不会被记录。您可以通过将日志级别(LogLevel)从INFO级别提高到VERBOSE级别,从而创建额外的日志条目,其中包含客户端的IP地址。请注意,这将生成更大的日志,并且您应该随时留意日志不会超过您的可用容量。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- c# – 枚举时项目发生变化时是否会影响枚举?
- 测者的测试技术手册:Junit单元测试遇见的一个枚举类型的坑(枚举类型详解)
- 测者的JUnit单元测试探坑记:Junit单元测试遇见的一个枚举类型的坑(枚举类型详解)
- c# – 循环枚举类型
- Python 的枚举类型
- 枚举的使用示例
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。