目录
PHP基础知识:
类的结构:
序列化
反序列化
魔术方法
字符串逃逸
PHP基础知识:
类的结构:
常见访问权限修饰符:
- public:公共的,在类的内部、子类中或者类的外部都可以使用,不受限制;
- protected:受保护的,在类的内部、子类中可以使用,但不能在类的外部使用;
- private:私有的,只能在类的内部使用,在类的外部或者子类中都无法使用。
访问权限符对变量和函数都有效
序列化
序列化作用:序列化就是将对象的状态信息(属性)转换为可以存储或传输的形式过程。
<?php
$n = NULL;
echo (serialize($n)) . "<br>";
$i = 123;
echo(serialize($i))."<br>";
$s = "php是世界上最好的语言";
echo (serialize($s)) . "<br>";
$f=12.23;
echo(serialize($f))."<br>";
$a = ["abc", "def", "ghi"];
echo (serialize($a)) . "<br>";
?>
<?php
class car{
public $price=12345.56;
public $color = "black";
public $style = "越野";
function run(){
echo ("车启动");
}
}
$o=new car();
echo (serialize($o)) . "<br>";//O:3:"car":3:{s:5:"price";d:12345.56;s:5:"color";s:5:"black";s:5:"style";s:6:"越野";}
?>
O:3:"car":3:{s:5:"price";d:12345.56;s:5:"color";s:5:"black";s:5:"style";s:6:"越野";}
object:类名长度:类名:变量个数:{对每个变量名和变量值进行序列化}
这是上面的public 类型变量进行序列化的格式;对于private类型的是变量序列化时,在变量名前面加一个“空类名空”;protected类型的是加一个“空*空”(这里说的都是对于变量名。与变量值没有关系)
在正常序列化之后这个空是看不出来的,我们可以对其urlencode编码之后可以看到%00就是我们所说的空。
反序列化
反序列化就是将序列化后的参数还原成实例化的对象,经过反序列化后的内容为一个对象。
反序列化生成的对象里的值,由反序列化里的值提供;与原有类预定义的值无关;反序列化不触发类的成员方法;需要调用方法后才能触发。
<?php
class car{
public $price=12345.56;
protected $color = "black ";
private $style = "越野";
function run(){
echo ("车启动");
}
}
$o=new car();
$d = serialize($o);
echo $d. "<br>";
$s = 'O:3:"car":3:{s:5:"price";d:12345.56;s:8:"%00*%00color";s:6:"black ";s:10:"%00car%00style";s:6:"越野";}';
var_dump(unserialize(urldecode($s)));//urldecode就是参数进行url解码
/**
* vardump就是输出变量的详细信息,包括类型值等等
*/
?>
反序列化漏洞的成因:反序列化过程中,unserialize()接受的值(字符串)可控;通过更高这个值(字符串),得到所需要的代码,即生成的对象的属性值。
魔术方法
魔术方法就是一个预先定义好的,在特定情况下自动触发的行为方法。
魔术方法前提:魔术方法所在的类(或对象)被调用
- __construct()函数
这是一个构造函数,在实例化一个对象的时候,首先会去自动执行的一个方法。
- __destruct函数
这是一个析构函数,在对象的所有引用被删除或者当对象被显示销毁执行的魔术方法。
注意:在实例化对象后,代码运行完全销毁,会触发析构函数__destruct();反序列化得到的是对象,用完之后会销毁,触发析构函数。
- __sleep()函数
序列化serialize()函数会检查类中是否存在一个魔术方法__sleep()。如果存在,该方法会先被调用,然后执行序列化操作。此功能可以用于清理对象,并返回一个包含对象中所有应被序列化的变量名称的数组;如果该方法未返回任何内容,则NULL被序列化,并产生一个E_NOTICE级别的错误。
触发时机:序列化之前
功能:确定要序列化的成员变量
class car{ public $price=12345.56; protected $color = "black "; private $style = "越野"; function run(){ echo ("车启动"); } public function __sleep(){ return NULL; } } $o=new car(); $d = serialize($o); echo $d. "<br>";
结果:
- __wakeup()函数
unserialize()会检查是否存在一个__wakeup()方法。如果存在,则会先调用__wakeup()方法,预先准备对象需要的资源。
预先准对象资源,返回void,常用于反序列化操作中重新建立数据库连接或执行其他初始化操作。
- __toString()函数
这个魔术方法是表达式错误触发
触发时机:把对象当成字符串调用
对于一个new出来的对象,可以使用print_r或者var_dump;如果使用echo或者print只能调用字符串的方式去调用对象,此时就会触发toString()。
- __invoke()函数
触发时机:把对象当成函数调用
输出语句区别
- __clone()函数
触发时机:当使用clone关键字拷贝完成一个对象后,新对象会自动调用定义的魔术方法__clone()。
- __isset()函数
触发时机:对不可访问属性使用isset()或empty()时,__isset()会被调用。
参数:传参$arg1
返回值:不存在的成员属性的名称
isset()函数和empty()区别
- __unset()函数
触发时机:对不可访问或者不存在属性使用unset()时
参数:传参$arg1
返回值:不存在的成员属性的名称
unset()
- __set()函数
触发时机:给不存在的成员属性赋值
参数:传递$arg1,$arg2
返回值:不存在的成员属性的名称和赋的值
- __get()函数
触发时机:调用的成员属性不存在
参数:传递$arg1
返回值:不存在的成员属性的名称
- __call()函数
触发时机:调用一个不存在的方法
参数:2个参数传参$arg1,$arg2
返回值:调用的不存在的方法的名称和参数
- __callstatic()函数
触发时机:静态调用或调用成员常量时使用的方法不存在
参数:2个参数传参$arg1,$arg2
返回值:调用的不存在的方法的名称和参数
字符串逃逸
属性逃逸:一般在数据先经过一次serialize再经过unserialize,在这个中间反序列化的字符串变多或者变少的时候有可能存在反序列化属性逃逸。
上面我们所说的反序列化的结果是由字符串决定,并不是绝对的,我们要注意下面这种情况。
<?php
class A{
var $a="A";
var $b="hanhui";
var $c = "han";
var $d = "hui1";
}
echo(serialize(new A()))."<br>";
$b='O:1:"A":2:{s:1:"a";s:1:"A";s:2:"b1";s:6:"hanhui";}';
var_dump(unserialize($b))
?>
在这种场景下,我们反序列化不仅会把字符串的反序列化,还会包含原类中所有的成员属性;如果字符串中有变量名,则对应的值会按字符串中的,否则会按原对象中的值决定。
原理:字符串逃逸就是在反序列化时候会按照变量个数和字符串个数进行反序列化,所以通过改变这些值从而达到我们的目的。
知识点:
wakeup魔术方法绕过:如果产生__wakeup方法,调用unserialize()方法前则先调用__wakeup方法,但是序列化字符串中表示对象属性个数的值大于真实属性个数时,则会跳过__wakeup()的执行。
上述漏洞只有在PHP5<5.6.25和 PHP7<7.0.10中。
还有一个就是绕过正则表达式:当类名字符个数不允许是数字时候,在类名字符个数前面加一个+;然后进行url编码。
引用:就是保证两个值的值相同,可以使用$a=&$b。