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

相关推荐
飞翔的佩奇5 小时前
【完整源码+数据集+部署教程】【天线&水】舰船战舰检测与分类图像分割系统源码&数据集全套:改进yolo11-repvit
前端·python·yolo·计算机视觉·数据集·yolo11·舰船战舰检测与分类图像分割系统
哆啦A梦15886 小时前
点击Top切换数据
前端·javascript·vue.js
程序猿追6 小时前
Vue组件化开发
前端·html
艾德金的溪7 小时前
redis-7.4.6部署安装
前端·数据库·redis·缓存
小光学长7 小时前
基于Vue的2025年哈尔滨亚冬会志愿者管理系统5zqg6m36(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
前端·数据库·vue.js
@PHARAOH7 小时前
WHAT - 受控组件和非受控组件
前端·javascript·react.js
生莫甲鲁浪戴7 小时前
Android Studio新手开发第二十六天
android·前端·android studio
JH30738 小时前
B/S架构、HTTP协议与Web服务器详解
前端·http·架构
yi碗汤园8 小时前
【超详细】C#自定义工具类-StringHelper
开发语言·前端·unity·c#·游戏引擎