尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

2024年熵密杯 Flag3 精讲:证书伪造 — CA注册 + 持有者验证缺失

2024年熵密杯 Flag3 精讲:证书伪造 — CA注册 + 持有者验证缺失
📅 发布时间:2026/7/21 5:35:55

🔐 2024年熵密杯 Flag3 精讲:证书伪造 — CA注册 + 持有者验证缺失

阅读指引:本文是"熵密杯2024年真题精析"密码系统系列的第三篇。Flag3 考察的是证书认证中的身份验证缺失漏洞——系统在登录时验证了证书链的合法性(Issuer/Subject字段、有效期、证书链),却漏掉了最关键的一步:验证证书持有者(CommonName)是否与登录用户名一致。同时系统开放了CA注册接口,允许用户使用任意公钥申请合法证书。攻击者只需生成SM2密钥对 → 拿公钥去CA注册获取合法证书 → 用私钥+证书+管理员账号登录即可绕过认证,无需伪造证书。


📖 目录

  1. 题目场景还原
  2. 系统双接口分析
  3. 漏洞分析:关键字段验证缺失
  4. 攻击链路:合法证书 + 任意用户名
  5. 代码实现:攻击步骤详解
  6. 攻击原理总结与防御

1. 题目场景还原

题目描述:某数据库管理系统实现了基于 SM2 证书的挑战-应答登录认证。系统提供了两个接口:(1) 登录接口,需要提供用户名、私钥签名、证书三项;(2) 注册接口,允许用户提交裸公钥,CA用根证书签发合法证书返回。请以管理员身份(shangmibeiadmin)登录系统获取 flag3。

┌──────────────────────────────────────────────────────────────┐ │ 系统架构 & 双接口 │ │ │ │ ┌─────────────────────┐ ┌─────────────────────┐ │ │ │ 注册接口 /register │ │ 登录接口 /login │ │ │ │ │ │ │ │ │ │ 输入:裸公钥(PEM) │ │ 输入:username │ │ │ │ │ │ cert(PEM) │ │ │ │ 输出:CA签发的证书 │ │ signature(hex) │ │ │ │ (合法证书!) │ │ │ │ │ └─────────┬───────────┘ │ 验证:Issuer字段 ✓ │ │ │ │ │ Subject字段 ✓ │ │ │ │ │ 有效期 ✓ │ │ │ │ ┌──────────┤ 证书链签名 ✓ │ │ │ │ │ │ │ │ │ └─────┼──────────┤ CommonName = ? │ ← 缺失! │ │ │ └─────────────────────┘ │ │ │ │ │ 攻击者拿着自己公钥 │ │ 通过CA注册拿到合法证书 │ │ 然后用私钥+证书 │ │ 以 "shangmibeiadmin" 身份登录 │ │ → 证书是合法的 → 验证全通过 → flag3 │ └──────────────────────────────────────────────────────────────┘

2. 系统双接口分析

2.1 注册接口 — CA 证书签发

系统提供了一个证书注册接口,用户可以提交裸公钥(raw public key),CA 会使用根证书为该公钥签发一个合法证书:

用户 ──提交裸公钥(PEM)──► CA 服务 │ ├─ 检查公钥格式是否正确 ├─ 使用根证书签发新证书 │ Subject.CommonName = 用户提交的或默认值 │ Subject.PublicKey = 用户提交的公钥 │ 用根证书私钥签名 │ 合法证书(PEM) ◄──┘

关键事实:任何人可以拿着任意公钥去CA注册,获得一个由根证书签名的、完全合法的证书。该证书能通过所有密码学验证。

2.2 登录接口 — 证书验证流程

funcvalidateCertificate(cert,rootCert*x509.Certificate)error{// 第一层: Issuer 字段字符串比对ifcert.Issuer.CommonName!=rootCert.Subject.CommonName{returnfmt.Errorf("证书校验失败")}ifcert.Issuer.Organization[0]!=rootCert.Subject.Organization[0]{returnfmt.Errorf("证书校验失败")}// 第二层: 有效期检查iftime.Now().Before(cert.NotBefore)||time.Now().After(cert.NotAfter){returnfmt.Errorf("证书校验失败")}// 第三层: Subject 字段硬编码比对ifcert.Subject.Organization[0]!="ShangMiBei"{returnfmt.Errorf("证书校验失败")}ifcert.Subject.OrganizationalUnit[0]!="ShangMiBei2024"{returnfmt.Errorf("证书校验失败")}// 第四层: x509 证书链密码学验证roots:=x509.NewCertPool()roots.AddCert(rootCert)opts:=x509.VerifyOptions{Roots:roots}if_,err:=cert.Verify(opts);err!=nil{returnfmt.Errorf("证书链校验失败")}returnnil}

2.3 验签流程

funcvalidateSignature(message,signaturestring,publicKey*sm2.PublicKey)(bool,error){hexSignature,_:=hex.DecodeString(signature)isValid:=publicKey.Verify([]byte(message),hexSignature)returnisValid,nil}

验签用的publicKey是从用户上传的证书中提取的——证书来自CA注册,完全合法;私钥在攻击者手中,签名必然验真。


3. 漏洞分析:关键字段验证缺失

漏洞(致命): CommonName 持有者身份未验证

整个验证流程检查了四项内容:

检查项验证内容结果
Issuer 字段Issuer == 根证书 Subject✅ 通过
有效期NotBefore ≤ now ≤ NotAfter✅ 通过
Subject 硬编码Org/OU 字段是否匹配✅ 通过
证书链签名cert.Verify() 密码学验证✅ 通过
CommonName vs username证书持有者 = 登录用户名?❌未检查!
// 代码中缺失了这一行:// if cert.Subject.CommonName != username {// return fmt.Errorf("证书持有者与登录用户不匹配")// }

后果:登录用户名username与证书持有者Subject.CommonName之间没有任何绑定关系。攻击者可以拿着以自己公钥注册的合法证书(CommonName可能是自己的名字或默认值),在登录时填写username="shangmibeiadmin",系统不会发现任何异常。

为什么证书链验证全部通过?

因为证书本身就是合法的——它不是自签证书,不是伪造证书,而是通过CA正规注册流程签发的真实证书:

根证书 ──密码学签名──► 攻击者的证书 (CA签发) │ ├─ Issuer = 根证书 Subject ✅ ├─ Subject.O = "ShangMiBei" ✅ ├─ Subject.OU = "ShangMiBei2024" ✅ ├─ 有效期正常 ✅ └─ cert.Verify() 密码学验证 ✅

证书完全合法,但持有者不是管理员——而系统恰恰没检查这一条。


4. 攻击链路:合法证书 + 任意用户名

步骤①: 生成攻击者的 SM2 密钥对 (privKey, pubKey) 步骤②: 将 pubKey 以 PEM 格式提交到注册接口 /register → CA 使用根证书签发合法证书 certPEM → certPEM 中的 PublicKey = 攻击者的 pubKey 步骤③: GET /challenge?username=shangmibeiadmin → 服务端返回 randomStr 步骤④: 用攻击者私钥 privKey 对 randomStr 做 SM2 签名 → signature = SM2_Sign(privKey, randomStr) 步骤⑤: POST /login { username: "shangmibeiadmin", ← 管理员账号 cert: certPEM, ← CA签发的合法证书 signature: hex(signature) ← 自己的私钥签名 } 步骤⑥: 服务端验证: ├─ validateCertificate(cert, rootCert) → ✅ 通过 (合法CA签发) ├─ 提取 cert.PublicKey → 攻击者的公钥 ├─ validateSignature(msg, sig, pubKey) → ✅ 通过 (自己私钥签名) └─ isAdmin = (username == "shangmibeiadmin") → true! 步骤⑦: 签发 JWT → 管理员身份 → 访问 /admin/flag → flag3
┌─────────────────────────────────────────────────────────────┐ │ 攻击链路全景 │ │ │ │ gmssl 生成密钥对 │ │ ┌──────────────────┐ │ │ │ privKey + pubKey │ │ │ └───────┬──────────┘ │ │ │ │ │ ├──► pubKey ──► CA注册接口 ──► 合法证书 certPEM │ │ │ │ │ ├──► privKey ──► SM2签名(challenge) ──► signature │ │ │ │ │ └──► certPEM + signature + "shangmibeiadmin" │ │ │ │ │ ▼ │ │ POST /login │ │ │ │ │ ▼ │ │ ┌─────────────────────────────┐ │ │ │ 证书合法? ✅ (CA签发) │ │ │ │ 证书持有者 = 登录用户? ← 没查! │ ← 致命缺陷 │ │ │ 签名有效? ✅ (自己私钥签的) │ │ │ │ username = "shangmibeiadmin" │ │ │ │ → isAdmin = true │ │ │ └─────────────────────────────┘ │ │ │ │ │ ▼ │ │ JWT Token → GET /admin/flag → flag3{...} │ └─────────────────────────────────────────────────────────────┘

5. 代码实现:攻击步骤详解

步骤①: 生成 SM2 密钥对

fromgmsslimportsm2,func# 使用 gmssl 生成 SM2 密钥对# 方式一:gmssl 命令行# $ gmssl genpkey -algorithm SM2 -out priv.pem# $ gmssl pkey -in priv.pem -pubout -out pub.pem# 方式二:Python gmssl库sm2_crypt=sm2.CryptSM2()priv_key_hex=func.random_hex(64)# 32字节私钥pub_key_hex=sm2_crypt.public_key_from_private(priv_key_hex)

或使用合适的工具生成

步骤②: 注册证书

importrequests# 读取公钥 PEMwithopen('pub.pem','r')asf:pub_pem=f.read()# 提交到注册接口resp=requests.post("http://server/register",json={"public_key":pub_pem})cert_pem=resp.json()["certificate"]# cert_pem 是 CA 用根证书签发的合法证书# 保存供后续使用withopen('cert.pem','w')asf:f.write(cert_pem)

注意:注册接口只是验证公钥格式(PEM格式、SM2 OID),不限制谁可以注册。任何人都能拿到由根证书签名的合法证书。

步骤③④⑤: 请求challenge → 签名 → 登录

fromgmsslimportsm2,func# 1. 获取 challenge(以管理员身份请求)resp=requests.get("http://server/challenge?username=shangmibeiadmin")random_str=resp.json()["random_str"]# 2. 用攻击者私钥对 challenge 做 SM2 签名sm2_crypt=sm2.CryptSM2(private_key=priv_key_hex,public_key="")signature=sm2_crypt.sign(random_str.encode())# 3. 发送登录请求resp=requests.post("http://server/login",json={"username":"shangmibeiadmin",# ← 管理员账号"cert":cert_pem,# ← 自己注册的合法证书"signature":signature.hex()})# 4. 提取 JWT Token, 以管理员身份访问 flag 接口token=resp.json()["token"]flag_resp=requests.get("http://server/admin/flag",headers={"Authorization":f"Bearer{token}"})print(flag_resp.json()["flag"])# flag3{...}

6. 攻击原理总结与防御

为什么攻击能够成功?

系统设计了证书链密码学验证,但忽略了授权的核心问题: "证书持有者是谁?" "他有没有权限以这个用户名登录?" ┌──────────────────────────────────────────────────────┐ │ 验证了什么 没验证什么 │ │ ─────────── ─────────── │ │ ✅ 证书是CA签发的吗? ❌ 证书是谁的? │ │ ✅ Issuer字段对吗? ❌ CommonName == username? │ │ ✅ Subject硬编码对吗? ❌ 证书持有者 = 管理员? │ │ ✅ 有效期正常吗? │ │ ✅ 证书链签名有效吗? │ │ ✅ 挑战-应答签名对吗? │ └──────────────────────────────────────────────────────┘ 结果:系统确认了"证书是真的",但没有确认"证书是这个人的"。 就像检查了身份证的防伪标识,却没看照片和姓名。

防御措施

措施说明
验证证书持有者登录时必须核对cert.Subject.CommonName == username,确保证书属于登录用户
CA注册时验证身份不应允许任意用户以任意身份注册证书。CA签发前应验证申请者的真实身份
公钥绑定服务端应预注册管理员公钥白名单,而非从请求中的证书动态提取
证书用途限制在证书扩展中设置 KeyUsage/ExtendedKeyUsage,区分认证证书和签名证书
OAuth/mTLS使用成熟的双向TLS认证或OAuth协议,而非自建证书挑战-应答
不信任用户提交的身份声明用户名(用户声称的身份)和证书(密码学凭证)必须交叉验证

上一篇:Flag2 — SM2协同签名 k3=k2 攻击
下一篇:Flag4 — LCG状态恢复:从IV破译SM4密钥

相关新闻

  • C++结构体在算法竞赛中的核心应用与实战技巧
  • 四大AI开发框架对比:Spring AI、LangChain、LangGraph与LlamaIndex
  • SpringBoot+Vue停车场系统实战:从CRUD到可维护架构的进阶之路

最新新闻

  • 微服务契约测试体系:Spring Cloud Contract/Pact 实战与 CI/CD 自动化 Mock 搭建
  • GameVault高级功能探索:游戏时间追踪、存档管理与成就系统实现
  • F2837xD ADC配置精解:从SOC触发到Burst模式实战
  • 长沙钻石回收行情透视:6家机构硬核测评,S级标杆溢价15% - 一日一测评
  • 如何高效使用HiGHS:开源线性优化求解器的终极完整指南
  • Go协程优化Claude API并发调用的实践与性能分析

日新闻

  • Python开发内部工具:7大核心库实战解析
  • 合肥雷达官方2026年7月最新信息:客户服务网点地址与售后热线权威公示 - 亨得利官方服务中心
  • PCA实战指南:从变量纠缠诊断到主成分业务解读

周新闻

  • SaaS软件行业GEO实践:AI搜索时代的品牌可见性与获客新路径
  • 什么是PCTFE?医药高端包装的“防潮王牌“材料
  • 【JVM调优实战】16-可视化利器-JConsole-VisualVM-JMC

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号