对 libssh2 整数溢出漏洞 (CVE-2019-17498)的分析
2019-11-07 10:13:40 Author: www.4hou.com(查看原文) 阅读量:103 收藏

0x01 漏洞挖掘

在2019年3月18日,Canonical Ltd.的Chris Coulson披露了libssh2中的九个漏洞(CVE-2019-3855CVE-2019-3863)。这些漏洞已在libssh2 v1.8.1中修复。当时,我的同事Pavel Avgustinov注意到,修复漏洞的报告在LGTM上引入了多个新告警。这些告警是由于像下面的代码:

 if((p_len = _libssh2_get_c_string(&buf, &p)) < 0)

问题出现在_libssh2_get_c_string返回 -1是一个error code,但是p_len没有符号,因此错误条件将被忽略。libssh2团队已经在后面的的报告中修复了这些问题,但是它促使我们仔细查看代码以查看其包含明显错误的漏洞。我们很快发现了这个用于边界检查的函数:

 int _libssh2_check_length(struct string_buf *buf, size_t len)
 {
     return ((int)(buf->dataptr - buf->data) <= (int)(buf->len - len)) ? 1 : 0;
 }

此函数的问题在于强制转换int可能会溢出。左侧的强制转换是安全的,因为的字段buf是受信任的值,但是右侧的强制转换是不安全的,因为的值len是不受信任的。创建漏洞利用exp并非难事,该漏洞利用使值len大于绕过此溢出检查buf->len + 0x80000000。可以在GitHub上找到PoC 。

我后来了解到,该问题_libssh2_check_length是在1.8.2版发布后在主要开发分支上引入的,因此漏洞范围检查在1.8.2版中不存在。不幸的是,版本1.8.2不包含任何边界检查,因此PoC仍然有效。在1.8.2版中,漏洞的源位置是kex.c:1675。问题在于其中p_len包含一个不受信任的值,因此后续的读取s可能会超出范围。因为_libssh2_check_length在1.8.2版中不存在,所以不需要将值p_len大于0x80000000触发漏洞。这意味着较小的值len可以触发越界读取,该漏洞更有可能被利用来实现远程信息泄露。

0x02  negative error codes 转换为 unsigned

当我和我的同事正在查看漏洞补丁报告时,我们注意到一种常见的错误模式,其中将negative error返回值强制转换为unsigned。这是一个非常容易犯的错误,并且它没有被编译器警告所](https://godbolt.org/z/CvqwDm)捕获。所以我写了这个简单的查询来查找漏洞实例:

 import cpp
 import semmle.code.cpp.dataflow.DataFlow
 import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis
 
 from Function f, FunctionCall call, ReturnStmt ret, DataFlow::Node source, DataFlow::Node sink
 where call.getTarget() = f
 and ret.getEnclosingFunction() = f
 and ret.getExpr().getValue().toInt() < 0
 and source.asExpr() = call
 and DataFlow::localFlow(source, sink)
 and sink.asExpr().getFullyConverted().getType().getUnderlyingType().(IntegralType).isUnsigned()
 and lowerBound(sink.asExpr()) < 0
 select sink

查询代码如下

 r_len = _libssh2_get_c_string(&buf, &r);
 if(r_len <= 0)
     return -1;

该查询会返回负整数常量的函数。例如,_libssh2_get_c_string在773行上执行此操作,然后,它将查找对该函数的调用,这些调用返回值并将其转换为无符号类型。

0x03 触发漏洞

漏洞的源位置是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);

越界读取通常只会导致分段错误,但是LIBSSH2_DISCONNECT在 第499行的调用中也有可能导致其他类型的问题:

 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)。

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

0x04 libssh2中整数溢出的变体分析

我在2019年6月底之前写了关于libssh2的最后一篇博客文章。为了准确了解该博客文章的技术细节,我需要回过头来再看一下libssh2代码。我注意到许多粗心的边界检查代码,例如上述错误。

变体分析是关于漏洞的所有变体,并在可能的情况下,创建可在多个代码库之间重用的查询。但是我的目标集中在libssh2和我发现的漏洞上。

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

1. 一个漏洞的PoC。

2. 一个QL查询,它标识了我认为存在漏洞的的所有代码位置。

QL查询和PoC有几个好处:

1. 如果代码中包含几个非常相似的错误,那么我可以编写一个列举所有漏洞的查询。

2. 该查询使我能够轻松地检查漏洞是否已修复(在90天的最后期限即将到期时,这非常方便)。

3. 我可以将QL查询及其结果列表作为单个URL包括在内,这对我来说很方便,希望对接收者也很方便。

编写PoC通常需要大量工作,因此,如果存在多个非常相似的漏洞,那么我通常只为其中一个编写PoC。我的感觉是,一个PoC足以证明安全影响是真实的。下面的查询针对此用例进行了调整,该查询的目的不是在libssh2中找到所有整数溢出漏洞,而且由于查询中编码了某些libssh2特定的细节,它也不会扩展到其他代码库。

 /**
  * @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"

该查询与我的同事FermínSerna针对U-Boot NFS漏洞编写的查询非常相似。该isSource可以找出_libssh2_ntohu32和_libssh2_ntohu64,用于网络主机字节顺序转换。它查找包含可能会溢出的子表达式的比较操作。例如,message_len < datalen-13是一个比较表达式,其中子表达式datalen-13可能会溢出。我的查询还覆盖了可选isAdditionalFlowStep,我对该谓词进行了调整,以生成精确的近似变体漏洞列表。

0x05  缓解措施

这不是openssh中的漏洞,因此它不会影响ssh(https://linux.die.net/man/1/ssh)。 libssh2是客户端C库,使应用程序可以连接到SSH服务器。其次,这不是libssh的漏洞,后者是一个不相关的C库,提供与libssh2类似的功能。

漏洞存在于libssh2 1.8.2和更早版本中,已在libssh2版本1.9.0中得到修复。除了升级到1.9.0版之外,没有任何缓解此漏洞的方法。

该漏洞是越界读取,可能导致远程信息泄露。使用libssh2连接到恶意SSH服务器时将触发该事件。溢出发生在Diffie Hellman密钥交换期间,可以在完成身份验证之前在连接过程的早期触发漏洞。libssh2接收uint32_t来自恶意服务器的请求,并且对此没有任何限制。然后,libssh2从所指定的偏移量中读取uint32_t内存。我写了一个漏洞PoC,其中恶意的SSH服务器返回非常大的偏移值,这会导致libssh2因分段错误而崩溃。但是,我认为,更仔细选择的偏移量可能会导致信息泄露,因为读取的内存随后会返回给服务器。可利用性将取决于堆栈布局,由于libssh2只是一个库,因此将根据使用该库的应用程序而有所不同。


文章来源: https://www.4hou.com/info/news/21143.html
如有侵权请联系:admin#unsafe.sh