作者:Hcamael@知道创宇404实验室
时间:2018年10月19日

最近出了一个libSSH认证绕过漏洞,刚开始时候看的感觉这洞可能挺厉害的,然后很快github上面就有PoC了,msf上很快也添加了exp,但是在使用的过程中发现无法getshell,对此,我进行了深入的分析研究。

前言

搞了0.7.5和0.7.6两个版本的源码:[1]

360发了一篇分析文章,有getshell的图:[2]

Python版本的PoC到Github上搜一下就有了:[3]

环境

libSSH-0.7.5源码下载地址: [4]

PS: 缺啥依赖自己装,没有当初的编译记录了,也懒得再来一遍

主要用两个,一个是SSH服务端Demo:examples/ssh_server_fork, 一个是SSH客户端Demo:./examples/samplessh

服务端启动命令:sudo examples/ssh_server_fork -p 22221 127.0.0.1 -v

客户端使用命令:./examples/samplessh -p 22221 myuser@127.0.0.1

PS: 用户那随便填,我使用myuser,只是为了对比正常认证请求和bypass请求有啥区别,正常情况下SSH服务端是使用账户密码认证,账户是: myuser, 密码是: mypassword

修改../src/auth.cssh_userauth_xxxx函数,我修改的是ssh_userauth_password:

根据360的分析文章和我自己的研究结果,修改了上图箭头所示的三处地方,这样./examples/samplessh就会成为了验证该漏洞的PoC

PS: 修改完源码后记得再执行一次make

漏洞分析

根据服务端输出的调试信息,可以找到ssh_packet_process函数[5], 看到第1211行:

然后追踪到callbacks数组等于default_packet_handlers[6]

正常情况下,发送SSH2_MSG_USERAUTH_REQUEST请求,进入的是ssh_packet_userauth_request函数,而该漏洞的利用点就是,发送SSH2_MSG_USERAUTH_SUCCESS请求,从而进入ssh_packet_userauth_success函数

PS: 我们可以进入该数组中的任意函数,但是看了下其他函数,也没法getshell

正常情况下的执行路径是:

找找这个函数,发现在服务端Demo中进行了设置:

找到了auth_password函数,由服务端的编写者设置的:

认证成功后的路径:

正常情况下,在SSH登录成功后,libSSH给session设置了认证成功的状态,SSH服务端编写的人给自己定义的标志位设置为1: sdata->authenticated = 1;

利用该漏洞绕过验证,服务端的流程:

可以成功的把libSSH的session设置为认证成功的状态,但是却不会进入auth_password函数,所以用户定义的标志位sdata->authenticated仍然等于0

我们在网上看到别人PoC验证成功的图,就是由ssh_packet_userauth_success函数输出的Authentication successful

研究不能getshell之谜

很多人复现该漏洞的时候肯定都发现了,服务端调试的信息都输出了认证成功,但是在getshell的时候却一直无法成功,根据上面的代码,发现session已经被设置成认证成功了,但是为啥还无法获取shell权限呢?对此,我又继续深入研究。

根据服务端的调试信息,我发现都能成功打开channel,但是在下一步pty-req channel_request我服务端显示的信息是被拒绝:

所以我继续跟踪代码执行的流程,跟踪到了ssh_execute_server_request函数:

接着发现ssh_callbacks_exists(channel->callbacks, channel_pty_request_function)检查失败,所以没有进入到该分支,导致请求被拒绝。

然后回溯channel->callbacks,回溯到了SSH服务端ssh_server_fork.c

在libSSH中没有任何设置channel的回调函数的代码,只要在服务端中,由开发者手动设置,比如上面的545行的代码

然后我们又看到了sdata.authenticated,该变量再之前说了,该漏洞绕过的认证,只能把session设置为认证状态,却无法修改SSH服务端开发者定义的sdata.authenticated变量,所以该循环将不会跳出,直到n = 100的情况下,reutrn结束该函数。这就导致了我们无法getshell。

如果想getshell,有两种修改方式:

1.删除sdata.authenticated变量

2.把channel添加回调函数的代码移到循环之前

在修改了服务端代码后,我也能成功getshell:

总结

之后我看了审计了一下ssh_execute_server_request函数的其他分支,发现SSH_REQUEST_CHANNEL分支下所有的分支:

都是调用channel的回调函数,所以在回调函数未注册的情况下,是无法成功getshell。

最后得出结论,CVE-2018-10933并没有想象中的危害大,而且网上说的几千个使用libssh的ssh目标,根据banner,我觉得都是libssh官方Demo中的ssh服务端,存在漏洞的版本的确可以绕过认证,但是却无法getshell。

引用

  1. https://0x48.pw/libssh/
  2. https://www.anquanke.com/post/id/162225
  3. https://github.com/search?utf8=%E2%9C%93&q=CVE-2018-10933&type=
  4. https://www.libssh.org/files/0.7/libssh-0.7.5.tar.xz
  5. https://0x48.pw/libssh/libssh_0.7.6/src/packet.c.html#ssh_packet_process
  6. https://0x48.pw/libssh/libssh_0.7.5/src/packet.c.html#default_packet_handlers

Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/720/