libSSH 認證繞過漏洞(CVE-2018-10933)分析

作者: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: 缺啥依賴自己裝,沒有當初的編譯記錄了,也懶得再來一遍

$ 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 [email protected]

PS: 用戶那隨便填,我使用myuser,只是爲了對比正常認證請求和bypass請求有啥區別,正常情況下SSH服務端是使用賬戶密碼認證,賬戶是: myuser, 密碼是: mypassword

修改 ../src/auth.c ssh_userauth_xxxx 函數,我修改的是 ssh_userauth_password :

根據360的分析文章和我自己的研究結果,修改了上圖箭頭所示的三處地方,這樣 ./examples/samplessh 就會成爲了驗證該漏洞的PoC

PS: 修改完源碼後記得再執行一次make

漏洞分析

根據服務端輸出的調試信息,可以找到 ssh_packet_process 函數 [5] , 看到第1211行:

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

然後追蹤到 callbacks 數組等於 default_packet_handlers [6]

正常情況下,發送 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
......
    struct ssh_server_callbacks_struct server_cb = {
        .userdata = &sdata,
        .auth_password_function = auth_password,
        .channel_open_request_session_function = channel_open,
    };
    ssh_callbacks_init(&server_cb);
    ssh_callbacks_init(&channel_cb);
    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:
  session->session_state = SSH_SESSION_STATE_AUTHENTICATED;
  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

研究不能getshell之謎

很多人復現該漏洞的時候肯定都發現了,服務端調試的信息都輸出了認證成功,但是在getshell的時候卻一直無法成功,根據上面的代碼,發現session已經被設置成認證成功了,但是爲啥還無法獲取shell權限呢?對此,我又繼續深入研究。

根據服務端的調試信息,我發現都能成功打開 channel ,但是在下一步 pty-req channel_request 我服務端顯示的信息是被拒絕:

所以我繼續跟蹤代碼執行的流程,跟蹤到了 ssh_execute_server_request 函數:

        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

    ssh_set_auth_methods(session, SSH_AUTH_METHOD_PASSWORD);
    ssh_event_add_session(event, session);
    n = 0;
    while (sdata.authenticated == 0 || sdata.channel == NULL) {
        /* If the user has used up all attempts, or if he hasn't been able to
         * authenticate in 10 seconds (n * 100ms), disconnect. */
        if (sdata.auth_attempts >= 3 || n >= 100) {
            return;
        }
        if (ssh_event_dopoll(event, 100) == SSH_ERROR) {
            fprintf(stderr, "%s\n", ssh_get_error(session));
            return;
        }
        n++;
    }
    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 變量

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

2.把channel添加回調函數的代碼移到循環之前

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

在修改了服務端代碼後,我也能成功getshell:

總結

之後我看了審計了一下 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

Paper 本文由 Seebug Paper 發佈,如需轉載請註明來源。本文地址: https://paper.seebug.org/720/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章