libSSH 認證繞過漏洞(CVE-2018-10933)分析
作者:Hcamael@知道創宇404實驗室
時間:2018年10月19日
最近出了一個libSSH認證繞過漏洞,剛開始時候看的感覺這洞可能挺厲害的,然後很快github上面就有PoC了,msf上很快也添加了exp,但是在使用的過程中發現無法getshell,對此,我進行了深入的分析研究。
前言
搞了0.7.5和0.7.6兩個版本的原始碼:
360發了一篇分析文章,有getshell的圖:
環境
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 [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
函式, 看到第1211行:
1121r=cb->callbacks[type - cb->start](session,type,session->in_buffer,cb->user);
然後追蹤到 callbacks
陣列等於 default_packet_handlers
正常情況下,傳送 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 ...... 514struct ssh_server_callbacks_struct server_cb = { 515.userdata = &sdata, 516.auth_password_function = auth_password, 517.channel_open_request_session_function = channel_open, 518}; 519ssh_callbacks_init(&server_cb); 520ssh_callbacks_init(&channel_cb); 521ssh_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: 994session->session_state = SSH_SESSION_STATE_AUTHENTICATED; 995session->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
函式:
166case 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
:
530ssh_set_auth_methods(session, SSH_AUTH_METHOD_PASSWORD); 531ssh_event_add_session(event, session); 532n = 0; 533while (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. */ 536if (sdata.auth_attempts >= 3 || n >= 100) { 537return; 538} 539if (ssh_event_dopoll(event, 100) == SSH_ERROR) { 540fprintf(stderr, "%s\n", ssh_get_error(session)); 541return; 542} 543n++; 544} 545ssh_set_channel_callbacks(sdata.channel, &channel_cb);
在libSSH中沒有任何設定channel的回撥函式的程式碼,只要在服務端中,由開發者手動設定,比如上面的545行的程式碼
然後我們又看到了 sdata.authenticated
,該變數再之前說了,該漏洞繞過的認證,只能把session設定為認證狀態,卻無法修改SSH服務端開發者定義的 sdata.authenticated
變數,所以該迴圈將不會跳出,直到 n = 100
的情況下,reutrn結束該函式。這就導致了我們無法getshell。
如果想getshell,有兩種修改方式:
1.刪除 sdata.authenticated
變數
533while (sdata.channel == NULL) { ...... 544}
2.把channel添加回調函式的程式碼移到迴圈之前
530ssh_set_auth_methods(session, SSH_AUTH_METHOD_PASSWORD); 531ssh_event_add_session(event, session); 532ssh_set_channel_callbacks(sdata.channel, &channel_cb); 533n = 0; 534while (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。
引用
- ofollow,noindex" target="_blank">https://0x48.pw/libssh/
- https://www.anquanke.com/post/id/162225
- https://github.com/search?utf8=%E2%9C%93&q=CVE-2018-10933&type=
- https://www.libssh.org/files/0.7/libssh-0.7.5.tar.xz
- https://0x48.pw/libssh/libssh_0.7.6/src/packet.c.html#ssh_packet_process
- https://0x48.pw/libssh/libssh_0.7.5/src/packet.c.html#default_packet_handlers