zoukankan      html  css  js  c++  java
  • SSH用户枚举漏洞(CVE-2018-15473)原理学习

    一、漏洞简介



    1、漏洞编号和类型


    CVE-2018-15473 SSH 用户名(USERNAME)暴力枚举漏洞

    2、漏洞影响范围


    OpenSSH 7.7及其以前版本

    3、漏洞利用方式


    由于SSH本身的认证机制存在缺陷,导致攻击者可以使用字典,暴力枚举SSH存在的用户名(Username)

    4、漏洞修复方式


    升级openssh

    二、漏洞原理及其利用分析



    1、漏洞原理


    参考国外文献:http://www.openwall.com/lists/oss-security/2018/08/15/5
    观察下列openssh代码

      87 static int
      88 userauth_pubkey(struct ssh *ssh)
      89 {
     ...
     101         if (!authctxt->valid) {
     102                 debug2("%s: disabled because of invalid user", __func__);
     103                 return 0;
     104         }
     105         if ((r = sshpkt_get_u8(ssh, &have_sig)) != 0 ||
     106             (r = sshpkt_get_cstring(ssh, &pkalg, NULL)) != 0 ||
     107             (r = sshpkt_get_string(ssh, &pkblob, &blen)) != 0)
     108                 fatal("%s: parse request failed: %s", __func__, ssh_err(r));
    

    可以看出来,当用户不可用时,连接userauth_pubkey会直接返回,如果用户可用,则会进入下一个条件判断,调用fatal函数。所以在username可用于不可用两种情况下,可以看出来这个函数的返回是不同的

    2、PoC原理


    PoC地址:https://github.com/Rhynorater/CVE-2018-15473-Exploit
    可以看下这段代码,这就是判断username是否可用的原理

    try:
            transport.auth_publickey(username, paramiko.RSAKey.generate(1024))
        except BadUsername:
                return (username, False)
        except paramiko.ssh_exception.AuthenticationException:
                return (username, True)
    

    由此可见,一切就在paramiko这个库的transport.auth_publickey这个函数中

    def auth_publickey(self, username, key, event=None):
            """
            Authenticate to the server using a private key.  The key is used to
            sign data from the server, so it must include the private part.
    
            If an ``event`` is passed in, this method will return immediately, and
            the event will be triggered once authentication succeeds or fails.  On
            success, `is_authenticated` will return ``True``.  On failure, you may
            use `get_exception` to get more detailed error information.
    
            Since 1.1, if no event is passed, this method will block until the
            authentication succeeds or fails.  On failure, an exception is raised.
            Otherwise, the method simply returns.
    
            If the server requires multi-step authentication (which is very rare),
            this method will return a list of auth types permissible for the next
            step.  Otherwise, in the normal case, an empty list is returned.
    
            :param str username: the username to authenticate as
            :param .PKey key: the private key to authenticate with
            :param .threading.Event event:
                an event to trigger when the authentication attempt is complete
                (whether it was successful or not)
            :return:
                list of auth types permissible for the next stage of
                authentication (normally empty)
    
            :raises:
                `.BadAuthenticationType` -- if public-key authentication isn't
                allowed by the server for this user (and no event was passed in)
            :raises:
                `.AuthenticationException` -- if the authentication failed (and no
                event was passed in)
            :raises: `.SSHException` -- if there was a network error
            """
            if (not self.active) or (not self.initial_kex_done):
                # we should never try to authenticate unless we're on a secure link
                raise SSHException('No existing session')
            if event is None:
                my_event = threading.Event()
            else:
                my_event = event
            self.auth_handler = AuthHandler(self)
            self.auth_handler.auth_publickey(username, key, my_event)
            if event is not None:
                # caller wants to wait for event themselves
                return []
            return self.auth_handler.wait_for_response(my_event)
    

    根据PoC的代码,在username可用时,auth_publickey的函数会抛出异常,但是抛出的类型AuthenticationException,通过阅读这个函数的代码,返现只有self.auth_handler = AuthHandler(self)、self.auth_handler.auth_publickey(username, key, my_event), return self.auth_handler.wait_for_response(my_event)三条语句有可能会抛出这个异常,运行PoC测试发现,在最后一句话中抛出了异常(测试方法很简单的点灯法,节点前后print信息即可判断),跟踪进入这个函数wait_for_response。

    def wait_for_response(self, event):
            max_ts = None
            if self.transport.auth_timeout is not None:
                max_ts = time.time() + self.transport.auth_timeout
            while True:
                event.wait(0.1)
                #print self.transport.is_active()
                if not self.transport.is_active():
                    e = self.transport.get_exception()
                    #print "e:",e
                    if (e is None) or issubclass(e.__class__, EOFError):
                        e = AuthenticationException('Authentication failed.')
                    raise e
                if event.is_set():
                    break
                if max_ts is not None and max_ts <= time.time():
                    raise AuthenticationException('Authentication timeout.')
    
            if not self.is_authenticated():
                e = self.transport.get_exception()
                if e is None:
                    e = AuthenticationException('Authentication failed.')
                # this is horrible.  Python Exception isn't yet descended from
                # object, so type(e) won't work. :(
                if issubclass(e.__class__, PartialAuthentication):
                    return e.allowed_types
                raise e
            return []
    

    这里有三个点可以抛出Authentication异常,经过修改打印信息获取到,异常抛出在下面的这个地方。

        if (e is None) or issubclass(e.__class__, EOFError):
                        e = AuthenticationException('Authentication failed.')
                    raise e
    

    当用户不可用时,也是在这个点位抛出异常,但是没有进上文那个判断,所以e应该不是None,也不是EOFerror, 我们屏蔽掉BadUsername,回归到最近本的Python异常,发现返回的是这个Authencation Failed2异常,

    def wait_for_response(self, event):
            max_ts = None
            if self.transport.auth_timeout is not None:
                max_ts = time.time() + self.transport.auth_timeout
            while True:
                event.wait(0.1)
                #print self.transport.is_active()
                if not self.transport.is_active():
                    e = self.transport.get_exception()
                    #print "e:",e
                    if (e is None) or issubclass(e.__class__, EOFError):
                        e = AuthenticationException('Authentication failed1.')
                    raise e
                if event.is_set():
                    break
                if max_ts is not None and max_ts <= time.time():
                    raise AuthenticationException('Authentication timeout.')
    
            if not self.is_authenticated():
                e = self.transport.get_exception()
                if e is None:
                    e = AuthenticationException('Authentication failed2.')
                # this is horrible.  Python Exception isn't yet descended from
                # object, so type(e) won't work. :(
                if issubclass(e.__class__, PartialAuthentication):
                    return e.allowed_types
                raise e
            return []
    

    彻掉后event事件被置位了,可以看出,而且根据下文中auth_publickey函数的注释部分可以看到的确如此,当身份验证完成时触发事件,根据漏洞原来描述,当username不可用时,openssh的函数就返回了,身份验证完成,触发了事件,因而跳出了while循环,又因为身份验证失败,所以进入了下一个判断,exception的对象e是None,于是就成了一个新的AuthenticationException异常。而username可用时,并没有完成身份认证,event没有触发,所以在while循环中因为认证失败跑出了异常。当然PoC中定义自定义异常,来区别这两个异常,从而做到判断,但是从程序过程中可以看到两个地方的的确在网络通信上是有差别的。

    """
            Authenticate to the server using a private key.  The key is used to
            sign data from the server, so it must include the private part.
    
            If an ``event`` is passed in, this method will return immediately, and
            the event will be triggered once authentication succeeds or fails.  On
            success, `is_authenticated` will return ``True``.  On failure, you may
            use `get_exception` to get more detailed error information.
    
            Since 1.1, if no event is passed, this method will block until the
            authentication succeeds or fails.  On failure, an exception is raised.
            Otherwise, the method simply returns.
    
            If the server requires multi-step authentication (which is very rare),
            this method will return a list of auth types permissible for the next
            step.  Otherwise, in the normal case, an empty list is returned.
    
            :param str username: the username to authenticate as
            :param .PKey key: the private key to authenticate with
            :param .threading.Event event:
                an event to trigger when the authentication attempt is complete
                (whether it was successful or not)
            :return:
                list of auth types permissible for the next stage of
                authentication (normally empty)
    
            :raises:
                `.BadAuthenticationType` -- if public-key authentication isn't
                allowed by the server for this user (and no event was passed in)
            :raises:
                `.AuthenticationException` -- if the authentication failed (and no
                event was passed in)
            :raises: `.SSHException` -- if there was a network error
            """
    博主简介:博主国内安全行业目前最强大的网络安全公司做技术研究员,常年做技术工作。 获得过以下全国竞赛大奖: 《中国电子作品大赛一等奖》 《云计算技术大赛一等奖》 《AIIA人工智能大赛优胜奖》《网络安全知识竞赛一等奖》 《高新技术个人突出贡献奖》,并参与《虚拟化技术-**保密**》一书编写,现已出版。还拥有多项专利,多项软件著作权! 且学习状态上进,立志做技术牛逼的人。座右铭:在路上,永远年轻,永远热泪盈眶。可邮件联系博主共同进步,个人邮箱:pigeon_code@163.com
  • 相关阅读:
    python设计模式-单例模式
    bash脚本条件测试总结
    Python网络编程:IO多路复用
    Python面向对象高级编程:@property--把方法变为属性
    Sql Server存储过程基本语法
    接口幂等性
    [转载]使用消息队列实现分布式事务-公认较为理想的分布式事务解决方案
    C#分布式事务解决方案-TransactionScope
    SOA架构和微服务架构的区别
    Atlas实现数据库读写分离
  • 原文地址:https://www.cnblogs.com/mutudou/p/14652852.html
Copyright © 2011-2022 走看看