【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 可以提权

相关推荐
用户47949283569152 分钟前
Bun 卖身 Anthropic!尤雨溪神吐槽:OpenAI 你需要工具链吗?
前端·openai·bun
p***434815 分钟前
前端在移动端中的网络请求优化
前端
g***B73823 分钟前
前端在移动端中的Ionic
前端
拿破轮1 小时前
使用通义灵码解决复杂正则表达式替换字符串的问题.
java·服务器·前端
whltaoin1 小时前
【 Web认证 】Cookie、Session 与 JWT Token:Web 认证机制的原理、实现与对比
前端·web·jwt·cookie·session·认证机制
Aerelin1 小时前
爬虫playwright入门讲解
前端·javascript·html·playwright
5***o5002 小时前
前端在移动端中的NativeBase
前端
灵魂学者2 小时前
Vue3.x —— 父子通信
前端·javascript·vue.js·github
1***Q7842 小时前
前端跨域解决方案
前端
小雨青年2 小时前
MateChat 进阶实战:打造零后端、隐私安全的“端侧记忆”智能体
前端·华为·ai·华为云·状态模式