【CTF】强网杯2025 Web题目writeup

WEB

SecretVault

User(id=0, username='admin'),并为该用户插入了一个 VaultEntry,label='flag',密码字段是用 Fernet 加密的 flag(文件 /flag 的内容)。

只要以 id=0 的身份访问 dashboard,就能看到并解出 flag。

题目有一个中间件代理,使用Go的reverseproxy做代理转发接收的请求,然后发给flask,首先想到要伪造jwt,伪造一个uid为0的jwt token,但是没办法获取secretkey。另一种思路就是怎么让代理转发后的请求头没有X-User请求头,或者X-User请求头对应值为0,但是main.go中写死了,请求头中的X-User肯定会存在,要不为jwt解密出来的uid,要不为anonymous。

之后查看golang 的 reverseproxy 源码,发现如下代码片段

https://go.dev/src/net/http/httputil/reverseproxy.go

代码对应的功能是移除 HTTP 请求 / 响应中的 "逐跳头部"(Hop-by-Hop Headers),确保代理服务器(如反向代理、网关)在转发请求时符合 HTTP 协议规范,避免头部被不当传递到下游服务。

代码会遍历 Connection 头部的值(可能是逗号分隔的多个字段),然后分割逗号分隔的字段,之后删除该字段。

例如 Connection: keep-alive, X-Custom 表示 keep-alive 和 X-Custom 是逐跳头部,应被当前代理移除,不转发给下游。这段代码会解析 Connection 头部的值,将其中声明的所有字段从请求响应头中删除。

利用这个代理转发特性,将Connection头部值改成 Connection: close,x-user

然后就会将如下请求

复制代码
GET /dashboard HTTP/1.1
Host: xxxxxxx
User-Agent: curl/7.68.0
Accept: */*
X-User: 1
Connection: close,x-user
修改成如下请求转发
GET /dashboard HTTP/1.1
Host: xxxxxxx
User-Agent: curl/7.68.0
Accept: */*

# 以下头部被移除:
# - Connection: close,x-user(标准逐跳头部,被删除)
# - X-User: 1(被 Connection 声明为逐跳,被删除)

之后flask收到的请求头不包含X-User: 1,默认将0赋值给uid,然后输出flag

ezphp

复制代码
function generateRandomString($length = 8){
    $characters = 'abcdefghijklmnopqrstuvwxyz';
    $randomString = '';
    for ($i = 0;$i < $length; $i++) {
        $r = rand(0, strlen($characters) - 1);
        $randomString .= $characters[$r];
    }
    return $randomString;
}
date_default_timezone_set('Asia/Shanghai');

class test{
    public $readflag;
    public $f;
    public $key;
    public function __construct(){
        $this->readflag = new class {
            public function __construct(){
                if (isset($_FILES['file']) && $_FILES['file']['error'] == 0) {
                    $time = date('Hi');
                    $filename = $GLOBALS['filename'];
                    $seed = $time . intval($filename);
                    mt_srand($seed);
                    $uploadDir = 'uploads/';
                    $files = glob($uploadDir . '*');
                    foreach ($files as $file) {
                        if (is_file($file)) unlink($file);
                    }
                    $randomStr = generateRandomString(8);
                    $newFilename = $time . '.' . $randomStr . '.' . 'jpg';
                    $GLOBALS['file'] = $newFilename;
                    $uploadedFile = $_FILES['file']['tmp_name'];
                    $uploadPath = $uploadDir . $newFilename;
                    if (system("cp ".$uploadedFile." ". $uploadPath)) {
                        echo "success upload!";
                    } else {
                        echo "error";
                    }
                }
            }
    
            public function __wakeup(){
                phpinfo();
            }
            
            public function readflag(){
                function readflag(){
                    if (isset($GLOBALS['file'])) {
                        $file = $GLOBALS['file'];
                        $file = basename($file);
                        if (preg_match('/:\/\//', $file)) 
                            die("error");
                        
                        $file_content = file_get_contents("uploads/" . $file);
                    
                        if (preg_match('/<\?|\:\/\/|ph|\?\=/i', $file_content)) {
                            die("Illegal content detected in the file.");
                        }
                        
                        include("uploads/" . $file);
                    }
                }
            }
        };

    }

    public function __destruct(){
        $func = $this->f;
        $GLOBALS['filename'] = $this->readflag;
        if ($this->key == 'class')
            new $func();
        else if ($this->key == 'func') {
            $func();
        } else {
            highlight_file('index.php');
        }
    }
}

$ser = isset($_GET['land']) ? $_GET['land'] : 'O:4:"test":N';

@unserialize($ser);

析构函数中可以直接调用函数,可以将$func赋值为函数名调用函数,也可以用数组回调对象方法,第一个参数为对象变量,第二个参数为类中的方法名称字符串。

然后看test类中的构造函数

新建了一个匿名类,赋值给$this->readflag。新的匿名类中包含一个构造函数,功能大致为可以向uploads文件夹任意上传文件,文件内容无限制,文件名称和后缀不可控。还包含一个readflag函数,其中又声明了readflag函数,主要功能,是先检查文件的内容,如果不包含危险字符,则包含对应的文件,本地测试发现,上传的文件在每分钟之内文件名固定,

那么就可以条件竞争,一边传执行命令的php脚本,一边传可通过过滤的文本。

执行test类的__construct函数,很好构造

这样就可以传任意内容的文件了。

之后就是要触发readflag函数,进行文件包含了。

exp中最后序列化的一个数组,依次分三个步骤,第一步,先去调用执行test类的__construct函数,然后得到test实例化后的对象,对应exp中的t变量,第二步,调用t变量,第二步,调用t变量,第二步,调用this->readflag的readflag方法,然后得到对readflag函数的声明,之后就可以调用readflag的方法,进行文件包含。构造好这3个对象,存于一个数组中,然后进行序列化,可执行readflag函数。

之后burp 条件竞争,先列目录

之后发现flag没权限读,需要提权,suid提权 find / -perm -u=s -type f 2>/dev/null

base64 可以提权

相关推荐
Man1 天前
当我们执行 npm run xxx 的时候实际执行逻辑和流程
前端·javascript·前端框架
曾富贵1 天前
【eslint】快速配置
前端
阿珊和她的猫1 天前
Webpack Loader 和 Plugin 实现原理详解
前端·webpack·node.js
做怪小疯子1 天前
JavaScript 中Array 整理
开发语言·前端·javascript
香香爱编程1 天前
Electron里的electron-window-state 使用
前端·javascript·vue.js·vscode·electron·前端框架
牧野星辰1 天前
eslint你不懂的都在这里,不信你进来看嘛~
前端·eslint
FogLetter1 天前
设计模式奇幻漂流:从单例孤岛到工厂流水线
前端·设计模式
ohyeah1 天前
深入理解 JavaScript 数组:从创建到遍历的完整指南
前端·javascript
逛逛GitHub1 天前
GitHub 开源 AI 好玩神器,自动记录你的一天。
前端·github
hollyhuang1 天前
正则校验:校验只能输入数字且首位不能是0
前端