libSSH 认证绕过漏洞(CVE-2018-10933)分析

栏目: 编程工具 · 发布时间: 6年前

内容简介:作者:Hcamael@知道创宇404实验室最近出了一个libSSH认证绕过漏洞,刚开始时候看的感觉这洞可能挺厉害的,然后很快github上面就有PoC了,msf上很快也添加了exp,但是在使用的过程中发现无法getshell,对此,我进行了深入的分析研究。搞了0.7.5和0.7.6两个版本的源码:

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

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

前言

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

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

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

环境

libSSH-0.7.5源码下载地址:

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

$ tar -xf libssh-0.7.5.tar.xz
$ cd libssh-0.7.5
$ mkdir build
$ cd build
$ cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Debug ..
$ make

主要用两个,一个是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 :

libSSH 认证绕过漏洞(CVE-2018-10933)分析

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

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

漏洞分析

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

1121        r=cb->callbacks[type - cb->start](session,type,session->in_buffer,cb->user);

然后追踪到 callbacks 数组等于 default_packet_handlers

libSSH 认证绕过漏洞(CVE-2018-10933)分析

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

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

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

ssh_packet_userauth_request ->
ssh_message_queue -> 
ssh_execute_server_callbacks ->
ssh_execute_server_request ->
rc = session->server_callbacks->auth_password_function(session,
                        msg->auth_request.username, msg->auth_request.password,
                        session->server_callbacks->userdata);

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

// examples/ssh_server_fork.c
......
514    struct ssh_server_callbacks_struct server_cb = {
515        .userdata = &sdata,
516        .auth_password_function = auth_password,
517        .channel_open_request_session_function = channel_open,
518    };
519    ssh_callbacks_init(&server_cb);
520    ssh_callbacks_init(&channel_cb);
521    ssh_set_server_callbacks(session, &server_cb);
......

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

// examples/ssh_server_fork.c

static int auth_password(ssh_session session, const char *user,
                         const char *pass, void *userdata) {
    struct session_data_struct *sdata = (struct session_data_struct *) userdata;
    (void) session;
    if (strcmp(user, USER) == 0 && strcmp(pass, PASS) == 0) {
        sdata->authenticated = 1;
        return SSH_AUTH_SUCCESS;
    }
    sdata->auth_attempts++;
    return SSH_AUTH_DENIED;
}

认证成功后的路径:

ssh_message_auth_reply_success ->
ssh_auth_reply_success:
994  session->session_state = SSH_SESSION_STATE_AUTHENTICATED;
995  session->flags |= SSH_SESSION_FLAG_AUTHENTICATED;

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

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

ssh_packet_userauth_success:
  SSH_LOG(SSH_LOG_DEBUG, "Authentication successful");
  SSH_LOG(SSH_LOG_TRACE, "Received SSH_USERAUTH_SUCCESS");
  session->auth_state=SSH_AUTH_STATE_SUCCESS;
  session->session_state=SSH_SESSION_STATE_AUTHENTICATED;
  session->flags |= SSH_SESSION_FLAG_AUTHENTICATED;

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

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

libSSH 认证绕过漏洞(CVE-2018-10933)分析

研究不能getshell之谜

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

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

libSSH 认证绕过漏洞(CVE-2018-10933)分析

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

166        case SSH_REQUEST_CHANNEL:
            channel = msg->channel_request.channel;
            if (msg->channel_request.type == SSH_CHANNEL_REQUEST_PTY &&
                ssh_callbacks_exists(channel->callbacks, channel_pty_request_function)) {
                rc = channel->callbacks->channel_pty_request_function(session, channel,
                        msg->channel_request.TERM,
                        msg->channel_request.width, msg->channel_request.height,
                        msg->channel_request.pxwidth, msg->channel_request.pxheight,
                        channel->callbacks->userdata);
                if (rc == 0) {
                    ssh_message_channel_request_reply_success(msg);
                } else {
                    ssh_message_reply_default(msg);
                }
                return SSH_OK;

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

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

530    ssh_set_auth_methods(session, SSH_AUTH_METHOD_PASSWORD);
531    ssh_event_add_session(event, session);
532    n = 0;
533    while (sdata.authenticated == 0 || sdata.channel == NULL) {
534        /* If the user has used up all attempts, or if he hasn't been able to
535         * authenticate in 10 seconds (n * 100ms), disconnect. */
536        if (sdata.auth_attempts >= 3 || n >= 100) {
537            return;
538        }
539        if (ssh_event_dopoll(event, 100) == SSH_ERROR) {
540            fprintf(stderr, "%s\n", ssh_get_error(session));
541            return;
542        }
543        n++;
544    }
545    ssh_set_channel_callbacks(sdata.channel, &channel_cb);

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

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

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

1.删除 sdata.authenticated 变量

533    while (sdata.channel == NULL) {
......
544    }

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

530    ssh_set_auth_methods(session, SSH_AUTH_METHOD_PASSWORD);
531    ssh_event_add_session(event, session);
532    ssh_set_channel_callbacks(sdata.channel, &channel_cb);
533    n = 0;
534    while (sdata.authenticated == 0 || sdata.channel == NULL) {
......

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

libSSH 认证绕过漏洞(CVE-2018-10933)分析

总结

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

SSH_CHANNEL_REQUEST_PTY
SSH_CHANNEL_REQUEST_SHELL
SSH_CHANNEL_REQUEST_X11
SSH_CHANNEL_REQUEST_WINDOW_CHANGE
SSH_CHANNEL_REQUEST_EXEC
SSH_CHANNEL_REQUEST_ENV
SSH_CHANNEL_REQUEST_SUBSYSTEM

都是调用 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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Operating System Algorithms

Operating System Algorithms

Nathan Adams、Elisha Chirchir / CreateSpace Independent Publishing Platform / 2017-4-21 / USD 39.15

Operating System Algorithms will walk you through in depth examples of algorithms that you would find in an operating system. Selected algorithms include process and disk scheduling.一起来看看 《Operating System Algorithms》 这本书的介绍吧!

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具