0x00 前言
知识补漏行动继续,这次是jenkins。
0x01 环境搭建
搭环境最惨的就是踩了一堆坑之后发现前边做的都是没用的。。。
1 | git clone https://github.com/jenkinsci/jenkins.git |
接下来在idea配置远程调试:
注意以调试模式运行时,只有接收到idea的调试链接之后才会继续运行。
调试模式启动的jenkins没有任何插件,需要手工安装,可以将vulhub环境中的/var/jenkins_home/plugins
复制到本地的jenkins/war/work/plugins
。也没有默认的登陆认证,也可以用vulhub中的配置文件。
另外要注意的一点是,jenkins的默认配置中,匿名用户的读权限也是默认关闭的:
0x02 漏洞分析
路由解析
jenkins/war/src/main/webapp/WEB-INF/web.xml
从web.xml可以看到jenkins将所有的请求发送到org.kohsuke.stapler.Stapler
这个servlet进行统一处理,跟进看一下org/kohsuke/stapler/stapler/1.254.1/stapler-1.254.1-sources.jar!/org/kohsuke/stapler/Stapler.java
的service
方法:
在处理一些特殊情况之后,会创建一个根结点(这里暂时理解为jenkins对路由的处理是将其转化为一个一个的节点),进入invoke
方法:
org/kohsuke/stapler/stapler/1.254.1/stapler-1.254.1-sources.jar!/org/kohsuke/stapler/Stapler.java
在这里将url按照/
进行分割,并进一步封装request和response对象。
org/kohsuke/stapler/stapler/1.254.1/stapler-1.254.1-sources.jar!/org/kohsuke/stapler/TokenList.java
最后请求会进入tryInvoke
方法中进行处理:
org/kohsuke/stapler/stapler/1.254.1/stapler-1.254.1-sources.jar!/org/kohsuke/stapler/Stapler.java
如果节点的类型为StaplerProxy
,会检查用户是否有权限访问该路由:
jenkins/core/src/main/java/jenkins/model/Jenkins.java
在第一次权限检查失败之后,会有一个二次检查:
如果我们访问的url在白名单中,是可以通过权限检测的。
回到tryInvoke
方法:
根据node获取metaClass,在这个过程中有一个buildDispatchers
方法,它的主要作用就是寻找对应的node节点与相应的处理方法(继承家族树中的所有类)并把这个方法加入到分配器dispatchers中。
由于这个方法特别长,总结起来就是遍历一个方法列表,如果有符合如下条件的dispatcher,就添加到metaClass.dispatchers
中:
.do(...)
也就是do(...)
和@WebMethod
标注的方法.doIndex(...)
js
也就是js(...)
- 有
@JavaScriptMethod
标注的方法NODE.getTOKEN()
也就是get()
NODE.getTOKEN(StaplerRequest)
也就是get(StaplerRequest)
.get(String)
也就是get(String)
.get(int)
也就是get(int)
.get(long)
也就是get(long)
.getDynamic(,...)
也就是getDynamic()
.doDynamic(...)
也就是doDynamic()
然后遍历metaClass的所有dispatcher,找到符合路由的dispatcher进行调用:
继续跟进:
org/kohsuke/stapler/stapler/1.254.1/stapler-1.254.1-sources.jar!/org/kohsuke/stapler/MetaClass.java
这里首先调用了getSecurityRealm
方法,返回的结果作为新的node,再次进入tryInvoke
方法进行递归解析,直到url解析完毕。
到这里路由解析的流程就梳理清楚了,如果能够找到token对应的方法,就调用,找不到就作为上一个方法的参数:
1 | http://jenkin.local/adjuncts/whatever/class/classLoader/resource/index.jsp/content |
1 | jenkins.model.Jenkins.getAdjuncts("whatever") |
这个漏洞利用的核心点就在于通过寻找getter方法,构成一条利用链,直到rce。
寻找利用链
payload:
1 | http://127.0.0.1:8080/jenkins/securityRealm/user/admin/descriptorByName/org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SecureGroovyScript/checkScript?sandbox=true&value=import+groovy.transform.*%0a%40ASTTest(value%3d%7bassert+java.lang.Runtime.getRuntime().exec(%22open+%2fApplications%2fCalculator.app%22)%7d)%0aclass+Person%7b%7d |
从前面的分析中我们知道路由/securityRealm
是在白名单中的,任何权限都可以访问:
解析之后会得到一个HudsonPrivateSecurityRealm
类型的对象:
jenkins/core/src/main/java/jenkins/model/Jenkins.java
查看对应的dispatchers,发现能够接收用户输入的只有:
同样的方法我们会得到一个hudson.model.User
的对象,到这里看起来还没有利用漏洞的点,继续找接收用户输入的dispatcher:
jenkins/core/src/main/java/hudson/model/DescriptorByNameOwner.java
一路跟进来到jenkins/core/src/main/java/jenkins/model/Jenkins.java
:
这里会获取当前jenkins安装的所有插件,如果与传入参数匹配,则返回。这里选择了org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SecureGroovyScript
这个插件,希望通过执行groovy脚本来达到rce的目的。
org/jenkins-ci/plugins/script-security/1.13/script-security-1.13-sources.jar!/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SecureGroovyScript.java
调用栈如下:
下面就是在我看来这个洞最nb的地方,通常我们认为恶意代码只有在运行的时候才能造成危害,但是编程语言为了让自身更加灵活,会赋予自身许多动态特性,比如java中的字节码,是在编译的时候才将字节码插入指定的类中。我们能否寻找一些在编译时就执行命令的groovy script呢?答案当然是肯定的。
在orange大佬提出这个思路之后,许许多多的大佬贡献了非常多的利用方式,在这篇Jenkins RCE漏洞分析汇总中有详细的记录,就不再copy了XD。
0x03 参考链接
[Jenkins RCE分析(CVE-2018-1000861分析)](https://lucifaer.com/2019/03/04/Jenkins RCE分析(CVE-2018-1000861分析))
Hacking Jenkins Part 1 - Play with Dynamic Routing
Hacking Jenkins Part 2 - Abusing Meta Programming for Unauthenticated RCE!