师傅们的教学

https://blog.csdn.net/solitudi/article/details/113588692
https://spaceman-911.gitee.io/2021/06/30/PHP-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%EF%BC%88%E8%B6%85%E7%BB%86%E7%9A%84%EF%BC%89/

写在开头可能用到的提醒

  • 序列化的字符串,如:O:11,可写成O:+11,类似于S:4也可
  • 绕过__wakeup(),属性超过原定义属性个数
  • 限定版本:PHP5-5.6.25 | PHP7-7.0.10
    绕过__wakeup
  • 十六进制绕过,将序列化的字符串小写的s改为大写S,里面的字符串可用十六进制代替
  • 例如 s:"name" 可改为 S:"n\97me"
  • 字符串序列化还是字符串本身,例如:serialize(test),那么输出还是test
  • 当__serialize和__sleep方法同时存在,序列化时忽略__sleep方法而执行__serialize;当__unserialize方法和__wakeup方法同时存在,反序列化时忽略__wakeup方法而执行__unserialize。

魔术方法

  • __construct(),在对象被new的时候自动调用
  • __destruct(),当对象被销毁的时候会自动调用
  • __wakeup(),当执行反序列化的时候执行也就是unserialize()
  • __sleep(),当执行序列化的时候执行也就是serialize()
  • __invoke(),当对象被当成函数调用时触发,例如$a()
  • __call() 当从对象中调用不存在的方法时触发,例如a对象中没有b方法,触发
  • __get(),在对象中调用不存在的变量时候触发
  • __set(),在对象中写入不可写入的的属性时触发
  • __isset(),在不可访问的私有属性调用iset()或empty()时候触发
  • __unset,在不可访问的私有属性上使用unset()时触发
  • __tostring(),把类当成字符串来调用时候触发,或者把对象当成字符串去输出
  • __sleep(),当被serialize时候,如果有__sleep,会优先触发
    属性

案例

经典案例

include('flag.php');

class ctfShowUser{
    public $username='xxxxxx';
    public $password='xxxxxx';
    public $isVip=false;

    public function checkVip(){
        return $this->isVip;
    }
    public function login($u,$p){
        return $this->username===$u&&$this->password===$p;
    }
    public function vipOneKeyGetFlag(){
        if($this->isVip){
            global $flag;
            echo "your flag is ".$flag;
        }else{
            echo "no vip, no flag";
        }
    }
}

$username=$_GET['username'];
$password=$_GET['password'];

if(isset($username) && isset($password)){
    $user = unserialize($_COOKIE['user']);    
    if($user->login($username,$password)){
        if($user->checkVip()){
            $user->vipOneKeyGetFlag();
        }
    }else{
        echo "no vip,no flag";
    }
}
  • 方法: $user是反序列化后的类,所以$_cookie['user']要传入序列化的类,根据代码,我们可以把ctfshowuser这个类中的$isvip的布尔值改为true,然后将这个class,放入本地,然后echo urlencode(serialize(new ctfshowuser())),把这个输出值放入cookie。
  • 思路: 因为$user是unserialize后的类,所以所以要把利用的类给序列化即可
  • 序列化不会改变方法中的详细代码,只是可以改变调用什么之类的

    反序列化逃逸

    <?php
    error_reporting(0);
    class message
    {
      public $from;
      public $msg;
      public $to;
      public $token = 'user';
      public function __construct($f, $m, $t)
      {
          $this->from = $f;
          $this->msg = $m;
          $this->to = $t;
      }
    }
    
    $f = $_GET['f'];
    $m = $_GET['m'];
    $t = $_GET['t'];
    
    if (isset($f) && isset($m) && isset($t)) {
      $msg = new message($f, $m, $t);
      $umsg = str_replace('fuck', 'loveU', serialize($msg));
      setcookie('msg', base64_encode($umsg));
      echo 'Your message has been sent';
    }
    
    highlight_file(__FILE__);
    
    
  • 详细原理: https://www.bilibili.com/video/BV1D64y1m78f?p=9&spm_id_from=pageDriver
  • 目标:使public $token='admin'
  • 方法:使用字符串逃逸,因为题目使用了str_replace来替换
  • 过程:因为传入fuck会被替换为loveU,那么序列化的时候,s:4:"fuck"被替换后会变成s:4:"loveU",这样就会不等相差了一个字符。然后在$f传入 fuck(若干个)";s:3:"msg";s:1:"b";s:2:"to";s:1:"c";s:5:"token";s:5:"admin";},这个字符串是被替换后 的序列化字符串截取的,把user换成admin,然后一直添加fuck,然后一直查看序列化后的s:数量:"和这里面的字符串数量一不一样",如果一样了,可以使用var_dump(unserialize(序列化后的字符串)) 看看是不是ok了
  • 正常的序列化(未被过滤): O:7:"message":4:{s:4:"from";s:4:"fuck";s:3:"msg";s:1:"b";s:2:"to";s:1:"c";s:5:"token";s:4:"user";}
  • 过滤后的: O:7:"message":4:{s:4:"from";s:4:"loveU";s:3:"msg";s:1:"b";s:2:"to";s:1:"c";s:5:"token";s:5:"admin";}
  • 例如字符串: 字符串逃逸,system被替换为了ctfshow,差异一个字符,后面用";}闭合

    O:8:"backdoor":2:{s:1:"m";s:168:"ctfshowctfshowctfshowctfshowctfshowctfshowctfshowctfshowctfshowctfshowctfshowctfshowctfshowctfshowctfshowctfshowctfshowctfshowctfshowctfshowctfshowctfshowctfshowctfshow";s:1:"a";s:6:"whoami";}";s:1:"a";s:6:"whoami";}

指针改变变量

include('flag.php');
highlight_file(__FILE__);
class ctfshowAdmin{
    public $token;
    public $password;

    public function __construct($t,$p){
        $this->token=$t;
        $this->password = $p;
    }
    public function login(){
        return $this->token===$this->password;
    }
}

$ctfshow = unserialize($_GET['ctfshow']);
$ctfshow->token=md5(mt_rand());

if($ctfshow->login()){
    echo $flag;
}
  • 解法: 本地试,复制关键代码,然后把$this->password = $p 修改为$this->password = &$this->token,然后$msg=serialize(new ctfshowAdmin('1','2')) echo $msg; 输出即可
  • 思路,代码要求类中的login方法,token和password全等于,把password的值赋值为,&$this->password ,意思就是把password的地址给token,这样值就会想等

Level框架,反序列化链POC

level框架命令执行
level框架5.8版本 写入后门
Thinkphpv5.1 反序列化链 生成的参数要加&lin='shell命令'

最后修改:2022 年 11 月 19 日
如果觉得我的文章对你有用,请随意赞赏