SSH⽤户枚举漏洞(CVE-2018-15473)原理学习
⼀、漏洞简介
1、漏洞编号和类型
CVE-2018-15473 SSH ⽤户名(USERNAME)暴⼒枚举漏洞
2、漏洞影响范围
OpenSSH 7.7及其以前版本
3、漏洞利⽤⽅式
由于SSH本⾝的认证机制存在缺陷,导致攻击者可以使⽤字典,暴⼒枚举SSH存在的⽤户名(Username)
4、漏洞修复⽅式
升级openssh
⼆、漏洞原理及其利⽤分析
1、漏洞原理
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原理
try:
transport.auth_publickey(username, ate(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
ansport.auth_timeout is not None:
max_ts = time.time() + ansport.auth_timeout
while True:
event.wait(0.1)
#ansport.is_active()
if ansport.is_active():
e = _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 = _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
ansport.auth_timeout is not None:
max_ts = time.time() + ansport.auth_timeout
while True:
event.wait(0.1)
#ansport.is_active()
if ansport.is_active():
e = _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 = _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:
cve漏洞库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
"""
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论