对 libssh2 整数溢出漏洞 (CVE-2019-17498)的分析 - 嘶吼 RoarTalk – 网络安全行业综合服务平台,4hou.com

对 libssh2 整数溢出漏洞 (CVE-2019-17498)的分析

h1apwn 新闻 2019-11-07 09:13:40
295778
收藏

导语:该问题_libssh2_check_length是在1.8.2版发布后在主要开发分支上引入的,因此漏洞范围检查在1.8.2版中不存在。

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只是一个库,因此将根据使用该库的应用程序而有所不同。

  • 分享至
取消

感谢您的支持,我会继续努力的!

扫码支持

打开微信扫一扫后点击右上角即可分享哟

发表评论

 
本站4hou.com,所使用的字体和图片文字等素材部分来源于原作者或互联网共享平台。如使用任何字体和图片文字有侵犯其版权所有方的,嘶吼将配合联系原作者核实,并做出删除处理。
©2022 北京嘶吼文化传媒有限公司 京ICP备16063439号-1 本站由 提供云计算服务