51

漏洞分析:OpenSSH用户枚举漏洞(CVE-2018-15473)分析

 5 years ago
source link: http://www.freebuf.com/vuls/184583.html?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

jiye6b7.jpg!web

介绍

这个漏洞虽然不能生成有效用户名列表,但是它可以允许攻击者猜测用户名。目前这个OpenSSH用户枚举漏洞( CVE-2018-15473 )的详细信息已经上传至了GitHub,感兴趣的同学可以自行查看【 传送门 】。

在这篇文章中,我们将对该漏洞进行深入分析,并提供一些可行的缓解方案。

技术细节

这个漏洞存在于OpenSSH所实现的一些认证功能之中,首先我们一起看一看Ubuntu OpenSSH的公共密钥认证漏洞。

通过向一台OpenSSH服务器发送恶意的公共密钥认证消息,攻击者将能够获取特定的用户名信息。如果用户不存在,服务器将会向客户端发送认证失败的消息。如果用户存在,消息将无法解析并终止通信,即通信连接会在没有任何消息回传的情况下断开。关于该漏洞的漏洞利用代码可以从这个Python PoC脚本中获取:【 传送门 】。

这个漏洞之所以存在,是因为服务器在对消息完整解析之前,用户查询了不存在的用户名。想要修复该漏洞也很简单,按攻击逻辑反着来就行了:首先对消息进行完整解析,然后再建立通信连接。

测试漏洞利用PoC的一种方法就是在调试模式下开启OpenSSH服务器:

6zyInuM.jpg!web

然后用已存在的有效用户名运行PoC脚本:

bYVfUfj.jpg!web

在服务器端将会查看到错误提示:

jArQN3Q.jpg!web

相关错误信息还可以在/var/log/auth.log中找到:

biaUreE.jpg!web

如果无法正确解析消息,会导致客户端跟服务器端之间的通信中断,而且中断时不会收到服务器发送的提示信息:

AfmAnqM.jpg!web

注意粉红色标记的最后一个数据包(客户端数据包),这里没有后续的蓝色数据包(服务器数据包)。

当PoC脚本以不存在的用户名运行之后:

ABvIZbU.jpg!web

不会弹出“imcomplete message”错误提示:

MFvMru6.jpg!webRNJZRr.jpg!web

注意通信数据结尾处的蓝色服务器数据包。

这就是该漏洞(公共密钥认证漏洞)暴露有效用户名的整个流程了。

其中,userauth_pubkey函数是认证功能所实现的其中一个函数,专门用于根据公共密钥来完成身份验证。如果认证失败,则返回“0”,成功则返回“1”。当服务器端接收到了SSH2_MSG_USERAUTH_REQUEST请求后,便会调用该函数,之后的结果会用来给客户端回传SSH2_MSG_USERAUTH_FAILURE或SSH2_MSG_USERAUTH_SUCCESS消息。

2uiIveb.jpg!web

该函数的运行逻辑为:

1.   如果用户名不存在:返回“0”;
2.   如果用户名存在但密钥错误:返回“0”;
3.   如果用户名存在且密钥正确:返回“1”;

但是有人发现,我们竟然可以在第一步和第二步中间终止userauth_pubkey函数的运行。第一步执行完后,userauth_pubkey函数会从客户端获取消息字符串,如果获取失败(恶意字符串导致),整个过程都会终止,并在不发送任何回传消息的情况下关闭连接。

packet_get_string所导致的情况如下:

MJbUveF.jpg!web

如果用户名存在,第一步会在程序从消息域中提取完数据后进行。

第一个提取的数据域是一个布尔值(1字节),对应函数为packet_get_char()。如果认证类型为publickey,返回值就是“1”。后续跟着的是两个字符串:算法和密钥。在SSH消息中,字符串会以一个“长度-值“键值对进行编码,一个字符串为4个字节。

函数packet_get_string可以从消息中提取字符串,并对其进行验证,这个函数还需要依赖另一个函数:ssh_ssh_packet_get_string。

7RVvaeB.jpg!web

ssh_packet_get_string函数会调用sshpkt_get_string函数,如果返回的值不是“0”,它还会调用fatal函数。函数fatal会记录致命的错误事件,然后终止生成的OpenSSH进程(不回传任何错误信息)。

Qb6bUv6.jpg!web

接下来会执行sshpkt_get_string函数并调用sshbuf_get_string函数:

ZzURfiz.jpg!web

然后sshbuf_get_string函数会调用sshbuf_get_string_direct:

VzIr6nz.jpg!web

然后sshbuf_get_string_direct会调用sshbuf_peek_string_direct:

AN3eErq.jpg!web

最后,sshbuf_peek_string_direct会进行字符串验证:

3M3UV3M.jpg!web

如果消息中剩余数据小于4字节,或者说消息中的剩余数据小于字符串长度,则会返回SSH_ERR_MESSAGE_INCOMPLETE 错误消息。这就是我们之前那个Python PoC脚本所要触发的东西。首先,它会跟OpenSSH服务器建立一条加密的通信链接,然后向其发送恶意的SSH2_MSG_USERAUTH_REQUEST消息。通过重定义add_boolean函数,消息中的布尔值域会被忽略。

当函数userauth_pubkey解析了恶意消息之后,首先会读取布尔值域,由于这个域其实是不存在的,因此读取的会是下一个域(函数packet_get_char):加密算法字符串的4字节长度值。然后调用下一个函数packet_get_string来读取加密算法字符串。

下面是解析合法消息的过程:

ii26nuz.jpg!web

下面是解析恶意消息的过程:

Ubee6bZ.jpg!web

结果就是,代码解析了一个1907字节的字符串(十六进制为0×00000773),这比整个消息的长度还要长,这会导致ssh_packet_get_string调用fatal函数,并中断OpenSSH进程。

漏洞总结

这是一个非常隐蔽的漏洞,它不是一个缓冲区溢出漏洞,也不是远程代码执行漏洞,更不是错误输入验证漏洞。这里不存在任何的缓冲区溢出问题,所有的输入都在使用之前进行了验证。问题就是,输入验证是在进行了一些函数处理之后完成的。

问题的解决方法也比较简单:更换函数的调用顺序即可,也就是首先进行输入验证,然后再进行函数处理就可以了。

* 参考来源: nviso ,FB小编Alpha_h4ck编译,转载请注明来自FreeBuf.COM


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK