0.前言本文是一个系列的日常学习记录,web安全php漏洞。对象的序列化和反序列化不再详述。php中序列化的结果是一种php自定义的字符串格式,有点类似于json。我们需要用任何语言设计对象的序列化和反序列化解决几个问题序列化一个对象后,序列化的结果有自描述的功能(从序列化结果知道对象的具体类型,知道类型是不够的,的当然,你还需要知道这个类型对应的具体类型值)。序列化时的权限控制,可以自定义序列化字段等,比如用golang做就很方便了。时间性能问题:在一些对性能敏感的场景下,对象序列化不能延迟,例如:高性能服务(我经常使用protobuf进行序列化)。空间性能问题:序列化后的结果不能太长,比如内存中的一个int对象。序列化后数据长度变为int长度的10倍。那么这个序列算法是有问题的。本文只是从php代码的角度来讲解php中的序列化和反序列化过程。请记住,序列化和反序列化操作只是对象数据,具有面向对象开发经验的应该很容易理解。1.序列化serialize和反序列化方法unserializephp原生提供对象序列化功能,不像c++...^_^。使用起来也很简单,就两个接口。classfobnn{public$hack_id;私人$hack_name;公共函数__construct($name,$id){$this->hack_name=$name;$this->hack_id=$id;}publicfunctionprint(){echo$this->hack_name.PHP_EOL;}}$obj=newfobnn('fobnn',1);$obj->print();$serializedstr=serialize($obj);//通过serialize接口进行序列化echo$serializedstr.PHP_EOL;;$toobj=unserialize($serializedstr);//通过unserialize反序列化$toobj->print();fobnnO:5:"fobnn":2:{s:7:"hack_id";i:1;s:16:"fobnnhack_name";s:5:"fobnn";}fobnn看到第二行了该字符串的输出是序列化的结果。这个结构其实很好理解。可以发现是通过对象名/成员名来映射的。当然,不同访问权限的成员序列化后的标签名称略有不同。根据我上面提到的3个问题,那么我们可以看一下1.自描述函数O:5:"fobnn":2其中o表示对象类型,类型名称为fobnn,采用这种格式,则后面的2表示有2个成员对象。关于成员对象,其实是同一套子描述,是递归定义。自描述功能主要是通过字符串记录对象和成员的名称来实现的。2.性能问题php序列序列化的时间性能本文不做分析,详见下文,但序列化结果其实类似于json/bson定义的协议,有一个协议头,协议header表示类型,protocolbody表示类型对应的值,不会对序列化结果进行压缩。2、反序列化中的魔术方法,对应的就是上面提到的第二个问题。其实PHP中是有解决办法的,一是通过魔术方法,二是自定义序列化函数。先介绍一下魔术方法__sleep和__wakeuphttp://php.net/manual/en/lang...http://php.net/manual/en/lang...classfobnn{public$hack_id;私人$hack_name;公共函数__construct($name,$id){$this->hack_name=$name;$this->hack_id=$id;}publicfunctionprint(){echo$this->hack_name.PHP_EOL;}publicfunction__sleep(){returnarray("hack_name");}publicfunction__wakeup(){$this->hack_name='ha哈';}}$obj=newfobnn('fobnn',1);$obj->print();$serializedstr=serialize($obj);echo$serializedstr.PHP_EOL;;$toobj=unserialize($serializedstr);$toobj->print();fobnnO:5:"fobnn":1:{s:16:"fobnnhack_name";s:5:"fobnn";}哈哈在序列化之前会调用__sleep,返回的是一个成员名数组需要序列化的,这样我们就可以控制需要序列化的数据。案例中我只返回了hack_name,可以看到结果中只序列化了hack_name成员。序列化完成后,将被跳过。__wakeup这里我们可以做一些后续工作,比如重新连接数据库。3、CustomSerializable接口自定义序列化接口http://php.net/manual/en/clas...interfaceSerializable{abstractpublicstringserialize(void)abstractpublicvoidunserialize(string$serialized)}通过这个接口,我们可以自定义序列化和反序列化的行为。这个函数主要可以用来自定义我们的序列化格式。fobnn类实现Serializable{public$hack_id;私人$hack_name;公共函数__construct($name,$id){$this->hack_name=$name;$this->hack_id=$id;}publicfunctionprint(){echo$this->hack_name.PHP_EOL;}publicfunction__sleep(){returnarray('hack_name');}公共功能__wakeup(){$this->hack_name='haha';}publicfunctionserialize(){returnjson_encode(array('id'=>$this->hack_id,'name'=>$this->hack_name));}publicfunctionunserialize($var){$array=json_decode($var,true);$this->hack_name=$array['name'];$this->hack_id=$array['id'];}}$obj=newfobnn('fobnn',1);$obj->print();$serializedstr=serialize($obj);echo$serializedstr.PHP_EOL;;$toobj=unserialize($serializedstr);$toobj->print();fobnnC:5:"fobnn":23:{{"id":1,"name":"fobnn"}}fobnn使用自定义序列化接口后,我们的魔术方法就没用了。4、PHP动态类型与PHP反序列化既然上面说了自描述函数,在序列化结果中保存了对象的类型,而php是动态类型语言,那么我们可以做一个简单的实验。classfobnn{public$hack_id;公共$hack_name;公共函数__construct($name,$id){$this->hack_name=$name;$this->hack_id=$id;}publicfunctionprint(){var_dump($this->hack_name);}}$对象=新的fobnn('fobnn',1);$obj->print();$serializedstr=serialize($obj);echo$serializedstr.PHP_EOL;;$toobj=unserialize($serializedstr);$toobj->print();$toobj2=unserialize("O:5:\"fobnn\":2:{s:7:\"hack_id\";i:1;s:9:\"hack_name\";i:12345;}");$toobj2->打印();我们修改hack_name的反序列化结果为int类型,i:12345string(5)"fobnn"O:5:"fobnn":2:{s:7:"hack_id";i:1;s:9:"hack_name";s:5:"fobnn";}string(5)"fobnn"int(12345)可以查到,对象序列化成功返回??!并且可以正常工作!当然php这种机制提供了灵活多变的语法,但同时也引入了安全隐患。后续继续分析php的序列化反序列化特性带来的安全问题。终于完结了。。。如有不足之处,请指点,或留言或联系fobcrackgp@163.com。本文为独行原创文章,首发于大题小作。永久链接:PHP反序列化漏洞系列--PHP序列化反序列化原理https://www.ifobnn.com/phpserialize.html
