> For AI agents: the complete documentation index is available at /zh/llms.txt, the full documentation bundle is available at /zh/llms-full.txt.

# 动态 flag

GZCTF 自带对于动态 flag 分发的支持，将会在容器启用时采用 `GZCTF_FLAG` 环境变量进行注入。

:::info

采用此环境变量的主要原因是出于防止 GZCTF 被商业滥用的考虑，因此短时间内不会开放此功能的自定义。

:::

## 配置规则

在动态题目的 flag 及附件管理页面中，flag 模板将作为生成动态 flag 的依据，具有如下的规则：

1. 留空以生成随机 `GUID` 作为 flag
2. 指定 `[GUID]` 则会 **仅** 替换此处的占位符为随机 GUID
3. 若指定 `[TEAM_HASH]` 则它将会被替换为队伍 Token 与相关信息所生成的哈希值
4. 若未指定 `[TEAM_HASH]` 则将启用 Leet 字符串功能，将会基于模板对花括号内字符串进行变换，需要确保 flag 模板字符串的熵足够高
5. 若需要在指定 `[TEAM_HASH]` 的情况下启用 Leet 字符串功能，请在 flag 模板字符串之前添加 `[LEET]` 标记，此时不会检查 flag 模板字符串的熵
6. 若需要在 Leet 字符串时启用特殊字符（可能会导致注入问题），请在 flag 模板字符串之前添加 `[CLEET]` 标记

## 规则示例

1. 留空会得到 `flag{1bab71b8-117f-4dea-a047-340b72101d7b}`
2. `MyCTF{[GUID]}` 会得到 `MyCTF{1bab71b8-117f-4dea-a047-340b72101d7b}`
3. `flag{hello world}` 会得到 `flag{He1lo_w0r1d}`
4. `[CLEET]flag{hello sara}` 会得到 `flag{He1!o_$@rA}`
5. `flag{hello_world_[TEAM_HASH]}` 会得到 `flag{hello_world_5418ce4d815c}`
6. `[LEET]flag{hello world [TEAM_HASH]}` 会得到 `flag{He1lo_w0r1d_5418ce4d815c}`

## Leet 字符串

Leet 字符串是一种将字符串中的字符替换为数字或符号的方法，例如将 `a` 替换为 `4`，将 `e` 替换为 `3` 等，GZCTF 采用的 Leet 字符串规则如下：

```txt
+-------------+---------------+-------------+---------------+-------------+---------------+
| Characters  | Replaced with | Characters  | Replaced with | Characters  | Replaced with |
+-------------+---------------+-------------+---------------+-------------+---------------+
|     A       |      Aa4      |      B      |      Bb68     |      C      |      Cc       |
|     D       |      Dd       |      E      |      Ee3      |      F      |      Ff1      |
|     G       |      Gg69     |      H      |      Hh       |      I      |      Ii1l     |
|     J       |      Jj       |      K      |      Kk       |      L      |      Ll1I     |
|     M       |      Mm       |      N      |      Nn       |      O      |      Oo0      |
|     P       |      Pp       |      Q      |      Qq9      |      R      |      Rr       |
|     S       |      Ss5      |      T      |      Tt7      |      U      |      Uu       |
|     V       |      Vv       |      W      |      Ww       |      X      |      Xx       |
|     Y       |      Yy       |      Z      |      Zz2      |      0      |      0oO      |
|     1       |      1lI      |      2      |      2zZ      |      3      |      3eE      |
|     4       |      4aA      |      5      |      5Ss      |      6      |      6Gb      |
|     7       |      7T       |      8      |      8bB      |      9      |      9g       |
+-------------+---------------+-------------+---------------+-------------+---------------+
```

启用复杂 Leet 字符串时，请注意字符注入问题，它采用的规则如下，由于可能性更多，达到指定的熵所需的长度会更短：

```txt
+-------------+----------------+-------------+----------------+-------------+----------------+
| Characters  | Replaced with  | Characters  | Replaced with  | Characters  | Replaced with  |
+-------------+----------------+-------------+----------------+-------------+----------------+
|     A       |     Aa4@       |      B      |      Bb6&      |      C      |      Cc(       |
|     D       |     Dd         |      E      |      Ee3       |      F      |      Ff1       |
|     G       |     Gg69       |      H      |      Hh        |      I      |      Ii1l!     |
|     J       |     Jj         |      K      |      Kk        |      L      |      Ll1I!     |
|     M       |     Mm         |      N      |      Nn        |      O      |      Oo0#      |
|     P       |     Pp         |      Q      |      Qq9       |      R      |      Rr        |
|     S       |     Ss5$       |      T      |      Tt7       |      U      |      Uu        |
|     V       |     Vv         |      W      |      Ww        |      X      |      Xx        |
|     Y       |     Yy         |      Z      |      Zz2?      |      0      |      0oO#      |
|     1       |     1lI|       |      2      |      2zZ?      |      3      |      3eE       |
|     4       |     4aA        |      5      |      5Ss       |      6      |      6Gb       |
|     7       |     7T         |      8      |      8B&       |      9      |      9g        |
+-------------+----------------+-------------+----------------+-------------+----------------+
```

### 安全性

Leet 字符串的安全性取决于 flag 模版字符串的熵，对于 flag 模版中每一个字符，它都有可能被替换为多个字符。我们采用每一个可变字符的可变字符集合的长度对 2 取对数后累加，从而得到了 Leet 字符串的熵：

```math
\begin{aligned}
H &= \sum_{i=1}^{n} \log_2{m_i} \\
m_i &= \begin{cases}
    \text{len}(\text{LeetMap}[c_i]) & \text{if } c_i \text{ is in LeetMap} \\
    0 & \text{otherwise}
\end{cases}
\end{aligned}
```

在 GZCTF 中，这一指标被限制不得低于 32，否则将会导致 flag 的安全性降低。

## 队伍哈希

队伍哈希是一种将队伍 Token 与相关信息进行哈希的方法，它将会被用于动态 flag 的生成，以保证每一个队伍都有唯一的 flag。

在 GZCTF 中，队伍哈希为 SHA256 哈希的中部 12 位，例如 `5418ce4d815c`，它将会被用于替换 flag 模板中的 `[TEAM_HASH]` 占位符。

队伍哈希的计算采用了三个参数：

- 队伍 Token：在队伍注册时由系统生成并签发、可被公钥验证的 ed25519 签名
- 题目 ID：题目的唯一标识符
- 比赛哈希盐：加密后的比赛签名私钥加盐之后的 SHA256 哈希

生成 Team Hash 的 Python 代码如下：

```python
from hashlib import sha256

str_sha256 = lambda s: sha256(s.encode()).hexdigest()

encrypted_game_pk = "...some base64..."
chal_id = 114
team_token = "114:...some base64..."

# you can get this salt from /api/edit/games/{id}/hashsalt
game_salt = str_sha256(f"GZCTF@{encrypted_game_pk}@PK")

# you should calculate this hash by yourself, and put it in challenge
chal_salt = str_sha256(f"{game_salt}::{chal_id}")

# let your challenge to calculate team hash
team_hash = str_sha256(f"{chal_salt}::{team_token}")[12:24]
```

其中，比赛哈希盐 `game_salt` 可以通过管理员权限访问 `/api/edit/games/{id}/hashsalt` 接口获取，如需使用请注意保密。

### 安全性

- 队伍 Token 由 GZCTF 签发，它是一个 ed25519 签名，**可以被公钥验证，因此不会被伪造**
- 比赛哈希盐是一个比赛特异的值，源自比赛签名私钥的哈希，**管理员需确保其保密性**
- 题目 ID 是一个整数，在创建题目时由系统生成，**由管理员结合比赛哈希盐来计算题目哈希盐**
- 题目哈希盐是一个题目特异的值，应当被用于最终的 Team Hash 计算，**其泄漏不会影响其他题目的安全性**

### 正确使用

队伍哈希的核心使用场景之一是外部题目（队伍所访问的最终容器并非 GZCTF 所启动的容器），例如某些 Web 题目的部署难度高、依赖复杂的情况下，题目可能只有一个外部实例，而不是每一个队伍都有一个独立的实例。

在这种情况下，我们可以通过校验队伍 Token 并根据队伍 Token 来独立生成 flag，从而保证每一个队伍都有唯一的动态 flag。

### 队伍签名校验

比赛公钥可以直接从比赛管理页面获取，它是一个被 Base64 编码的 ed25519 公钥，例如：

```
s2r5WQUClYNsldJrRKanrKivBUtyN+3MjeOiKNL3znI=
```

队伍 Token 是一个被 Base64 编码的 ed25519 签名，它的格式为：

```
1201:HCdjp352NcQoL/4gS8RP3xRt5B9xX2V4m2UeoqfM2dxcLrI5FiYQ7HC9pqreG+tudWjYJf0atzQhhAKyYDKsCg==
```

可以使用以下代码来校验队伍 Token，其中 `base64` 和 `nacl` 为 Python 库：

```python
from base64 import b64decode
from nacl.signing import VerifyKey

token = "1201:HCdjp352NcQoL/4gS8RP3xRt5B9xX2V4m2UeoqfM2dxcLrI5FiYQ7HC9pqreG+tudWjYJf0atzQhhAKyYDKsCg=="
verify_key = VerifyKey(b64decode("s2r5WQUClYNsldJrRKanrKivBUtyN+3MjeOiKNL3znI="))

data = f"GZCTF_TEAM_{token.split(':')[0]}".encode()

try:
    verify_key.verify(data, b64decode(token.split(':')[1]))
except:
    print("Invalid token")
```

PyNaCl 是 libsodium 的 Python 封装，在常见的系统中大概率已经预装了 libsodium，详情参考： [PyNaCl](https://pynacl.readthedocs.io/en/latest/)。

你也可以使用任何其他语言的 ed25519 签名校验库来校验队伍 Token 是否为平台所签发的有效签名，并为下发 flag 的安全性做密码学保证。
