您当前的位置: 飞鹏网>游戏攻略>【互联网攻防日记】NFS苹果攻击 孙子兵法之李代桃僵

【互联网攻防日记】NFS苹果攻击 孙子兵法之李代桃僵

本文由飞鹏网小编发表于2019-09-10 14:32:08

评论

【*飞鹏网不保证所有的技术人员都有高尚的道德水平,但是通过传播普及互联网安全知识让更多的人熟悉并可以运用黑客知识,才能推进社会互联网水平的强制进步。我们的初衷是好的,就是手段有点残忍。通过大量培养泛黑客,尤其是中文黑客,让令人头痛的漏洞问题无所遁形。如果有最强的矛,那么一定会产生最强的盾。】

高手在民间,虽然个体恶意利用这个漏洞也是可以的,然是只要是有良心的技术人员都希望互联网整体在高速发展路上走得更远。

顺便说一句,这个漏洞在2018年10月30日已经修复。

所以本文仅剩余学习研究的价值。

控制或者阻止服务

利用NSF不需要特殊的权限,在macos中,因此任何级别的用户都可以触发这些漏洞,甚至是不需要密码的访客账户。此外许多计算机(尤其是企业环境中的)会配置为在启动时自动挂载NFS共享。

共享之门就是黑客对你电脑的入侵钥匙。

  1. 可能被用于在使用NFS文件管理器的企业中快速传播病毒。

    第一种办法是抓包,90%的远程黑客都需要通过拦截或者复制信息流来植入病毒。

  2. 用于提权。

    局域网或者本地黑客常用,只要能用Guest身份登录,就可能在计算机上获取内核级别的访问权限。

苹果公司为这五个漏洞分配了CVE分别是:CVE-2018-4259,CVE-2018-4286,CVE-2018-4287,CVE-2018-4288和CVE-2018-4291。我在2018年5月21日发给苹果的漏洞报告中,分别在源码中列出了14条不确定的点。但由于苹果最近才发布了更新,所以我也没有来得及完成对全部源代码的审核。因此,为避免意外泄露任何可能未修复的错误,本文中我只谈及其中两个已经得到验证和修复的漏洞。
我编写了一个PoC去验证漏洞的可用性,可以使用0覆盖4096个字节的堆内存从而导致内核崩溃。我做了一个简短的视频来证明这一点。
4096是一个随机选择,事实上我可以随意修改来发送尽可能多的数据,任何大于128字节的数都会触发缓冲区溢出,我也能够完全控制写入的字节值。因此,尽管这些动作只破坏了内核,但是实际上是可以通过这些缓冲区溢出来实现远程代码执行以及本地提权的操作。
在我第一次发现这个漏洞时,几乎不敢想象我会为了PoC去自己编写NFS服务器。但是在我学了一些NFS相关知识以及了解到如何使用rpcgen之后,我就发现其实想要实现也非常简单。用来验证这个漏洞的PoC,仅包含46行C语言以及63行 RPC语言代码。当然,这些源代码会在苹果官方完成全部修复之后再放出。
在我写的PoC中,这两个漏洞都需要通过这一行看似无害的代码来实现:
nfsm_chain_get_fh(error, &nmrep, nfsvers, fh);
这行代码的作用是读取NFS服务器发送回Mac的回复消息(nmrep)中的文件句柄(fh)。这个文件句柄是NFS共享的文件或目录中的不透明标识符。NFSv3中的文件句柄最多64个字节,NFSv4中最多128个字节,XNU中的fhandle_t类型则有足够的空间容纳128字节的文件句柄,但是他们却忽略了去检查nfsm_chain_get_fh宏中的缓冲区溢出情况:
/* get the size of and data for a file handle in an mbuf chain */#define nfsm_chain_get_fh(E, NMC, VERS, FHP) \ do { \ if ((VERS) != NFS_VER2) \ nfsm_chain_get_32((E), (NMC), (FHP)->fh_len); \ else \ (FHP)->fh_len = NFSX_V2FH;\ nfsm_chain_get_opaque((E), (NMC), (uint32_t)(FHP)->fh_len, (FHP)->fh_data);\ if (E) \ (FHP)->fh_len = 0;\ } while (0)
由于宏命令的大量使用,想理解这段代码可能有些难,但它的实际作用却非常简单:它能够从消息中读取一个32为无符号整数到(FHP)->fh_len,然后读取该字节数,从消息直接进入(FHP)->fh_data。由于没有边界检查,因此攻击者可以选择任何字节序列覆盖任意数量的内核堆。被覆盖的文件句柄在内存中的nfs_socket.c:1401中分配。
这个PoC中,第二个bug是nfsm_chain_get_opaque中的整数溢出:
/* copy the next consecutive bytes of opaque data from an mbuf chain */#define nfsm_chain_get_opaque(E, NMC, LEN, PTR) \ do { \ uint32_t rndlen; \ if (E) break; \ rndlen = nfsm_rndup(LEN); \ if ((NMC)->nmc_left >= rndlen) { \ u_char *__tmpptr = (u_char*)(NMC)->nmc_ptr; \ (NMC)->nmc_left -= rndlen; \ (NMC)->nmc_ptr += rndlen; \ bcopy(__tmpptr, (PTR), (LEN)); \ } else { \ (E) = nfsm_chain_get_opaque_f((NMC), (LEN), (u_char*)(PTR)); \ } \ } while (0)
这段代码使用bfsn_rndup将LEN移动至4的下一个倍数处。但它在调用bcopy时会使用LEN的原始值。如果其初值为0xFFFFFFFF,则nfsm_rndup中将出现加法溢出,renlen的值为0,这意味着能够与(NMC)->nmc_left比较成功,并且使用0xFFFFFFFF作为size参数调用bcopy。这便会导致内核崩溃,因此它被用作拒绝服务攻击。
QL的一大优势是能够找到已知错误的变种。今年早些时候,我的同事Jonas Jensen在苹果的NFS启动中发现了两个漏洞:CVE-2018-4136和CVE-2018-4160。我们当时也发布了一篇关于这些漏洞的文章,主要就是针对对bcopy的调用,这个调用可能存在为负的用户控制的大小参数。最简单的方法就是查找用户控制源缓冲区中对bcopy的调用。这很有趣,因为它们可以将用户的数据复制到内核中。
/** * @name bcopy of network data * @description Copying a variable-sized network buffer into kernel memory * @kind path-problem * @problem.severity warning * @id apple-xnu/cpp/bcopy-negative-size */import cppimport semmle.code.cpp.dataflow.DataFlowimport semmle.code.cpp.rangeanalysis.SimpleRangeAnalysisimport DataFlow::PathGraphclass MyCfg extends DataFlow::Configuration { MyCfg() { this = "MyCfg" } override predicate isSink(DataFlow::Node sink) { exists (FunctionCall call | sink.asExpr() = call.getArgument(1) and call.getTarget().getName() = "__builtin___memmove_chk" and not call.getArgument(2).isConstant()) } override predicate isSource(DataFlow::Node source) { source.asExpr().(FunctionCall).getTarget().getName() = "mbuf_data" }}from DataFlow::PathNode sink, DataFlow::PathNode source, MyCfg dataFlowwhere dataFlow.hasFlowPath(source, sink)select sink, source, sink, "bcopy of network data"
上面这条查询相当简单,因为它查找的是对bcopy的任何调用,该调用过程是将数据从mbuf复制到内核中。只要正确检查size参数的边界,这样的调用就没有错误。然而事实证明,很大一部分使用nfsm_chain_get_fh的情况中,不会进行任何边界检查。因此,尽管查询方式很简单,但却很有效,发现了很多重要的漏洞。
实现边界检查的常用方法,一般是:
if (n < limit) { bcopy(src, dst, n);}
我又写了一段来对上面这步进行检测:
/** * Holds if `guard` is a bounds check which ensures that `size` is less than * `limit`. For example: * * if (size < limit) { * ... size ... * } */predicate guardedSize(GuardCondition guard, Expr size, Expr limit, RelationStrictness strict) { exists (boolean branch, Expr sz, BasicBlock block | guard.controls(block, branch) and block.contains(size) and globalValueNumber(size) = globalValueNumber(sz) and relOpWithSwapAndNegate(guard, sz, limit, Lesser(), strict, branch))}
这段代码使用Guards库来查找在guard控制的控制流位置中使用的大小表达式,然后使用globalValueNumber库来检查条件本身是否出现相同大小的表达式。GlobalValueNumbering库可以提供预测功能,以检测有效短语的权重:
if (packet.data.size < limit) { ... packet.data.size ...}
最后,它使用名为relOpWithSwapAndNegate的程序来检查size表达式是否小于限制:
if (packet.data.size >= limit) { return -1;} else { ... packet.data.size ...}
当然,有时候也可以通过另一种方式实现边界检查,例如调用min:
/** * Holds if `size` is bounds checked with a call to `min`: * * size = min(n, limit); * * ... size ... */predicate minSize(Expr size) { exists (DataFlow::Node source, DataFlow::Node sink | DataFlow::localFlow(source, sink) and source.asExpr().(FunctionCall).getTarget().getName() = "min" and size = sink.asExpr())}
我简单的组合了一下:
/** * Holds if `size` has been bounds checked. */predicate checkedSize(Expr size) { lowerBound(size) >= 0 and (guardedSize(_, size, _, _) or minSize(size))}
注意一点,我使用了lowerBound来确保不出现负整数溢出的情况。需要做的就是在isSink方法中使用checkedSize,以减少误报的数量。语句如下:
/** * @name bcopy of network data * @description Copying a variable-sized network buffer into kernel memory * @kind path-problem * @problem.severity warning * @id apple-xnu/cpp/bcopy-negative-size */import cppimport semmle.code.cpp.valuenumbering.GlobalValueNumberingimport semmle.code.cpp.controlflow.Guardsimport semmle.code.cpp.dataflow.DataFlowimport semmle.code.cpp.dataflow.TaintTrackingimport semmle.code.cpp.rangeanalysis.RangeAnalysisUtilsimport semmle.code.cpp.rangeanalysis.SimpleRangeAnalysisimport DataFlow::PathGraph/** * Holds if `guard` is a bounds check which ensures that `size` is less than * `limit`. For example: * * if (size < limit) { * ... size ... * } */predicate guardedSize(GuardCondition guard, Expr size, Expr limit, RelationStrictness strict) { exists (boolean branch, Expr sz, BasicBlock block | guard.controls(block, branch) and block.contains(size) and globalValueNumber(size) = globalValueNumber(sz) and relOpWithSwapAndNegate(guard, sz, limit, Lesser(), strict, branch))}/** * Holds if `size` is bounds checked with a call to `min`: * * size = min(n, limit); * * ... size ... */predicate minSize(Expr size) { exists (DataFlow::Node source, DataFlow::Node sink | DataFlow::localFlow(source, sink) and source.asExpr().(FunctionCall).getTarget().getName() = "min" and size = sink.asExpr())}/** * Holds if `size` has been bounds checked. */predicate checkedSize(Expr size) { lowerBound(size) >= 0 and (guardedSize(_, size, _, _) or minSize(size))}class MyCfg extends DataFlow::Configuration { MyCfg() { this = "MyCfg" } override predicate isSink(DataFlow::Node sink) { exists (FunctionCall call | sink.asExpr() = call.getArgument(1) and call.getTarget().getName() = "__builtin___memmove_chk" and not checkedSize(call.getArgument(2))) } override predicate isSource(DataFlow::Node source) { source.asExpr().(FunctionCall).getTarget().getName() = "mbuf_data" }}from DataFlow::PathNode sink, DataFlow::PathNode source, MyCfg dataFlowwhere dataFlow.hasFlowPath(source, sink)select sink, source, sink, "bcopy of network data"
上一篇:开源的更加安全是对还是错,linux对比windows系统
下一篇:【互联网攻防日记】WebLogic防御T3协议漏洞

Copyright © 2010-2022 All Rights Reserved

鄂ICP备2022004099号-3