信息处理安全保障支援士 2024 年春季午后问 1 解说——JWT 的 alg=none 与 API 授权、WAF 的临时对策

· 更新日期: · · 信息处理安全保障支援士, 注册安全工程师, API, API安全, JWT, 认证, 授权, WAF, Log4Shell, 信息安全, 漏洞, IPA

更新记录(仅首版,2026年08月06日 发布)
首次发布
引用本文(DOI(已登记存档): 10.5281/zenodo.22176021)

以下 DOI 指向先前登记的存档,内容可能与当前正文不同。引用当前正文时,请使用本页网址。

Go Komura(2026)。《信息处理安全保障支援士 2024 年春季午后问 1 解说——JWT 的 alg=none 与 API 授权、WAF 的临时对策》。小村软件有限公司。 https://comcomponent.com/zh-CN/blog/sc-exam-r6s-pm-q1-api-security/

DOI(已登记存档)
10.5281/zenodo.22176021
DOI(上次登记版本)
10.5281/zenodo.22176022

“明明在验证 JWT,却能冒充他人”“JWT 仍然是正确的,却能读到别人的信息”。这两种情况看起来相似,但要修改的位置不同。前者是令牌验证的问题,后者是对目标数据的授权问题。

情報処理安全確保支援士試験(信息处理安全保障支援士考试)2024 年度春季午后问 1,以从智能手机调用的 API 为题材,考查如何区分这些不同的确认。前半部分涉及 JWT、用户 ID、更新字段和认证码,后半部分涉及库的漏洞与 WAF。1

本文以官方参考答案和评分讲评为基础,按 “答案要点 → 题干中的依据 → 实务中追加的设计” 的顺序说明。请把考试字数范围内要写的内容,与实际系统所需的对策区分开来阅读。23

1. 先说结论——上一步验证通过,也不能省略下一步确认

贯穿本文的原则是:不要把上一步验证的成功,当作省略下一道信任边界的理由。 所谓信任边界,是决定外部传入的值在什么条件下可以信任的分界。

持有正确的 JWT,不等于可以读取他人的信息。能够更新自己的信息,也不等于可以连计费状态一起变更。认证码的有效期和 WAF,同样各自需要独立的确认与运维。

答案全览

下表是官方答案的要点。文字类答案请在各章中确认小题的字数限制与依据。2

小题与空格 答案要点 详细说明
小题 1、a 无状态 第 3 章
小题 2(1)、b 500 秒 第 4 章
小题 2(2) 以 JWT 头部的 alg 为对象,验证其不是 NONE 第 5 章
小题 2(3) 验证 JWT 的用户 ID 与 mid 是否一致 第 6 章
小题 2(4)、c 公共模块 P 第 7 章
小题 2(5)、d 连续失败次数超过阈值就锁定账户 第 8 章
小题 3(1) 记录并确认对测试服务器 index.html 的访问 第 9 章
小题 3(2)、e、f 两者都是 Header 10.1 节
小题 3(3) 对大写和小写都能匹配的正则表达式 10.2 节
小题 3(4) 避免误报造成阻断,并精查告警 10.3 节

先在第 2 章把握题目的结构,再按小题顺序读第 3~10 章,就能跟上全文。答案的写法整理在第 12 章,用于实务设计与代码评审的检查清单整理在第 13 章。

图中实线表示始终成立的关系,虚线表示带条件的关系(成立条件写在详情页各关系的说明中)。关系的完整列表(共 18 条,附依据与确信度)以及主要概念的定义,汇总在知识地图详情页(日文)。数据:JSON-LD / Turtle

2. 题目的结构——把五种确认分开来读

题目的背景是准备新推出健康管理服务的 G 公司。用户通过智能手机应用输入饮食和体重等数据,获得健康风险判定和饮食菜单建议。系统构建在云上,组合了 API 网关、事件驱动的处理和托管数据库。

题干中的产品名和服务名做了抽象化处理。本文不直接转载试题册中的图表,而是改写出便于理解各小题的结构来说明。给出官方答案要点的部分,与实务补充分开标注。1

题目全貌把认证码、JWT、目标 ID、更新字段、来自外部输入的执行分别当作不同的确认来读。用户的请求核对认证码签发并验证 JWT用户 API目标 mid 的授权更新 status 的授权把外部输入记入日志存在漏洞的库通过 JNDI 执行外部代码

图 1:从认证到 API 操作,再到日志处理,信任值的地方不止一处。

2.1. 区分认证、令牌验证和两种授权

用认证码确认本人身份、签发并验证 JWT 之后,仍然留有对数据和字段的授权。把外部输入写入日志的路径上,也需要一道不让输入被当成命令的边界。

[用户 ID / 密码]
          |
          v
[核对 4 位认证码] ---- 无尝试次数限制 ----> 暴力破解
          |
          v
[签发 JWT]
          |
          v
[JWT 库] ------- 允许 alg=none ------> 篡改用户 ID
          |
          v
[用户 API]
    |             |
    |             +-- 原样传入 status ----> 属性级授权缺失
    |
    +-- 信任 mid -----------------------> 对象级授权缺失

[把外部输入记入日志]
          |
          v
[存在漏洞的库] ---- JNDI/LDAP/HTTP ------> 执行外部代码

这里最重要的是下面这组区分。

确认 所问的问题 本题中被突破的例子
认证 你是谁 4 位认证码的暴力破解
令牌验证 该身份信息是否被篡改 alg=none
对象级授权 是否可以访问该用户的数据 替换 mid
属性级授权 是否可以变更该字段 status=paid
从输入到执行的边界 外部输入是否会被解释为命令 JNDI Lookup

上一道确认通过,并不构成省略下一道确认的理由。 持有正确 JWT 的用户,未必可以读取他人的数据。能够更新自己数据的用户,也未必可以连计费状态一起变更。

做出这种阶段划分之后,各小题的答案就不再靠死记硬背。

认证与授权的区别即使确认了主体,仍然需要另外确认是否允许该主体操作目标数据或字段。认证与令牌验证确定请求来自谁是否可以访问该数据是否可以变更该字段

图 2:认证与授权的区别。认证在先,授权是另一道确认。

2.2. 小题 2 按攻击者改动的值来区分

下面按攻击者控制的值,梳理小题 2 中容易混淆的论点。

攻击 攻击者改动的值 不该信任的地方 根本对策
JWT 篡改 JWT 头部的 alg、载荷中的用户 ID 令牌自己申报的验证算法 在服务器端固定允许的算法
获取他人信息 请求中的 mid 客户端指定的目标 ID 与 JWT 的主体核对,或由 JWT 决定目标 ID
变更为付费用户 规格之外的 status 被自动绑定的全部属性 把可更新属性做成许可列表
突破 4 位认证码 otp 的候选值 不受限制的认证尝试 加入失败次数限制、延迟和风险判定

重要的是,不要把这些全都归结为“验证输入值”。

  • alg 是密码处理的策略
  • mid 是对象级授权
  • status 是属性级授权
  • otp 是对在线猜测的抵抗力

即使处在同一个 HTTP 请求之中,需要防护的理由也各不相同。

3. 小题 1——无状态也照样保存业务数据和失败次数

小题 1 考查 RESTful API 设计原则之一,即不进行会话管理的性质。

答案:空格 a 是“无状态”。2

3.1. 依据——仅凭每个请求本身就能凑齐处理所需的信息

无状态是指,即使服务器不记住上一个请求的会话状态,仅凭每个请求本身也能凑齐处理所需的信息。在本题中,智能手机应用会在每个请求的 Authorization 请求头中附上 JWT。服务器验证该 JWT,识别出这个请求的用户。

3.2. 容易误解之处——并不是连数据和失败次数都不保存

容易误解的地方,是把无状态读成“服务器不保存任何状态”。实际上,下列状态照常保存。

  • 保存用户信息和健康数据的数据库
  • 计费状态
  • 认证码的值、有效期和失败次数
  • JWT 的签名密钥
  • 如果设计上使用吊销列表,则还有吊销信息
  • 日志和审计记录

不保存的是:不把仅为延续会话而存在的服务器端会话状态,当作每次 API 调用的前提。

另外,无状态本身并不会自动提升安全性。每次都发送 JWT 确实便于水平扩展,但只要 JWT 的验证写错,这个错误也会一样地扩散到所有节点。架构层面的性质与安全层面的正确性是两回事。

无状态与保存的状态即使不依赖会话历史来处理每个请求,业务数据和失败次数等状态仍然要保存。带 JWT 的请求仅凭该请求识别用户执行处理并响应用户信息、计费、失败次数不需要依赖之前的会话

图 3:去掉对会话的依赖,与保存业务和安全状态,两者可以并存。

4. 小题 2(1)——计算 4 位认证码的平均突破时间

答案:空格 b 是“500”,单位是秒。2

4.1. 依据——把候选数、尝试速度和有效期放在一起看

认证 API 在用户 ID 与密码正确时,会向邮箱发送 4 位数字。之后,只要用户 ID 与 4 位认证码一致,就签发 JWT。认证码自生成起 10 分钟内有效。

在安全诊断中,每秒可以尝试 10 次。要求的是平均多少秒能够突破。

计算时取“候选数的一半”

4 位数字包含前导 0 在内,共有下面这 1 万种。

0000, 0001, 0002, ... , 9999

在正确值均匀选取、且按顺序不重复地尝试的前提下,考试中把平均尝试次数按候选数的一半计算。

平均尝试次数 = 10,000 ÷ 2 = 5,000 次
平均时间     = 5,000 ÷ 10 次/秒 = 500 秒

按这个近似计算,就得到官方答案的 500 秒。若严格地对第 1 次到第 10,000 次求平均是 5,000.5 次,这里按考试答案处理。

最坏情况需要 1,000 秒,但题目问的是平均值。而认证码的有效期是 600 秒,比平均突破时间 500 秒更长。这就是判定“被突破的可能性很高”的理由。

4 位认证码的时间量级按考试的近似算法平均为 500 秒,若不重复地尝试,在 600 秒有效期内可以查完六成候选。候选共 10000 种平均约 5000 次每秒 10 次约需 500 秒短于 600 秒的有效期600 秒可确认 6000 个候选

图 4:4 位认证码的时间量级。按候选数的一半来平均尝试,就会在有效期内命中。

4.2. 实务补充——不能只看失效时间,还要看能尝试的次数

认证码的强度,既不是只由位数决定,也不是只由有效期决定。

有效期内可尝试的次数
= 每秒尝试次数 × 有效期
= 10 × 600
= 6,000 次

如果按顺序尝试不重复的值,就能在有效期内确认 1 万种候选中的 60%。即使设置了失效时间,只要没有限制尝试次数,仍然不够。

现行的 NIST SP 800-63B 要求带外认证使用的短期秘密至少为 6 位,并规定在小于 64 bit 时必须限制尝试次数。它还要求不要把电子邮件用于带外认证4。考试中要在给定的 4 位、邮件发送这一规格内作答,但在实务的新设计中,连这个前提本身也应当重新审视。

5. 小题 2(2)——不让攻击者挑选 JWT 的验证方式

答案要点

该小题要求分别用 20 字以内回答,修正后的库 Q“对哪些数据、进行什么样的验证”。

参考答案如下。

项目 答案要点
验证对象的数据 JWT 头部中 alg 所指定的值
验证的内容 验证其不是 NONE

这才是针对题干所述漏洞的直接修正。2 只写“验证签名”,等于把已经实现的处理重复一遍。评分讲评中也指出了这类错误答案。3

5.1. 依据——已有的签名验证,其“选择方式”是错的

题目中的 JWT 由头部、载荷、签名三部分组成。

base64url(header).base64url(payload).base64url(signature)

头部中记录着签名所用的算法为 RS256。载荷中包含用户 ID、签发时间和有效期限。

诊断人员改动了下面两处。

  1. 把头部的 algRS256 改为 NONE
  2. 把载荷中的用户 ID 改为另一个用户

发送这个 JWT 后,验证通过,从而冒充了他人。

JWT alg=none 攻击的流程攻击者改动验证方式和用户 ID 后,允许 none 的库就会接受被篡改的 JWT。取得正确的 JWT把 alg 改为 none把 user 改为另一个用户库跳过签名验证按另一个用户接受请求

图 5:JWT alg=none 攻击的流程。验证算法是由攻击者挑选的。

none 并不是规范之外的值

RFC 7519 把既无签名也无加密的 JWT 定义为 algnone 的“Unsecured JWT”5。因此,none 这个值并非在规范上完全不存在。

问题在于,本应只接受带签名 JWT 的 API,却接受了攻击者指定的 none

把存在漏洞的处理写成概念形式,如下所示。

1. 读取 JWT 头部
2. 查看头部中写的 alg,选择验证方式
3. 如果 alg 是 none,就不做签名验证
4. 信任载荷中的用户 ID

这等于让攻击者控制的输入来挑选安全强度本身。

5.2. 实务补充——在服务器端固定允许的算法

这里需要把考试答案和实务中的推荐做法分开。

RFC 8725 规定,JWT 库应让调用方指定允许算法的集合,并且不得使用该集合以外的算法6。也就是下面这种思路。

不好的思路:
  只要 token.header.alg != "none" 就接受

好的思路:
  仅当算法包含在 serverConfig.allowedAlgorithms 中才接受
  例: allowedAlgorithms = ["RS256"]

只拒绝 none,仍可能残留其他弱算法,或者把公钥算法与对称密钥算法弄混的算法混淆问题。原则是:不要用否定形式不断增加接受条件,而要把允许的条件收窄并固定下来。

在 JWT 验证中,除算法之外,还应根据用途至少确认下列各项。

项目 需要确认的内容
签名 能否用预期的密钥和算法验证通过
iss 是否为受信任的签发者
aud 是否为面向本 API 签发的令牌
exp 是否仍在有效期内
nbf 是否早于可用起始时间
sub 或用户 ID 是否为应用中有效的主体
令牌种类 是否把 ID 令牌与访问令牌等弄混

本题中载荷的键名是 user,但实务中应使用标准的 sub,或者明确定义自定义声明的含义。

安全的 JWT 验证与危险的 JWT 验证不按令牌的申报决定验证方式,而是在满足服务器端的允许条件之后再验证签名和各项声明。收到 JWT是否为服务器允许的 alg拒绝验证签名与各项声明作为已验证主体处理危险的处理按申报的 none 跳过验证

图 6:安全的验证与危险的验证。实务中要把允许的算法收窄固定。

5.3. 区分签名与保密——base64url 不是加密

关于 JWT 还有一个常见的误解。头部和载荷用 base64url 表示,但这并不是加密,任何人都可以解码阅读。

签名所保证的,仅仅是在验证正确通过的前提下,内容在签发之后没有被篡改。这并不意味着可以把需要保密的个人信息放进带签名 JWT 的载荷里。

JWT 的签名与保密是两回事base64url 是第三方也能读取的表示形式,篡改检测与机密性要分开考虑。带签名的 JWT读取头部与载荷解码 base64url内容并不保密正确验证签名确认是否被篡改

图 7:签名用于检测篡改,而不是把内容藏成读不出来的形式。

6. 小题 2(3)——正确的 JWT 与正确的操作对象是两回事

答案要点

表 5 的下划线②要求用 40 字以内回答,应在公共模块 P 的调用处理中追加什么处理。

参考答案如下。2

验证 JWT 中所含的用户 ID 是否与 mid 的值一致的处理

小题指定的追加位置不是公共模块 P 本身,而是调用 P 的“P 调用处理”。要在这里追加 JWT 与 mid 的一致性核对。1

实务中的目标是,包括 P 一侧在内,在到达数据的公共层统一实施授权。这样 GET 与 PUT 两者,以及今后使用 P 的其他 API,都容易套用同一套授权。仅仅把比较处理复制到各个界面或端点,一定会出现遗漏。

6.1. 依据——JWT 的主体与操作对象的 mid 是分别传入的

接下来是不篡改 JWT 本身的攻击。

用户 API 通过 GET 或 PUT 接收名为 mid 的用户 ID。公共模块 P 从数据库获取或更新与该 mid 关联的用户信息。

攻击的结构很简单。

JWT 中的用户 ID: user01    ← 签名正确的 JWT
请求中的 mid:    user02    ← 攻击者改动

JWT 的签名正确,所以认证会通过。但 API 原样信任 mid=user02,返回 user02 的信息。

这是 OWASP API Security Top 10 2023 所说的 Broken Object Level Authorization(BOLA) 的典型情形。使用用户指定的对象 ID 访问数据时,每次都需要确认对该对象的授权7

BOLA 攻击正确 JWT 的主体与请求中的 mid 不同,却只按 mid 返回了他人的信息。签名正确的 JWT(user01)用户 API被改动的 mid(user02)不与主体核对获取或更新 user02 的数据

图 8:BOLA 攻击。认证能通过,但没有确认授权。

6.2. 实务补充——本人专用 API 不接收 mid

如果 API 只获取或更新用户本人的信息,就没有必要从客户端接收用户 ID。

GET /users/me
Authorization: Bearer <JWT>

在服务器端,从已验证的 JWT 中取出主体。

principal = validateJwt(request.authorization)
userId = principal.subject
return repository.getUser(userId)

更新也是同样的做法。

principal = validateJwt(request.authorization)
input = validateProfileUpdate(request.body)
repository.updateProfile(
    userId = principal.subject,
    name = input.name,
    age = input.age
)

比较处理只要写了就能起到防护作用。但如果设计上不从外部接收目标 ID,就能减少漏写比较处理这一类缺陷本身

如果管理员需要操作他人的用户信息,就按下面的方式拆分。

PUT /users/me                  一般用户用
PUT /admin/users/{userId}      管理员用

管理员用接口要求另一套权限、审计日志,必要时还要求重新认证。与“在一般用户 API 中只为管理员追加例外”相比,授权策略的边界更容易看清。

BOLA 的防范方式核对已验证主体与 mid 是否一致,或在本人专用 API 中由主体推导目标,减少漏做比较。接收 mid本人专用 API已验证 JWT 的主体目标 ID 的确定方式是否与主体一致操作自己的数据拒绝由主体推导目标 ID

图 9:BOLA 的防范方式。要么不接收 mid,要么与 JWT 的主体核对。

6.3. 确认——“是谁”与“可以做什么”是两回事

无论在考试还是实务中,下面的说法都很有用。

  • 认证:是谁
  • 授权:这个人可以做什么

JWT 签名验证通过,只说明“可以信任这个令牌所代表的主体”。至于“该主体是否可以读取 user02”,必须另行确认。

7. 小题 2(4)——只接收允许更新的字段

答案:空格 c 是“公共模块 P”。2

7.1. 依据——规格之外的 status 被传到了哪里

用户 API 的规格中,定义了下列更新用参数。

mid   用户 ID
name  姓名
age   年龄

但诊断人员追加了规格中没有的下列值。

status=paid

结果,免费用户的状态变成了付费用户。

根据题干,服务 L 没有验证收到的参数,而是全部传给了公共模块 P。P 的实现可以据此直接更新数据库。

因此可知,接收规格之外的值、并且能连用户状态一起更新的接收方,就是公共模块 P。

Mass Assignment把规格之外的 status 也传给公共模块并更新,用户就能自行变更计费状态。规格是 mid、name、age追加 status=paid把全部参数传给 P原样写入内部数据计费状态变成 paid

图 10:Mass Assignment。规格之外的属性被整体写入内部对象。

7.2. 与 BOLA 的区别——守的不是对象,而是对象里的字段

上一章替换 mid 与这里追加 status 看似相似,但防护的粒度不同。

漏洞 攻击者改动的内容 本应确认的内容
替换 mid 目标对象 该用户是否可以访问该用户记录
追加 status 对象内部的属性 该用户是否可以变更该字段

OWASP API Security Top 10 2023 把后者归为 Broken Object Property Level Authorization,并把以前的 Mass Assignment 纳入这一分类8

7.3. 实务补充——把更新用的输入类型做成许可列表

存在漏洞的实现,从概念上看是下面这样。

entity = repository.find(body.mid)
bindAllProperties(entity, body)
repository.save(entity)

即使界面上只有 nameage 两个输入框,攻击者也可以直接构造 HTTP 请求。界面上没有的字段,并不构成安全边界。

安全的实现会显式列出可更新的字段。

input = parseExactSchema(body, fields = ["name", "age"])

entity = repository.find(authenticatedUserId)
entity.name = input.name
entity.age  = input.age
repository.save(entity)

这里有两点很重要。

  1. 更新用的输入类型中,只放用户允许变更的字段
  2. 对规格之外的未知字段,不要忽略,尽可能作为错误拒绝

默默忽略未知字段,虽然能掩盖攻击失败这一事实,却会漏掉客户端的实现失误和攻击的征兆。如果没有兼容性方面的理由,用严格的模式直接拒绝更便于排查。

字段级授权把更新用输入类型限定为 name 与 age,计费状态则依据已验证的支付通知走专用路径更新。资料更新请求是否只有 name 与 age只更新允许的字段拒绝未知字段已验证的支付通知核对 paymentId 并防止重复通过专用路径更新 status

图 11:字段级授权。用许可列表限制可更新字段,计费状态走另一条路径变更。

7.4. 计费状态只依据已验证的支付结果变更

status=paid 不是用户资料的一部分,而是由“支付成功”这一服务器端事实推导出的状态。

用户资料更新
  -> 只能变更 name / age

来自支付服务的已验证通知
  -> 核对 paymentId
  -> 防止重复处理
  -> 把 status 变更为 paid

即使保存在同一个数据库列中,变更的权限和路径也是分开的。如果把内部实体直接用作外部 API 的输入类型,这道边界就消失了。

7.5. 公共组件要把授权和输入限制也包含进来

本题中出现了 JWT 管理库 Q 和公共模块 P 两个公共组件。

公共组件有很大的优点。

  • 改一处,就能把修正应用到所有使用它的 API
  • 不必在各个功能中重复实现授权和验证
  • 可以把测试对象集中起来
  • 可以统一日志和审计的格式

另一方面,错误也会扩散到整体。

  • 库 Q 一旦接受 alg=none,所有使用 JWT 的 API 都会变得危险
  • 公共模块 P 一旦接受任意的 midstatus,GET 与 PUT 都会变得危险
  • 存在漏洞的库 H 一旦用在基础平台上,把 HTTP 请求头写入日志的多条路径都会成为攻击面

因此,需要公共化的不只是数据访问。必须把安全不变量公共化,并对该公共组件本身做严格验证。

例如,把 P 的契约定成下面这样。

P.getOwnUser(authenticatedSubject)
P.updateOwnProfile(authenticatedSubject, ProfileUpdate{name, age})

像下面这样的低层 API,不直接开放给一般调用方更安全。

P.getUser(arbitraryMid)
P.updateUser(arbitraryMap)

需要后者的只有管理处理等受限路径。把低层的自由度分发给所有 API,就等于设计成期待每个调用方每次都正确使用。

收窄公共组件的契约把授权和输入类型纳入公共组件的契约,就能减少对调用方每次都正确使用的依赖。一般用户用 API已验证主体与更新 DTO在公共组件中统一授权操作被许可的数据任意 ID、任意 Map 的操作分离到管理用等受限路径对公共组件做严格测试

图 12:需要公共化的不只是数据访问,还有应当守住的授权与输入条件。

8. 小题 2(5)——统计失败次数,阻止暴力破解

针对 4 位认证码的暴力破解,要用 30 字以内回答表 5 空格 d 中应填入的处理。阈值是 10。

参考答案如下。2

连续失败次数超过阈值就锁定账户的处理

8.1. 依据——失败次数是安全判断所需的状态

这一点与小题 1 的无状态并不矛盾。不把 API 调用的会话状态保存为服务器会话,与把安全判断所需的失败次数持久化,是两回事。

有无尝试次数限制保存失败次数并在超过阈值时锁定,可以阻止不受限制的在线猜测。认证码核对失败更新账户的失败次数是否超过阈值锁定账户允许剩余的尝试没有限制就能一直试下去

图 13:有无次数限制。失败次数限制可以在实用层面阻止暴力破解。

8.2. 实务补充——也要防范锁定造成的拒之门外

按账户统计的次数限制是必要的,但如果攻击者知道他人的用户 ID,就能故意失败到超过阈值,把正规用户挡在门外。因此,实务中会组合下列手段。

控制手段 作用
按账户统计的失败次数 阻止针对单个账户的暴力破解
分级等待时间 容忍正规用户的输入失误,同时降低攻击速度
针对来源 IP、终端、ASN 等的控制 抑制对大量账户各试少量次数的攻击
基于风险的判定 对异常的地区、终端和速度施加更强的限制
向用户发出通知 让用户能够察觉攻击或误操作
安全的恢复流程 不让解锁窗口变成新的攻击路径

此外,重新发送认证码时不能把失败次数清零。否则攻击者每调用一次重发 API,就能恢复一批尝试额度。现行的 NIST SP 800-63B 同样要求,即使生成了新的认证秘密,也不要重置失败次数4

8.3. 重新签发、重复使用和发送 API 要作为一组来防护

题干以有效期为中心,但实务中还需要下列措施。

  • 核对成功的认证码立即作废
  • 拒绝重复使用同一认证码
  • 不把认证码本身留在日志里
  • 让响应无法从认证码核对的成败推测出用户是否存在
  • 对发送认证码的 API 同样设置次数限制

既然用的是短秘密,就不能把安全性只交给随机生成。

认证码的对策除候选数和有效期之外,还要组合失败次数的保存、成功后的作废、通知与恢复流程。签发与重新签发认证码不重置失败次数限制次数与速度后再核对核对是否成功立即作废该认证码记录失败、延迟、锁定通知与安全的恢复流程发送 API 也限流且不记录认证码

图 14:认证码的对策。不只是位数和失效时间,还要组合尝试控制与运维。

9. 小题 3(1)——用访问日志确认验证命令确实执行了

答案要点

小题 3(1) 考查在测试服务器上实现什么,才能确认命令确实被执行了。

参考答案如下。

记录并确认对测试服务器 index.html 的访问的机制2

9.1. 依据——观测到链路一直抵达最后的验证命令

服务上线后,广泛使用的开源库 H 被公布存在重大漏洞 V。题干中的流程如下。

  1. 攻击者把含有 JNDI Lookup 的字符串放进 HTTP 请求头发送
  2. 被攻击的服务器把该值输出到日志
  3. 存在漏洞的库对 JNDI Lookup 求值,向攻击用 LDAP 服务器发起查询
  4. LDAP 响应返回攻击用 HTTP 服务器的 URL
  5. 被攻击的服务器获取类文件并执行命令

这可以读作隐去了具体名称的 Log4Shell(CVE-2021-44228)型攻击。Apache 的说明中也把它描述为:当攻击者能够控制日志消息或参数时,就可以执行从 LDAP 服务器加载的任意代码的漏洞9

Log4Shell 型漏洞的确认流程不只看 JNDI 引用和类文件获取,还要用日志确认验证命令取走了 index.html。HTTP 请求头中的外部输入目标服务器的日志处理通过 JNDI 查询 LDAP收到获取类文件的 URL目标服务器获取类文件在目标服务器上执行验证命令取走测试服务器的 index.html记录并确认这次访问

图 15:Log4Shell 型漏洞的确认流程。不用破坏性命令,而用 HTTP 访问记录来确认抵达。

验证命令引发的只是无害的 HTTP 访问

G 公司执行不影响系统的验证代码,确认能否从外部利用漏洞 V。验证代码执行的命令,只是获取测试服务器的 index.html

只要 Web 服务器的访问日志中留下了来自被攻击服务器的 GET,就至少可以确认下面这条链路是通的。

外部 HTTP 请求
  -> 日志处理
  -> JNDI Lookup
  -> LDAP 响应
  -> 获取类文件
  -> 执行验证命令
  -> 对测试服务器的 HTTP 访问

9.2. 为什么看的是测试服务器的日志,而不是界面显示

攻击目标是服务器,用户的浏览器界面未必会出现变化。而且即使漏洞存在,中途的出站通信也可能被防火墙拦下。

在测试服务器一侧记录访问,就能得到“从被攻击服务器到达了外部”这一可观测的证据。

9.3. 实务补充——取得批准,用最小副作用来确认

在实务中做同类验证时,必须遵守下列各项。

  • 取得目标系统所有者的明确批准
  • 采用对生产环境没有影响、或影响可接受的验证方法
  • 不使用写入、删除、变更配置等破坏性命令
  • 验证用的域名和服务器由本公司自行管理
  • 记录验证时刻、来源、目标以及期待的回调
  • 验证结束后撤除临时的 LDAP、HTTP 服务器和凭据

“确认存在任意代码执行”与“执行任意危险代码”并不是一回事。要把副作用压到满足目的的最小程度。

把验证限制在最小副作用取得批准后确定无害的确认方法与观测条件,执行完毕再撤除验证环境和凭据。所有者的明确批准确定影响与观测条件在自管服务器上记录访问与预期的时刻和来源核对撤除验证环境与凭据不做写入、删除、配置变更

图 16:先确定想观测的事实,再把无害确认和事后清理都纳入验证步骤。

10. 小题 3(2)~(4)——确定 WAF 的检查位置、模式与动作

10.1. 小题 3(2)——两个检查对象都是 Header

服务 N 的 WAF 可以把 GET、POST、PUT、ANY、Header、COOKIE、Multipart 选作检查对象。

攻击代码放在名为 x-api-version 的 HTTP 请求头的值中。因此,表 6 的空格 e 和 f 都是 Header2

依据是攻击字符串所在的位置

这里与其说考一般知识,不如说考读懂题干的数据流。

攻击字符串的存放位置:
  x-api-version 请求头
          |
          v
WAF 的检查对象:
  Header

本题中的 ANY 是指检查所有方法的参数值,与检查请求头的 Header 不同。1

它既不是 GET 参数,也不是 POST 正文。不要看着 WAF 的功能列表,觉得“像是攻击就选 ANY”,而要回答题干中攻击者放入值的位置

选择 WAF 的检查位置不按攻击名称,而按题干中攻击字符串的存放位置来确定 WAF 的检查对象。读出攻击字符串的存放位置x-api-version 请求头检查对象是 Header转向包含大小写的模式ANY 指所有方法的参数

图 17:不要从攻击名称去猜,而要把请求头这个存放位置对应到检查对象。

10.2. 小题 3(3)——对每个字符都允许大写和小写

最初的方案,从概念上看是下面这条规则。

Header  \Wjndi\W  阻断
Header  \Wldap\W  阻断

但像 jNdI 那样交换大小写,就能绕过只写小写的简单模式。

小题 3(3) 的参考答案是下面任意一个。2

\W[jJ][nN][dD][iI]\W
\W(j|J)(n|N)(d|D)(i|I)\W

在试题册中,反斜杠可能因日文环境的字形而显示成日元符号,但作为正则表达式它就是 \W\W 匹配字母、数字和下划线以外的字符。在 JNDI Lookup 的语法中,jndi 前后会出现 ${: 等非单词字符,所以把它们也一并纳入匹配。

按同样的思路,ldap 一侧也可以写成忽略大小写的形式。

\W[lL][dD][aA][pP]\W

10.3. 小题 3(4)——检测是为了避免误阻断

关于修改后的 WAF 规则,注册安全工程师 Z 建议,在生产环境上线后的一段时间内,把动作设为“检测”而不是“阻断”。

该小题要求分别用 25 字以内回答,设为检测的好处,以及为把损失降到最小应当实施的内容。

参考答案如下。2

项目 答案要点
好处 可以避免因误报造成阻断
应当实施的内容 收到告警后精查是否为攻击

依据——检测必须与精查告警的运维成套使用

在检测模式下,与规则匹配的通信照样放行,同时记入日志并发出告警。即使正常的 API 调用中偶然出现 jndildap 字符串,也不会突然中断业务。

作为代价,运维一侧需要下面这套流程。

收到告警
   |
   v
确认相关请求
   |
   +-- 正常通信 -> 收窄规则,考虑例外条件
   |
   +-- 攻击     -> 隔离对象、保全日志、调查影响、转入阻断

如果没有人看告警,检测模式就没有任何防护效果。检测必须与观测并判断的运维成套使用

10.4. 实务补充——先观测、调整,再转入阻断

一般的导入步骤如下。

  1. 以检测模式作用于真实流量
  2. 区分误报与真实检出
  3. 调整目标请求头、路径、API、字符边界等
  4. 确认对正常通信的影响可以接受
  5. 切换到阻断模式
  6. 监控阻断数量与业务影响

不过,这是平时的原则。当漏洞重大、已被实际利用且没有替代手段时,也可能判断入侵造成的损失大于误报导致的中断,从一开始就选择阻断。在考试给出的情境中,为了确认服务能否照常使用,先选择了检测。

从 WAF 检测转向阻断精查检测期间的告警,调整误报并处置攻击之后再转入阻断,转入后继续监控。误报攻击以检测模式观测通信告警的内容是什么调整规则或例外条件隔离、保全日志、调查影响转入阻断确认对正常通信的影响监控阻断数量与业务影响

图 18:从检测到阻断。先观测和调整,确认影响可接受后再转入阻断。

11. 实务中的漏洞应对——用 WAF 争取时间,同时推进更新与调查

在题干中,库 H 的官方网站上既没有修正版也没有临时对策,云服务商的全面 WAF 规则最长还要 72 小时才能提供。所以 G 公司自行确认影响,并先把已经查明的模式临时挡住。

考试考的是临时 WAF 规则及其运维,但 WAF 并不能修复漏洞本身。 影响确认、临时缓解、根本修复不必互相等待,能并行的部分就并行推进。包含实务补充在内的整体应对如下。

阶段 目的 本题与实务中的做法
影响确认 判断自己是否真的处于危险中 用无害的回调确认能否被外部利用
临时缓解 为修复争取时间 WAF 规则、检测与阻断、出站通信限制
根本修复 消除漏洞的成因 更新到修正版的库
事后确认 调查是否已经被利用 排查 WAF、应用、DNS、代理等日志
防止再发 让下次判断更快 依赖关系盘点、SBOM、更新流程、联络渠道

11.1. 不要把临时规则当成完整的 Log4Shell 对策

考试要求回答的,是针对题干所示绕过手法的正则表达式。在实际攻击中,还可能出现字符串拆分、其他 Lookup、编码、其他协议等仅靠特征签名难以覆盖的变形。

因此,它在实务中的定位如下。

  1. 用 WAF 临时挡住当前已查明的攻击模式
  2. 排查是否真的包含受影响的库
  3. 限制出站的 LDAP、RMI 和不必要的 HTTP 通信
  4. 更新到修正版
  5. 更新之后也要查看日志,调查是否已被入侵

WAF 是在修正版发布之前争取时间的一层。

WAF 的定位检测放行通信并加以观测,阻断与出站通信限制用于缓解,根本原因则通过更新消除。重大漏洞公布影响确认与临时应对用检测取得日志与告警阻断与出站通信限制根据观测进行调整与处置更新到修正版的库事后确认是否已被入侵

图 19:WAF 的定位。WAF 只是在修正版发布前争取时间,根本对策是更新。

11.2. 平时的准备——掌握所用的库及其部署位置

题干中提到,G 公司向 F 公司询问是否使用了库 H 时,对方需要做详细的构成分析,回答要花时间。

在实务中,如果等到重大漏洞公布之后才开始找 JAR 文件,处置就会滞后。至少应该在平时就准备好下列内容。

  • 直接依赖与传递依赖的清单
  • 发布包中实际包含的组件及其版本
  • 部署到了哪些服务、容器和终端
  • 更新依赖库并重新构建、重新分发的流程
  • 审批紧急变更的联络渠道
  • 出站通信的允许目标,以及停用时的影响
  • 日志的保存位置与检索方法

SBOM 本身不是目的,而是为了在短时间内回答“这个漏洞会影响哪些正在运行的系统”的索引

把依赖关系与运行中的系统关联起来平时就把依赖组件与分发目标对应起来,加快漏洞公布后的调查与更新判断。直接依赖与传递依赖的清单发布包中的实际版本运行中的服务与部署位置短时间内判断受影响范围审批、重新构建、重新分发同时掌握通信目标与日志位置

图 20:SBOM 的目的不是收集本身,而是快速判断影响范围与更新方式的索引。

11.3. 事后确认——不能更新完就结束入侵调查

在漏洞公布前后,可能已经遭到过攻击。即使更新到修正版、阻止今后被利用,也消不掉已经泄露的凭据和已被植入的后门。

如果是 Log4Shell 型漏洞,至少要从下列角度排查。

  • 含有 JNDI 或 LDAP 相关可疑字符串的 HTTP 请求
  • 从应用服务器向外部 LDAP、RMI、HTTP 的通信
  • 与平常不同的子进程启动
  • 可疑的 JAR、class、脚本和可执行文件的创建
  • 对云凭据和环境变量的访问
  • 更新前后的认证、权限变更和对外发送

重要的是,不要仅凭 WAF 日志就断定“没有被攻击”。因为存在不经过 WAF 的内部路径,也存在过去没有保存下来的日志。

把更新与入侵调查分开更新到修正版以防止今后被利用,与调查是否已经被入侵,是两件都需要做的事。更新到修正版阻止今后被利用排查更新前后的记录核对通信、进程与文件确认凭据与对外发送过去的入侵不会因此消失

图 21:修复成因与调查已经发生的入侵,要分开推进。

12. 答案的写法——用题干的用语回答与规格的差异

与其说本题是知识题,不如说是读出规格与实现之间差异的题目。

12.1 把表中的“规格”与“实现”分开

status 的问题上,API 规格中没有的值在实现中却能通过。

规格:
  mid / name / age

实现:
  把收到的参数全部发给 P

只要看清这个差异,就能确定空格 c 是公共模块 P。

12.2 给攻击者改动的值划线

各次攻击中被改动的值如下。

  • JWT 头部的 alg
  • JWT 载荷中的用户 ID
  • API 参数中的 mid
  • 规格之外的 status
  • 认证 API 的 otp
  • HTTP 请求头 x-api-version

各小题问的几乎都是“这个值应该在哪里被验证”。

12.3 把答案还原成题干的用语

在实务中可以称之为“BOLA”“Mass Assignment”“rate limiting”。但小题要求的,是贴合题干结构的具体处理。

不好的示例:

适当地进行授权。

好的示例:

验证 JWT 中所含的用户 ID 是否与 mid 的值一致。

不好的示例:

做暴力破解的防护。

好的示例:

连续失败次数超过阈值就锁定账户。

只知道抽象名称,写不出在字数限制内可评分的答案。

12.4 对 WAF 要追查“值放进了哪里”

WAF 的检查对象不是从攻击类型去推测,而是由攻击字符串的存放位置决定。

放进了 x-api-version 请求头
        ↓
检查对象是 Header

评分讲评指出,整体正确率处于平均水平,小题 2(2) 和小题 3(1) 略低。3 小题 3(1) 正确率偏低,也是因为不少答案与图 6 的攻击流程对不上。哪怕只是把攻击步骤用箭头重写一遍,也能看清应该观测什么。

从题干回到答案追查规格与实现的差异、被改动的值、在哪里信任了它,再用题干的用语写出具体处理。把规格与实现并排列出确定被改动的值追查在哪里信任了它确定要追加的处理还原成题干的用语与字数

图 22:不要只背术语,而要追踪值的流动,把答案写成具体的处理。

13. 实务 API 评审中使用的检查清单

下面是把本题带回实际设计与代码评审时使用的检查清单。

JWT 验证

  • 在服务器配置中固定允许的签名算法
  • 拒绝 none 和预期之外的算法
  • 根据用途验证签名、issaudexpnbf
  • 不混淆 ID 令牌、访问令牌和刷新令牌
  • 具备密钥轮换与吊销时的处理流程
  • 不把需要保密的信息放入 JWT 载荷

对象级授权

  • 改动请求中的 ID 后,无法访问他人的数据
  • 列表、详情、更新、删除、下载全都做了授权
  • 授权不放在界面层,而在到达数据的公共层实施
  • 在本人专用 API 中,已考虑过能否由令牌推导出目标 ID
  • 管理员用的操作与一般用户用 API 的策略是分开的

属性级授权

  • 外部输入类型与数据库实体是分开的
  • 用许可列表逐项列出了可更新的字段
  • 对规格之外的属性做了拒绝或审计
  • 权限、计费、审批、所有者等状态无法由用户输入变更
  • 响应中也没有包含不必要的机密属性

认证尝试

  • 具备按账户统计的失败次数限制
  • 具备分级延迟或按来源的控制
  • 重新签发认证码不会重置失败次数
  • 认证码只能使用一次
  • 不把认证码和密码留在日志里
  • 解锁与恢复流程没有变成另一条弱认证路径

重大的依赖库漏洞

  • 能够把运行中的服务与依赖版本对应起来
  • 具备用无害方式验证影响的步骤
  • 可以实施 WAF、出站通信限制等临时措施
  • 有专人查看检测告警的运维安排
  • 具备更新到修正版的紧急发布通道
  • 会从日志中调查更新前是否已被利用
评审要测正常流程之后的一步不只确认正常请求能成功,还要分别确认改动 ID 与字段、反复尝试的情况,以检验公共层的对策。确认正常请求能成功只改动目标 ID 再确认追加未知的更新字段再确认反复进行认证失败与重新签发确认拒绝、记录与恢复把它们当作各自独立的确认

图 23:以正常流程的成功为起点,确认各道边界是否真的能够拒绝。

14. 小结——不要省略下一道信任边界

2024 年度春季午后问 1,是一道要把 API 安全的各个论点逐一分离来读的题目。

用了 JWT,并不意味着认证是安全的。一旦让攻击者挑选签名算法,用户 ID 就能被改写。

JWT 的签名正确,并不意味着授权正确。只要信任请求中的 mid,正规用户就能访问他人的信息。

能够更新自己的对象,并不意味着可以变更全部属性。一旦把 status 这类内部状态自动绑定,权限和计费状态就会被改写。

认证码设有有效期,并不意味着它能抵抗暴力破解。必须计算候选数与尝试速度,并限制失败次数。

给 WAF 加上规则,并不意味着漏洞已经修复。要用检测和阻断争取时间、确认影响,最终还是要更新库。

漏洞与对策的对应表按令牌与认证尝试、目标与更新字段、来自外部输入的执行这几道边界分别准备对策。把信任的边界分开令牌与认证尝试目标数据与更新字段来自外部输入的执行固定允许的 alg 并限制尝试核对主体并限定更新 DTO临时缓解与更新库

图 24:漏洞与对策对应表。按被突破的边界分别准备对策。

贯穿本题的原则只有一条。

不要把上一步验证的成功,当作省略下一道信任边界的理由。

本系列此前的文章解说了2023 年秋季午后问 1 的存储型 XSS2023 年秋季午后问 2 从访客用 Wi-Fi 带出信息。关于网站整体的检查视角,还可以参考把 IPA《安全网站构建指南》当作检查清单来使用

最终小结不能因为上一道确认通过,就省略下一道确认或运维层面的对策。另行确认因为通过了就省略上一步验证通过下一道确认是否也满足逐道边界累积判断留下未确认的边界落实到日常的设计与验证

图 25:最终小结。信任边界要逐级确认,不能省略其中任何一道。

参考链接

  1. IPA,2024 年度春季 信息处理安全保障支援士考试 午后试题册。本文所讨论的题干。  2 3 4

  2. IPA,2024 年度春季 信息处理安全保障支援士考试 午后参考答案。各小题的官方参考答案。  2 3 4 5 6 7 8 9 10 11 12

  3. IPA,2024 年度春季 信息处理安全保障支援士考试 午后评分讲评。对正确率与错误倾向的说明。  2 3

  4. NIST,SP 800-63B: Authentication and Authenticator Management。给出了短期秘密的位数、尝试次数限制、重新签发时的失败次数,以及不把电子邮件用于带外认证等要求。  2

  5. RFC Editor,RFC 7519: JSON Web Token (JWT)。包含 Unsecured JWT 与 alg=none 的 JWT 规范。 

  6. RFC Editor,RFC 8725: JSON Web Token Best Current Practices。规定固定允许算法,以及验证签发者、主体和 audience 等内容的 BCP。 

  7. OWASP,API1:2023 Broken Object Level Authorization。说明需要针对用户指定的每个对象 ID 确认授权。 

  8. OWASP,API3:2023 Broken Object Property Level Authorization。说明包含 Mass Assignment 在内的属性级授权缺失及其对策。 

  9. Apache Logging Services,Security。说明 CVE-2021-44228 的影响、经由 JNDI 与 LDAP 的代码执行,以及修正版。 

共享相同标签的最新文章。可以围绕相近的主题进一步加深理解。

与本文相近的主题页面。以本文为起点,可以进一步了解相关服务和其他文章。

本文与以下服务页面相关联,欢迎从最接近的入口查看。

网站制作

因为在会员 API 和手机应用联动中,JWT 验证、对象级授权、可更新属性的限制,直接关系到 Web 系统的安全性。

常见问题

汇总了咨询这一主题时常见的问题。

JWT 的签名验证已经通过,为什么还能冒充他人?
在本题中,JWT 管理库按攻击者指定的方式接受 JWT 头部的 alg,把 alg=none 的 JWT 当作无签名却正确的令牌来处理。因此,即使改写了载荷中的用户 ID,验证依然会通过。考试的答案是:以 JWT 头部的 alg 为验证对象,确认其值不是 NONE。不过在实务中,只拒绝 NONE 并不够。应在服务器端的配置中把允许使用的算法固定为 RS256 等,不让令牌自行申报的算法直接决定选择。此外还要根据用途验证 issuer、audience、有效期限、subject 等。
只要比较 JWT 中的用户 ID 与请求中的 mid,作为授权对策就足够了吗?
作为本题的答案已经足够。在公共模块 P 中验证 JWT 所含用户 ID 与 mid 是否一致,就能阻止指定他人 mid 的攻击。不过,如果 API 只处理用户本人的信息,实务中更安全的设计是不从客户端接收 mid,而由已验证 JWT 的 subject 决定用户 ID。例如设计成 GET /users/me 或 PUT /users/me,就能减少漏写比较处理这类缺陷本身。管理员操作他人的 API,则拆分为另外的端点和授权策略。
追加 status 的攻击,为什么只靠通常的输入值验证防不住?
因为即使验证了 name 的长度或 age 的范围,只要把本来就不该接收的 status 自动绑定并传给内部对象,就防不住。问题不在于值的格式,而在于是否允许用户变更该属性,也就是属性级授权。更新用的输入类型中只定义 name 和 age,未知属性一律拒绝。计费状态必须只依据支付服务的成功结果等服务器可信的事件来变更。
4 位认证码 10 分钟就会失效,为什么仍然危险?
因为从 0000 到 9999 的候选只有 1 万种,每秒可以试 10 次时,平均 5,000 次、也就是 500 秒就能命中。有效期 10 分钟等于 600 秒,按顺序尝试不重复的候选,就能在有效期内确认 6,000 种。只有失效时间挡不住暴力破解,必须把候选数、尝试速度和尝试次数上限放在一起设计。
小题给出的对策是锁定账户,实务中只做立即锁定就够了吗?
不够。题目的空格中要填的是“连续失败次数超过阈值就锁定账户的处理”,但如果只做固定的永久锁定,攻击者就能故意让他人的账户被锁定,造成服务妨害。实务中要组合按账户统计的失败次数、分级等待时间、对来源和终端的风险判定、通知以及恢复流程。签发新认证码时不把失败次数清零,同样重要。
把 WAF 设为检测而不是阻断,意义何在?
意义在于,即使把正常字符串误判成攻击,也不会中断业务通信。题目的答案中,好处是可以避免因误报造成阻断,应当实施的内容是收到告警后精查是否为攻击。检测模式不是用来放着不管的配置,而是查看日志、排除误报、调整规则,为转入阻断做准备的观测期。当已知的重大漏洞正在被实际利用的紧急情况下,也可能权衡可用性风险,判断先阻断为好。
本题中的库 H 就是 Log4j 吗?
题干隐去了产品名,但 JNDI Lookup、LDAP 服务器、从 HTTP 服务器获取类文件、嵌入 HTTP 请求头的字符串、CVSS v3.1 较高的基本评分,这一连串攻击流程,自然可以读作对以 Log4Shell 之名为人所知的 CVE-2021-44228 的抽象化。本文会说明这种对应关系,但考试中不需要回答具体名称,凭给出的攻击步骤和 WAF 规格就能作答。
从本题中应该带回实务的是什么?
认证通过、JWT 未被篡改、可以访问目标对象、可以变更目标属性,这四件事全都是彼此独立的确认。此外,短位数的认证码需要尝试次数限制,而面对重大的库漏洞,影响确认、临时防御与根本修复要并行推进。把授权集中到公共组件、把输入模式做成许可列表、在服务器端固定 JWT 的验证条件、掌握依赖库并保持随时可更新的状态,是实务中的核心。

作者简介

本文作者的个人简介页面。

Go Komura

小村软件有限公司 代表

以 Windows 软件开发、技术咨询与故障排查为中心,擅长难以复现的故障调查,以及既有资产仍在运行的项目。

返回博客列表