Hacking Jenkins Part 2 - 滥用元编程实现未授权RCE!
这是Orange :)
2019年2月19日 星期二
漏洞分析
首先我们从Jenkins Pipeline开始解释CVE-2019-1003000!人们选择Jenkins的主要原因之一是它提供了强大的Pipeline功能,这使得编写软件构建、测试和交付脚本变得更加容易!你可以将Pipeline想象成一种操作Jenkins的强大语言(实际上,Pipeline是用Groovy构建的DSL)。
为了检查用户提供脚本的语法是否正确,Jenkins为开发者提供了一个接口!想想看,如果你是开发者,你会如何实现这个语法检查功能?你可以自己编写AST(抽象语法树)解析器,但这太困难了。所以最简单的方法是重用现有功能和库!
如前所述,Pipeline只是用Groovy构建的DSL,因此Pipeline必须遵循Groovy语法!如果Groovy解析器能够处理Pipeline脚本而不出错,那么语法一定是正确的!以下代码片段展示了Jenkins如何验证Pipeline:
|
|
这里Jenkins使用GroovyClassLoader.parseClass(…)方法验证Pipeline!需要注意的是,这只是一个AST解析。没有运行execute()方法,任何危险的调用都不会被执行!如果你尝试解析以下Groovy脚本,什么都不会发生:
|
|
从开发者的角度来看,Pipeline可以控制Jenkins,因此它必须是危险的,需要在每次Pipeline调用前进行严格的权限检查!然而,这只是一个简单的语法验证,所以这里的权限检查比平常要少。没有任何execute()方法,它只是一个AST解析器,必须是安全的!这是我第一次看到这个验证时的想法。然而,在写技术博客时,元编程(Meta-Programming)闪现在我的脑海中!
什么是元编程
元编程是一种编程概念!元编程的思想是为程序员提供一个抽象层,以不同的方式考虑程序,使程序更加灵活和高效!元编程没有明确的定义。一般来说,程序自身处理程序以及编写操作其他程序(编译器、解释器或预处理器等)的程序都是元编程!这里的哲学非常深刻,甚至可以成为编程语言的一个大主题!
如果仍然难以理解,你可以将eval(…)视为另一种元编程,它让你动态操作程序。虽然这有点不准确,但对于理解来说仍然是一个很好的比喻!在软件工程中,也有很多与元编程相关的技术。例如:
- C宏
- C++模板
- Java注解
- Ruby(Ruby是一个对元编程友好的语言,甚至有相关书籍)
- DSL(领域特定语言,如Sinatra和Gradle)
当我们谈论元编程时,我们根据范围将其分为(1)编译时和(2)运行时元编程。今天,我们关注编译时元编程!
如何利用?
从上一节我们知道Jenkins通过parseClass(…)验证Pipeline,并了解到元编程可以在编译时干扰解析器!编译(或解析)是一项艰巨的工作,有很多复杂的事情和隐藏的功能。所以,问题是,有没有我们可以利用的副作用?
有许多简单的案例证明元编程可以使程序变得脆弱,比如C语言中的宏扩展:
|
|
或者编译器资源炸弹(用仅18字节制作16GB的ELF):
|
|
或者通过编译器计算斐波那契数:
|
|
从编译后的二进制文件的汇编语言中,我们可以确认结果是在编译时计算的,而不是运行时!
|
|
更多例子,你可以参考StackOverflow上的文章《构建编译器炸弹》!
第一次尝试
回到我们的利用,Pipeline只是用Groovy构建的DSL,而Groovy也是一个对元编程友好的语言。我们开始阅读Groovy官方元编程手册,寻找一些利用方法。在2.1.9节,我们找到了@groovy.transform.ASTTest注解。以下是它的描述:
@ASTTest是一个特殊的AST转换,旨在帮助调试其他AST转换或Groovy编译器本身。它将让开发者在编译期间"探索"AST,并对AST执行断言,而不是对编译结果进行断言。这意味着这个AST转换在字节码生成之前提供对AST的访问。@ASTTest可以放在任何可注解的节点上,并需要两个参数:
什么!对AST执行断言?这不正是我们想要的吗?让我们先在本地环境写一个简单的概念验证:
|
|
酷,它工作了!然而,在远程Jenkins上复现时,它显示:
|
|
什么鬼!!!出了什么问题?
经过一点挖掘,我们找到了根本原因。这是由于Pipeline Shared Groovy Libraries Plugin引起的!为了在Pipeline中重用函数,Jenkins提供了将自定义库导入Pipeline的功能!Jenkins会在每个执行的Pipeline之前加载这个库。结果,问题变成了在编译时classPath中缺少相应的库。这就是为什么会出现unable to resolve class错误!
如何修复这个问题?很简单!只需转到Jenkins插件管理器并删除Pipeline Shared Groovy Libraries Plugin!它可以解决问题,然后我们就可以毫无错误地执行任意代码!但是,这不是一个好的解决方案,因为这个插件是随Pipeline一起安装的。要求管理员删除插件以执行代码是很蹩脚的!我们停止挖掘这个,尝试寻找另一种方法!
第二次尝试
我们继续阅读Groovy元编程手册,发现了另一个有趣的注解 - @Grab。手册上没有关于@Grab的详细信息。然而,我们在搜索引擎上找到了另一篇文章 - 《用Grape进行依赖管理》!
哦,从文章中我们了解到Grape是Groovy中内置的JAR依赖管理!它可以帮助程序员导入不在classPath中的库。用法如下:
|
|
通过使用@Grab注解,它可以在编译时自动导入不在classPath中的JAR文件!如果你只是想通过有效凭据和Pipeline执行权限绕过Pipeline沙箱,那就足够了。你可以按照@adamyordan提供的PoC执行任意命令!
然而,没有有效凭据和execute()方法,这只是一个AST解析器,你甚至无法控制远程服务器上的文件。那么,我们能做什么?通过深入研究@Grab,我们发现了另一个有趣的注解 - @GrabResolver:
|
|
如果你足够聪明,你会想将root参数改为恶意网站!让我们在本地环境中尝试:
|
|
|
|
哇,它工作了!现在,我们相信我们可以通过Grape让Jenkins导入任何恶意库!然而,下一个问题是,如何获得代码执行?
代码执行之路
在利用中,目标总是将读取原语或写入原语升级为代码执行!从上一节中,我们可以通过Grape将恶意JAR文件写入远程Jenkins服务器。然而,下一个问题是如何执行代码?
通过深入研究Groovy上的Grape实现,我们意识到库获取是由类groovy.grape.GrapeIvy完成的!我们开始寻找有没有任何方法可以利用,并注意到了一个有趣的方法processOtherServices(…)!
|
|
JAR文件只是ZIP格式的子集。在processOtherServices(…)中,如果有一些指定的入口点,Grape会注册服务。其中,Runner引起了我的兴趣。通过查看processRunners(…)的实现,我们发现了这个:
|
|
这里我们看到了newInstance()。这是否意味着我们可以在任何类上调用构造函数?是的,所以,我们可以创建一个恶意JAR文件,并将类名放入文件META-INF/services/org.codehaus.groovy.plugins.Runners中,我们就可以调用构造函数并执行任意代码!
以下是完整的利用代码:
|
|
|
|
PoC:
|
|
结语
通过这个利用,我们可以获得远程Jenkins服务器的完全访问权限!我们使用元编程在编译时导入恶意JAR文件,并通过Runner服务执行任意代码!尽管Jenkins上有内置的Groovy沙箱(脚本安全插件)来保护Pipeline,但它是无用的,因为漏洞在编译时,而不是运行时!
因为这是Groovy核心上的攻击向量,所有与Groovy解析器相关的方法都会受到影响!它打破了开发者的思维,认为没有执行就没有问题。这也是一个需要计算机科学知识的攻击向量。否则,你无法想到元编程!这就是使这个漏洞有趣的原因。除了我报告的doCheckScriptCompile(…)和toJson(…)入口点之外,在漏洞修复后,Mikhail Egorov也快速找到了另一个触发此漏洞的入口点!
除此之外,这个漏洞还可以与我之前在Hacking Jenkins Part 1中的利用链结合,绕过Overall/Read限制,实现应得的预认证远程代码执行。如果你完全理解了文章,你就知道如何链式利用:P
感谢阅读这篇文章,希望你喜欢!这是Hacking Jenkins系列的结束,我将来会发布更多有趣的研究:)
2019/07/02 更新
我的"Hacking Jenkins"幻灯片用于#pts19和#HITBAMS已发布!这是我第一次去法国,Pass the SALT(@passthesaltcon)是一个非常非常好的有趣会议!https://t.co/S4VUf3k1Zq — Orange Tsai 🍊 (@orange_8361) 2019年7月3日
2019/05/10 更新
“自2017年5月以来,Jenkins中没有预认证RCE,但这个是!” 发布一个更可靠和优雅的利用 - “awesome-jenkins-rce-2019"来自我的#HITB2019AMS演讲。感谢@0ang3el和@webpentest加入这个派对!https://t.co/qQCY2RYDa8 pic.twitter.com/sW0S7bctGT — Orange Tsai 🍊 (@orange_8361) 2019年5月10日
2019/02/22 更新
一些使利用更可靠的提示!
- 首先检查你的Java(JAR)版本!
- 有超过3个入口点可以触发此漏洞!
- 有时,
Grab
失败但ASTTest
完美工作!https://t.co/NcTFyNIcHR — Orange Tsai (@orange_8361) 2019年2月22日