目录
公私有属性
对象变量属性:
public(公共的):在本类内部、外部类、子类都可以访问
protect(受保护的):只有本类或子类或父类中可以访问
private(私人的):只有本类内部可以使用
序列化数据显示:
private属性序列化的时候格式是%00类名%00成员名
protect属性序列化的时候格式是%00*%00成员名
魔术方法
__construct(): //构造函数,当对象new的时候会自动调用
__destruct()://析构函数当对象被销毁时会被自动调用
__wakeup(): //unserialize()时会被自动调用
__invoke(): //当尝试以调用函数的方法调用一个对象时,会被自动调用
__call(): //在对象上下文中调用不可访问的方法时触发
__callStatci(): //在静态上下文中调用不可访问的方法时触发
__get(): //用于从不可访问的属性读取数据
__set(): //用于将数据写入不可访问的属性
__isset(): //在不可访问的属性上调用isset()或empty()触发
__unset(): //在不可访问的属性上使用unset()时触发
__toString(): //把类当作字符串使用时触发
__sleep(): //serialize()函数会检查类中是否存在一个魔术方法__sleep() 如果存在,该方法会被优先调用
详细介绍
公私有属性代码
<?php
header("Content-type: text/html; charset=utf-8");
/*public private protected说明
class test{
public $name="墨竹";
private $age="18";
protected $sex="man";
}
$a=new test();
$a=serialize($a);
print_r($a);
*/
__construct __destruct 魔术方法
class Test{
public $name;
public $age;
public $string;
// __construct:实例化对象时被调用,其作用是拿来初始化一些值。
public function __construct($name, $age, $string){
echo "__construct 初始化"."<br>";
$this->name = $name;
$this->age = $age;
$this->string = $string;
}
// __destruct:当删除一个对象或对象操作终止时被调用。其最主要的作用是拿来做垃圾回收机制。
/*
* 当对象销毁时会调用此方法
* 一是用户主动销毁对象,二是当程序结束时由引擎自动销毁
*/
function __destruct(){
echo "__destruct 类执行完毕"."<br>";
}
}
// 主动销毁
$test = new Test("Spaceman",566, 'Test String');
// 先执行__construct方法,在执行两个echo
unset($test);
echo '第一种执行完毕'.'<br>';
echo '----------------------<br>';
// 程序结束自动销毁,先执行__destruct再执行echo
$test = new Test("Spaceman",566, 'Test String');
echo '第二种执行完毕'.'<br>';
*/
__toString()方法
class Test{
public $variable = 'This is a string';
public function good(){
echo $this->variable . '<br />';
}
// 在对象当做字符串的时候会被调用
public function __toString(){
return '__toString <br>';
}
}
$a = new Test();
$a->good();
//输出调用
echo $a; 在把a这个对象当做字符串输出时会默认执行__toString函数
*/
__CALL 魔术方法
class Test{
public function good($number,$string){
echo '存在good方法'.'<br>';
echo $number.'---------'.$string.'<br>';
}
// 当调用类中不存在的方法时,就会调用__call();
public function __call($method,$args){
echo '不存在'.$method.'方法'.'<br>';
var_dump($args);
}
}
// 正常实例化a对象并访问good方法
$a = new Test();
$a->good(566,'nice');
// 实例化b对象,并调用不存在的ink方法从而触发__call魔术方法
$b = new Test();
$b->ink(899,'no');
*/
__get() 魔术方法
class Test {
public $n=123;
// __get():访问不存在的成员变量时调用
public function __get($name){
echo '__get 不存在成员变量'.$name.'<br>';
}
}
// 实例化对象a,存在成员变量n,可以正常访问
$a = new Test();
echo $a->n; 输出:123
echo '<br>';
// 不存在成员变量bamboo,所以调用__get方法
echo $a->bamboo;
*/
__set()魔术方法
class Test{
public $data = 100;
protect $noway=0;
// __set():设置对象不存在的属性或无法访问(私有)的属性时调用
/* __set($name, $value)
* 用来为私有成员属性设置的值
* 第一个参数为你要为设置值的属性名,第二个参数是要给属性设置的值,没有返回值。
public function __set($name,$value){
echo '__set 不存在成员变量 '.$name.'<br>';
echo '即将设置的值 '.$value."<br>";
$this->noway=$value;
}
public function Get(){
echo $this->noway;
}
}
$a = new Test();
// 读取 noway 的值,初始为0
$a->Get();
echo '<br>';
// 无法访问(私有)noway属性时调用__set魔术方法,在set方法中设置值为899
$a->noway = 899;
$a->Get();
echo '<br>';
// 设置对象不存在的属性spaceman,经过__set方法的设置noway的值为566
$a->spaceman = 566;
$a->Get();
*/
__sleep()
class Test{
public $name;
public $age;
public $string;
// __construct:实例化对象时被调用.其作用是拿来初始化一些值。
public function __construct($name, $age, $string){
echo "__construct 初始化"."<br>";
$this->name = $name;
$this->age = $age;
$this->string = $string;
}
// __sleep() :serialize之前被调用,可以指定要序列化的对象属性
public function __sleep(){
echo "当在类外部使用serialize()时会调用这里的__sleep()方法<br>";
// 例如指定只需要 name 和 age 进行序列化,必须返回一个数值
return array('name', 'age');
}
}
// 在进行实例化时会先调用__construct方法。
$a = new Test("Spaceman",566, 'Test String');
// 在进行序列化时会先调用__sleep方法。
echo serialize($a);
*/
__wakeup
class Test{
public $sex;
public $name;
public $age;
public function __construct($name, $age, $sex){
$this->name = $name;
$this->age = $age;
$this->sex = $sex;
}
public function __wakeup(){
echo "当在类外部使用unserialize()时会调用这里的__wakeup()方法<br>";
$this->age = 566;
}
}
$person = new Test('spaceman',21,'男');
$a = serialize($person);
echo $a."<br>";
// 在进行unserialize反序列化时会先调用__wakeup方法
var_dump (unserialize($a));
*/
__isset()
class Person{
public $sex;
private $name;
private $age;
public function __construct($name, $age, $sex){
$this->name = $name;
$this->age = $age;
$this->sex = $sex;
}
// __isset():当对不可访问属性调用 isset() 或 empty() 时,__isset() 会被调用。
public function __isset($content){
echo "当在类外部使用isset()函数测定私有成员 {$content} 时,自动调用<br>";
return isset($this->$content);
}
}
$person = new Person("spaceman", 25,'男');
// public 成员,可以正常的访问
echo ($person->sex),"<br>";
// private 成员,无法在外访问,所以会调用isset方法
echo isset($person->name);
*/
__unset()
class Person{
public $sex;
private $name;
private $age;
public function __construct($name, $age, $sex){
$this->name = $name;
$this->age = $age;
$this->sex = $sex;
}
// __unset():销毁对象的某个属性时执行此函数
public function __unset($content) {
echo "当在类外部使用unset()函数来删除私有成员时自动调用的<br>";
echo isset($this->$content)."<br>";
}
}
$person = new Person("spaceman", 21,"男"); // 初始赋值
echo "666666<br>";
unset($person->name);//调用__unset 属性私有
unset($person->age);//调用__unset 属性私有
unset($person->sex);//不调用__unset 属性共有
*/
__INVOKE()
class Test{
// _invoke():以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用
public function __invoke($param1, $param2, $param3){
echo "这是一个对象<br>";
var_dump($param1,$param2,$param3);
}
}
$a = new Test();
// 以函数的方法调用一个对象时,会触发__invoke方法
$a('spaceman',21,'男');
*/
?>
CTF-__wakeup()绕过
[极客大挑战 2019]PHP CVE-2016-7124
如果存在__wakeup方法,调用unserilize()方法前则先调用__wakeup方法,
但是序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行
1、下载源码分析,触发flag条件
开启靶机,访问www.zip(通过扫描可以直接扫到)文件进行下载,分析源码
查看index文件
查看index源码,发现包含了class.php文件,select为参数获取值,并且使用了反序列化对select变量,那么就查看一下class.php文件
2、分析会触发调用__wakeup 强制username值
查看class文件发现一个Name的类,找到flag,通过简单的查看可以知道触发函数__destruct函数并且username为admin时可以获取flag
__destruct方法只要被销毁就可以调用,所以条件很好触发,第一个if只要将password改为100即可,目前只要以第二个flag为突破口
在Name开端可以发现username和password已经为设置值,并且通过__construct赋值,还存在一个__wakeup函数(该函数在反序列化时会被触发),那么就证明即便修改了username的值,但是在反序列化后依旧会把username的值修改为guest
3、利用语言漏洞绕过 CVE-2016-7124
思路:那么就需要想办法绕过__wakeup这个方法
首先查看php版本,当版本在php5 <5.6.25或者php7 <7.0.10时,序列化字符串中表示对象属性个数的值大于真实属性个数就会跳过__weakup方法
4、构造payload后 修改满足漏洞条件触发
Pyload:
select=O%3A4%3A"Name"%3A3%3A%7Bs%3A14%3A"%00Name%00username"%3Bs%3A5%3A"admin"%3Bs%3A14%3A"%00Name%00password"%3Bi%3A100%3B%7D
先将属性值进行序列化
Name代表类名,2代表两个属性,将属性改为3
传入到select参数中,获取falg