另一个libssh2整数溢出(CVE-2019-17498)

严重程度和缓解

不要惊慌!这不是openssh中的漏洞,因此它不会影响我们大家每天使用的ssh。 libssh2是客户端的C库,使应用程序可以连接到SSH服务器。这也不libssh中的漏洞,它是一个不相关的C库,提供与libssh2类似的功能。

漏洞存在于libssh2 1.9.0及更低版本中。在撰写本文时,该错误已在master修复,但尚未发布包含该修复程序的新正式版本。

该漏洞是越界读取,可能导致拒绝服务或远程信息泄露。当使用libssh2连接到恶意SSH服务器时将触发该漏洞。SSH服务器发送断开消息时会发生溢出,这意味着可以在身份验证完成之前在连接过程的早期触发漏洞。

触发漏洞

漏洞的源位置是packet.c:480

if(message_len < datalen-13) {

datalen的值不受信任,因为它是由远程SSH服务器控制的。例如如果使用datalen == 11,则减法将溢出并且针对message_len的越界检查无效。message_len是一个32位无符号整数,它也由远程SSH服务器控制,所以这可能导致在第485行上读取越界:

language_len =
    _libssh2_ntohu32(data + 9 + message_len);

越界读取通常只会导致分段错误,但是这次的问题也可能会导致调用在 第499行LIBSSH2_DISCONNECT

if(session->ssh_msg_disconnect) {
    LIBSSH2_DISCONNECT(session, reason, message,
                       message_len, language, language_len);
}

它取决于libssh2库的使用方式,因为这session->ssh_msg_disconnect是一个回调函数,默认情况下为null,但可以由该库的用户设置(通过调用libssh2_session_callback_set)。

这里编写了一个 概念验证漏洞 ,其中恶意SSH服务器使用datalen == 11和返回断开连接消息message_len == 0x41414141,这导致libssh2因分段错误而崩溃。

libssh2中整数溢出的变体分析

Kevin Backhouse向供应商报告安全漏洞时,通常会尝试在报告中包括两点:

  1. 一个错误的概念证明利用。
  2. 一个QL查询,它标识了我认为应该固定的所有代码位置。

PoC包括QL查询和有几个好处:

  1. 如果代码中包含几个非常相似的错误,那么我可以编写一个列举所有错误的查询。
  2. 该查询使我能够轻松地检查错误是否已修复(在90天的最后期限即将到期时,这非常方便)。
  3. 我可以将QL查询及其结果列表作为单个URL包括在内,这对我来说很方便,希望对接收者也很方便。

创建PoC通常需要大量工作,因此,如果目标存在多个非常相似的错误,那么Kevin Backhouse通常只为其中一个编写PoC。Kevin Backhouse的感觉是,一个PoC足以证明安全影响是真实的,前提是其他变体显然非常相似。下面的查询针对此用例进行了调整。该查询的目的不是在libssh2中找到所有整数溢出漏洞,而且由于查询中编码了某些libssh2特定的细节,它也不会扩展到其他代码库。(跨多个代码库扩展通常是变体分析的主要目标之一。)相反,其目标是找到PoC触发的错误以及其他紧密变体。

/**
 * @kind path-problem
 */

import cpp
import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis
import semmle.code.cpp.dataflow.TaintTracking
import DataFlow::PathGraph

class Config extends DataFlow::Configuration {
  Config() { this = "_libssh2_ntohl bounds check overflow" }

  override predicate isSource(DataFlow::Node source) {
    source.asExpr().(FunctionCall).getTarget().getName().matches("_libssh2_ntoh%")
  }

  override predicate isSink(DataFlow::Node sink) {
    convertedExprMightOverflowNegatively(sink.asExpr()) and
    exists(RelationalOperation cmp | cmp.getAnOperand() = sink.asExpr())
  }

  override predicate isAdditionalFlowStep(DataFlow::Node source,
                                          DataFlow::Node target) {
    exists(Field f |
      source.asExpr() = f.getAnAssignedValue() and
      target.asExpr() = f.getAnAccess())
    or
    target.asExpr().(AddExpr).getAnOperand() = source.asExpr()
    or
    target.asExpr().(SubExpr).getAnOperand() = source.asExpr()
  }
}

from Config cfg, DataFlow::PathNode source, DataFlow::PathNode sink
where cfg.hasFlowPath(source, sink)
select sink, source, sink,
  "possible integer overflow of tainted expression in bounds check"

该查询与Kevin Backhouse的同事FermínSerna针对U-Boot NFS漏洞编写的查询非常相似。该isSource谓词找出将呼叫_libssh2_ntohu32_libssh2_ntohu64,这是用于网络主机字节顺序转换。这些功能通常是“攻击者控制的数据”的出色代理。但是我的isSink断言与费米的断言完全不同。它查找包含可能会溢出的子表达式的比较操作。例如,message_len < datalen-13是一个比较表达式,其中子表达式datalen-13可能会溢出。我的查询还覆盖了可选isAdditionalFlowStep谓词,以自定义数据流边缘集。我对该谓词进行了调整,以生成精确的近似变体列表。

LGTM的一个不错的功能是它永久保存查询结果。 此链接 显示了在2019年7月1日运行查询的结果。您可以在此处查看 2019年10月10日运行相同查询的结果。在2019年7月1日报告该错误时向libssh2团队发送了第一个链接。此后,大部分结果已修复,但是您可以从第二个链接中看到一些新结果也已出现。尚未调查任何新结果是否可利用。

时间线

  • 2019年7月1日:我向libssh2团队秘密披露了该漏洞。
  • 2019年7月2日:libssh2团队发送了初步答复。
  • 2019年8月30日:libssh2团队修复了其开发分支中的错误
  • 2019年10月10日:我通知libssh2团队我们90天的披露截止日期已到期,并询问他们何时计划发布新版本。
  • 2019年10月11日:分配了CVE-2019-17498
  • 2019年10月15日:我通过在GitHub上发布我的PoC公开披露CVE-2019-17498 。

 

*编译整理:Domino

*参考来源:semmle