本文档详细讲解 SECSNOW 平台的 Flag 机制,包括静态Flag、动态Flag、多段Flag的配置方法和最佳实践。

1. Flag 类型概述

SECSNOW 平台支持两种 Flag 类型:

Flag类型 说明 适用场景 安全性
静态Flag 所有用户答案相同 签到题、知识点题目 低(容易泄露)
动态Flag 每个用户或者队伍答案唯一 比赛题、重要题目 高(防止共享)

2. 多段 Flag 配置(漏洞靶场模块)

漏洞靶场模块支持一道题目配置多个 Flag(1-10个),并为每个 Flag 分配不同分数。

关键机制:平台会为每段Flag生成不同的值,并通过 SNOW_FLAG_1, SNOW_FLAG_2, SNOW_FLAG_3 等环境变量分别注入到容器中。

2.1 配置方法

在后台管理 → 练习题目 → 编辑题目时配置以下字段:

字段1:Flag 值

静态 Flag(手动配置答案):

flag{answer1},flag{answer2},flag{answer3}
  • 多个答案用英文逗号分隔
  • 系统自动检测逗号分隔的数量
  • 顺序提交,第1段给第1个Flag分数,第2段给第2个Flag分数

动态 Flag(自动生成):

3
  • 仅填写数量(如:3)
  • 系统自动为每个用户生成3个唯一的动态Flag
  • 注入方式:通过环境变量 SNOW_FLAG_1, SNOW_FLAG_2, SNOW_FLAG_3 分别注入

字段2:Flag 数量

3
  • 静态Flag:自动检测逗号数量,无需手动设置
  • 动态Flag:必须设置(1-10)

字段3:各 Flag 分数配置

[10, 20, 30]

验证规则: - 数组长度 = Flag数量(如3个Flag必须配3个分数) - 总和 = 题目总分(如总分60,则 [10,20,30] 总和60) - 全部为正整数 - 不符合规则时自动重置为平均分配

示例配置

题目总分:60分
Flag数量:3个
分数配置:[10, 20, 30]

结果:
- 第1段Flag:10分
- 第2段Flag:20分  
- 第3段Flag:30分
总计:60分

2.2 用户提交流程

  1. 用户创建容器,获得环境
  2. 解出第1段Flag → 提交 → 获得10分
  3. 解出第2段Flag → 提交 → 获得20分
  4. 解出第3段Flag → 提交 → 获得30分
  5. 全部提交正确 → 总分60分

3. 动态 Flag 注入机制

当题目关联容器时,需要配置动态Flag注入方式,确保Flag正确注入到容器中。

3.1 四种注入方式对比

注入方式 说明 镜像要求 平台操作 适用引擎
INTERNAL 镜像已内置 读取 SNOW_FLAG 环境变量 自动注入环境变量 Docker/K8s
CUSTOM_ENV 镜像已内置 读取自定义环境变量(如 FLAG 映射到自定义变量名 Docker/K8s
SCRIPT 脚本注入 无特殊要求 执行注入脚本 Docker/K8s
NONE 不支持动态 无特殊要求 使用静态Flag Docker/K8s

3.2 方式一:INTERNAL(推荐)

适用场景:镜像已内置动态Flag支持,且使用 SNOW_FLAG 环境变量读取

镜像要求示例

# 启动脚本中读取环境变量
FLAG=${SNOW_FLAG:-"flag{default}"}
echo $FLAG > /flag

平台配置

动态Flag适配:INTERNAL
Flag环境变量名:留空
Flag注入脚本:留空

工作原理

  1. 用户创建容器时,平台生成唯一Flag(如 flag{a1b2c3d4}
  2. 平台自动注入环境变量:SNOW_FLAG=flag{a1b2c3d4}
  3. 容器启动时读取 $SNOW_FLAG 并使用

优点

  • 最简单,无需额外配置
  • 性能最优(启动时注入)

3.3 方式二:CUSTOM_ENV

适用场景:镜像已内置动态Flag支持,但使用自定义环境变量名(如 FLAG, CTF_FLAG, GZCTF_FLAG

镜像要求示例

# 启动脚本中读取自定义环境变量
FLAG=${CTF_FLAG:-"flag{default}"}
echo $FLAG > /flag

平台配置

动态Flag适配:CUSTOM_ENV
Flag环境变量名:CTF_FLAG        # 填写镜像中使用的变量名
Flag注入脚本:留空

工作原理

  1. 平台生成唯一Flag:flag{a1b2c3d4}
  2. 平台将 SNOW_FLAG 映射为 CTF_FLAG=flag{a1b2c3d4}
  3. 容器启动时读取 $CTF_FLAG

优点

  • 兼容第三方镜像(如 GZCTF、CTFd 镜像)
  • Docker 和 K8s 通用
  • 无需修改镜像

3.4 方式三:SCRIPT(万能方案)

适用场景:镜像未内置动态Flag支持,需要通过脚本注入

镜像要求:无特殊要求

平台配置

动态Flag适配:SCRIPT
Flag环境变量名:留空
Flag注入脚本:echo "$SNOW_FLAG" > /flag

注意事项

  • 脚本执行失败可能会影响容器启动,请确保脚本中的命令在镜像中存在
  • 需要确保脚本中的命令在镜像中存在
  • 复杂脚本建议提前在容器中测试

3.5 方式四:NONE

适用场景:题目不支持动态Flag,使用静态Flag

平台配置

动态Flag适配:NONE
Flag环境变量名:留空
Flag注入脚本:留空
题目Flag值:flag{static_answer}

说明: - 所有用户答案相同 - 适合签到题、公开题目 - 不会注入任何环境变量


4. 如何出动态 Flag 题目

完整流程示例

场景:制作一道Web题,要求用户找到隐藏的Flag文件

步骤1:准备镜像

创建 Dockerfile

FROM nginx:alpine

# 方式1:使用 INTERNAL(推荐)
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

创建 entrypoint.sh

#!/bin/sh

# 读取平台注入的 SNOW_FLAG 环境变量
# 单段Flag时:SNOW_FLAG 或 SNOW_FLAG_1 都可以
FLAG=${SNOW_FLAG:-${SNOW_FLAG_1:-"flag{default}"}}

# 将Flag写入隐藏位置
echo $FLAG > /usr/share/nginx/html/.flag.txt
chmod 644 /usr/share/nginx/html/.flag.txt

# 启动Nginx
nginx -g 'daemon off;'

构建镜像:

docker build -t my-web-challenge:v1 .

步骤2:后台配置题目

1. 创建镜像配置(系统容器 → 镜像管理) - 镜像名称:my-web-challenge:v1 - 动态Flag适配:INTERNAL - Flag环境变量名:留空 - Flag注入脚本:留空

2. 创建题目(练习题目 → 添加题目) - 题目名称:Web - 找到隐藏Flag - Flag类型:动态Flag - Flag值:1 (生成1个动态Flag) - Flag数量:1 - 分数配置:[100] - 关联容器:选择刚创建的镜像配置

步骤3:用户使用流程

  1. 用户点击"启动环境"
  2. 平台创建容器,注入动态Flag:
  3. 单段Flag:SNOW_FLAG=flag{a1b2c3d4}SNOW_FLAG_1=flag{a1b2c3d4}
  4. 多段Flag:SNOW_FLAG_1=flag{xxx}, SNOW_FLAG_2=flag{yyy}, SNOW_FLAG_3=flag{zzz}
  5. 容器启动,entrypoint.sh 执行: bash echo "flag{a1b2c3d4}" > /usr/share/nginx/html/.flag.txt
  6. 用户访问 http://容器IP/.flag.txt 获得Flag
  7. 用户提交 flag{a1b2c3d4},系统验证通过

环境变量命名规则: - 单段FlagSNOW_FLAGSNOW_FLAG_1(两者等价) - 多段FlagSNOW_FLAG_1, SNOW_FLAG_2, ... SNOW_FLAG_N(N为Flag数量)


5. 多段动态 Flag 题目示例

场景:制作一道渗透题,分3个阶段,每阶段一个Flag

重要:多段Flag时,平台会为每段Flag生成不同的值,并通过 SNOW_FLAG_1, SNOW_FLAG_2, SNOW_FLAG_3 等环境变量分别注入。

步骤1:准备镜像

entrypoint.sh

#!/bin/sh

# 从环境变量读取多段Flag(平台自动注入)
FLAG1=${SNOW_FLAG_1:-"flag{default1}"}
FLAG2=${SNOW_FLAG_2:-"flag{default2}"}
FLAG3=${SNOW_FLAG_3:-"flag{default3}"}

# 注入到不同位置
echo $FLAG1 > /flag1.txt                    # 阶段1:简单文件
echo $FLAG2 | base64 > /var/www/flag2.enc  # 阶段2:Base64编码
sqlite3 /db.sqlite "INSERT INTO flags VALUES('$FLAG3');"  # 阶段3:数据库

# 启动服务
/start.sh

平台注入机制

# 平台自动为用户生成3个不同的Flag
SNOW_FLAG_1=flag{a1b2c3d4-段1}
SNOW_FLAG_2=flag{e5f6g7h8-段2}
SNOW_FLAG_3=flag{i9j0k1l2-段3}

步骤2:后台配置

题目总分:300分
Flag类型:动态Flag
Flag值:3               # 告诉平台生成3段Flag
Flag数量:3             # 与Flag值保持一致
分数配置:[50, 100, 150]  # 每段Flag对应的分数

镜像配置:
  动态Flag适配:INTERNAL
  # 平台会自动注入 SNOW_FLAG_1, SNOW_FLAG_2, SNOW_FLAG_3

步骤3:用户答题

  1. 阶段1:读取 /flag1.txt 得到第1段Flag → 提交 → 50分
  2. 阶段2:Base64解码 /var/www/flag2.enc 得到第2段Flag → 提交 → 100分
  3. 阶段3:从数据库获取第3段Flag → 提交 → 150分

关键说明: - 平台为每段Flag生成不同的UUID - 每个用户的3段Flag都是唯一的 - 环境变量命名:SNOW_FLAG_1, SNOW_FLAG_2, ... SNOW_FLAG_N - 不是只注入一个 SNOW_FLAG 然后自己派生


6. 常见问题

Q1:动态Flag没有注入到容器中?

排查步骤

# 1. 检查环境变量是否注入
docker exec secsnow-user-<id> env | grep SNOW_FLAG

# 2. 查看容器日志
docker logs secsnow-user-<id>

# 3. 检查镜像配置
# 后台 → 系统容器 → 镜像管理 → 查看配置

常见原因: - 镜像启动脚本未读取 $SNOW_FLAG - 脚本注入执行失败(SCRIPT模式) - 环境变量名配置错误(CUSTOM_ENV模式)


Q2:用户提交正确Flag但验证失败?

排查步骤

# 1. 查看用户实际Flag值
# 后台 → 用户容器记录 → 查看Flag值

# 2. 检查Flag格式
# 确保格式一致:flag{xxx} 还是 FLAG{xxx}

# 3. 查看提交日志
# 后台 → 答题记录

Q3:多段Flag分数配置不生效?

原因:配置不符合验证规则

正确示例

题目总分:100
Flag数量:3
分数配置:[20, 30, 50]  总和=100

错误示例:
分数配置:[20, 30, 40]  总和=90,不等于100
分数配置:[20, 30]      长度=2,不等于Flag数量3
分数配置:[20, -30, 110] 包含负数

解决:后台自动重置为平均分配 [33, 33, 34]


Q4:多段Flag只有第1段正确,其他段都验证失败?

原因:镜像只读取了 SNOW_FLAGSNOW_FLAG_1,没有读取 SNOW_FLAG_2, SNOW_FLAG_3

排查

# 1. 检查容器中的环境变量
docker exec <container_id> env | grep SNOW_FLAG

# 应该看到:
# SNOW_FLAG_1=flag{xxx}
# SNOW_FLAG_2=flag{yyy}
# SNOW_FLAG_3=flag{zzz}

# 2. 检查镜像启动脚本
docker exec <container_id> cat /entrypoint.sh

# 应该包含:
# FLAG1=${SNOW_FLAG_1:-"default"}
# FLAG2=${SNOW_FLAG_2:-"default"}
# FLAG3=${SNOW_FLAG_3:-"default"}

解决:修改镜像启动脚本,确保读取所有环境变量:

# 错误写法(只读取第1段)
FLAG=${SNOW_FLAG:-"default"}
echo $FLAG > /flag1.txt
echo $FLAG > /flag2.txt  # 错误:所有段都是同一个值

# 正确写法(分别读取每段)
FLAG1=${SNOW_FLAG_1:-"default1"}
FLAG2=${SNOW_FLAG_2:-"default2"}
FLAG3=${SNOW_FLAG_3:-"default3"}
echo $FLAG1 > /flag1.txt  # 正确
echo $FLAG2 > /flag2.txt  # 正确
echo $FLAG3 > /flag3.txt  # 正确

7. 最佳实践建议

7.1 镜像制作规范

  • 优先使用 INTERNAL 模式(简单可靠)
  • 启动脚本中添加默认Flag值(方便本地测试)
  • 单段Flag:使用 ${SNOW_FLAG:-"flag{default}"}${SNOW_FLAG_1:-"flag{default}"}
  • 多段Flag:必须使用 SNOW_FLAG_1, SNOW_FLAG_2, SNOW_FLAG_N
  • 防止变量为空:${SNOW_FLAG_1:-"flag{default1}"}

7.2 Flag 格式规范

  • 统一格式:flag{xxx}FLAG{xxx}
  • 动态Flag使用UUID4保证唯一性
  • 避免特殊字符(如引号、反斜杠)

7.3 分数配置建议

  • 难度递增:[10, 30, 60](后面阶段分数更高)
  • 总分为整数且易计算
  • 避免单个Flag分数过低(<5分)

7.4 测试流程

单段Flag测试

# 1. 本地测试镜像(单段)
docker run -e SNOW_FLAG="flag{test123}" my-image:v1
# 或
docker run -e SNOW_FLAG_1="flag{test123}" my-image:v1

# 2. 验证Flag注入
docker exec <container_id> cat /flag

# 3. 平台测试
# 创建测试用户 → 启动环境 → 提交Flag → 验证

多段Flag测试

# 1. 本地测试镜像(多段)
docker run \
  -e SNOW_FLAG_1="flag{test1}" \
  -e SNOW_FLAG_2="flag{test2}" \
  -e SNOW_FLAG_3="flag{test3}" \
  my-image:v1

# 2. 验证每段Flag注入
docker exec <container_id> cat /flag1.txt
docker exec <container_id> cat /flag2.txt
docker exec <container_id> cat /flag3.txt

# 3. 平台测试
# 创建测试用户 → 启动环境 → 依次提交3段Flag → 验证分数累加