本月19号和21号看了一下CVE-2018-10933 libssh的authentication bypass这个漏洞,记录一下整个过程。

0x01 漏洞环境

libssh的环境手工安装过程如下。

# keys for samplesshd-cb
ssh-keygen -t rsa
ssh-keygen -t dsa

# dependencies
apt-get install cmake libssl-dev
apt-get install libkrb5-dev # for samplesshd-cb see in examples/CMakeLists.txt

# download
wget  https://www.libssh.org/files/0.7/libssh-0.7.4.tar.xz
tar xvf libssh-0.7.4.tar.xz
cd libssh-0.7.4

# compile
mkdir build && cd build
cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Debug ..
make && make install
cd examples/
./samplesshd-cb --dsakey /root/.ssh/id_dsa --rsakey /root/.ssh/id_rsa 127.0.0.1 -p 22 -v

环境安装的过程中,有一个坑踩了一下,如果没有samplesshd-cb(libssh 官方给的example中的demo程序)多半是没有安装libkrb5-dev,具体可以参考example中CMakeLists.txt脚本。

image-20181021161338405

0x02 漏洞验证

git clone https://github.com/blacknbunny/libSSH-Authentication-Bypass.git
pip install -r requirement.txt
python libsshauthbypass.py  --host 172.17.0.4 -p22

用samplesshd-cb验证时,可以看到Authentication成功了,但是却不能getshell。输出:

image-20181018161234474

0x03 漏洞分析

为什么只能打开channel,不能getshell?exploit repo中提到了这个example有明确的auth handler。

People trying to reproduce libssh bug: the sample code (samplesshd-cb) is not vuln because it has explicit auth handlers. You can open a channel but nothing will happen.

https://github.com/blacknbunny/libSSH-Authentication-Bypass/issues/7

到这里还是不太明白原因,得看代码了。这时候发现网上已经有了分析文章[7]。整理关键部分如下:

漏洞的本质原因是攻击者可以构造SSH2_MSG_USERAUTH_SUCCESS的请求,从而可以让libssh的session直接被设置为session->auth_state=SSH_AUTH_STATE_SUCCESS;

libssh中正常的验证过程如下:

  1. 发送SSH2_MSG_USERAUTH_REQUEST请求,进入的是ssh_packet_userauth_request函数
  2. 调用session->server_callbacks->auth_password_function函数指针,指向用户基于libssh自己实现的auth函数
  3. 用户自己实现的函数(比如examples/ssh_server_fork.c)中auth_password实现验证,验证成功会修改session_data_struct *sdata中的sdata->authenticated = 1;。这样验证成功。

poc中是构造SSH2_MSG_USERAUTH_SUCCESS的请求,libssh中的执行流如下:

  1. 直接进入ssh_packet_userauth_success函数,修改了session->auth_state=SSH_AUTH_STATE_SUCCESS;
  2. 由于不会进入auth_password中,因此sdata->authenticated没有修改。

修改了session->auth_state=SSH_AUTH_STATE_SUCCESS;有用吗?有用。session验证过了之后,会和服务端打开channel,但是在ssh_server_fork实现时,还校验了sdata.authenticated ,因此不能getshell。

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);//getshell

vulhub[8]上有了环境,测试了可以rce。进到docker中看一下是用的ssh_server_fork这个程序。

[email protected]:/usr/src/build/examples# lsof -i:22
COMMAND   PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
ssh_serve   9 root    3u  IPv4  84938      0t0  TCP *:ssh (LISTEN)
[email protected]:/usr/src/build/examples# ps -aux |grep 9
root         9  0.0  0.1  20548  2796 ?        SL   05:20   0:00 /usr/src/build/examples/ssh_server_fork --hostkey=/etc/ssh/ssh_host_rsa_key --ecdsakey=/etc/ssh/ssh_host_ecdsa_key --dsakey=/etc/ssh/ssh_host_dsa_key --rsakey=/etc/ssh/ssh_host_rsa_key -p 22 0.0.0.0
root        19  0.0  0.1  18188  3240 pts/0    Ss   07:25   0:00 /bin/bash
root        94  0.0  0.1  36636  2892 pts/0    R+   08:20   0:00 ps -aux
root        95  0.0  0.0  11112   976 pts/0    S+   08:20   0:00 grep 9

根目录下告诉了我们漏洞环境的patch,是ssh_server_fork这个程序,也是通过删除sdata.authenticated == 0 来做的。

[email protected]:/# cat ssh_server_fork.patch
--- ssh_server_fork.c	2018-08-10 17:06:03.000000000 +0800
+++ ssh_server_fork_patch.c	2018-10-19 13:44:07.000000000 +0800
@@ -531,7 +531,7 @@
     ssh_event_add_session(event, session);

     n = 0;
-    while (sdata.authenticated == 0 || sdata.channel == NULL) {
+    while (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) {

0x04 总结

这个漏洞的本质上是由于libssh实现的问题导致了authentication bypass,可以通过发送数据包来修改本次session->auth_state变量。

如果要getshell还是要看具体基于libssh的程序实现,比如基于libssh官方demo中ssh_server_fork还校验了sdata.authenticated变量,在没有注册handler的情况是无法getshell的,因此漏洞危害没有那么大。其实github也用到了libssh,但是官方Github Security twitter上也说了,他们有自己的实现,事实上也是不受影响的。

We use a custom version of libssh; SSH2_MSG_USERAUTH_SUCCESS with libssh server is not relied upon for pubkey-based auth, which is what we use the library for. Patches have been applied out of an abundance of caution, but GHE was never vulnerable to CVE-2018-10933.

REF

  1. https://www.seebug.org/vuldb/ssvid-97614
  2. libssh repo https://github.com/simonsj/libssh/commits/master
  3. poc0 https://gist.github.com/mlosapio/2062ebf943485a7289d226e0d00498e7
  4. poc1 https://github.com/SoledaD208/CVE-2018-10933
  5. poc2 https://github.com/blacknbunny/libSSH-Authentication-Bypass/
  6. 360 cert analysis https://cert.360.cn/report/detail?id=a407dddd655dba34405688b1498c3aa1
  7. hcamael analysis https://0x48.pw/2018/10/19/0x49/#jump3
  8. Vulhub https://github.com/vulhub/vulhub/tree/master/libssh/CVE-2018-10933
  9. zoomeye https://zoomeye.org/searchResult?q=”SSH-2.0-libssh”
  10. mygist https://gist.github.com/thinkycx/6ec27dc470de03fb16c2f447dbbbd070