app/requirements-rp8.2.txt(wheel 哈希锁定,防依赖漂移):RestrictedPython==8.2 \
--hash=sha256:6ff69e2fe06674bfe88943f17f2ce8353a9bc743ee37ea7bec38ff8b4851afd1
app/requirements-rp8.3.txt:RestrictedPython==8.3 \
--hash=sha256:de36a9b27ad4490a368984984751179e5fc28e2b49540dc348878e5c0e79d737
Dockerfile(基础镜像 digest 锁定在 2026-09-01 的 python:3.12-slim;要换最新 tag,可先 docker pull python:3.12-slim 再用 docker image inspect python:3.12-slim --format '{{index .RepoDigests 0}}' 重新取 digest):端口被占用时,可用环境变量覆盖:
VULN_PORT=9020 FIXED_PORT=9021 docker compose up -d --build。专业黑客业务:渗透网站/网址 棋牌入侵破解 脚本搭建 ddos攻击/cc攻击 舆论删除/负面删除 BC博彩/投顾/股票/电子签/贷款/各类资料 学信网录入/改分/改档案 盘口改单/不中改中 域名劫持/Dns劫持 Pg赔率/爆率 Gov站点业务
技术小黄鸭🟡:@Xiaohyaa
safer_getattr 是库自带的安全 getattr 替代品——拒绝 _ 前缀属性、INSPECT_ATTRIBUTES 受限名,以及 str 上的 format / format_map(safe_builtins['_getattr_'] 的默认值就是它)。policy_getattr 记录完日志后拿它兜底"正常访问",这是嵌入方的典型姿势:自己的策略套在库提供的安全原语外面。在两个容器内运行(方案 B/C 则在对应 venv / PYTHONPATH 下运行同一文件):
docker compose exec -T sandbox-vuln python /srv/poc_local.py # exit 0
docker compose exec -T sandbox-fixed python /srv/poc_local.py # exit 2
两个版本的实测输出(容器内真实执行):
# 8.2
RestrictedPython version: 8.2
f(O()) -> 'shadowed' (policy hook calls: [])
VERDICT: VULNERABLE (policy hook was shadowed, access policy bypassed)
# 8.3
RestrictedPython version: 8.3
compile_restricted raised SyntaxError: ('Line 2: "_getattr_" is an invalid variable name because it starts with "_"',)
VERDICT: PATCHED (guard shadowing is rejected at compile time)
注意判定条件的设计:
calls == [] 证明的是受信策略 hook 一次都没执行,而不是"读到了 secret"。这个区分后面还会反复出现——它决定了我们能把结论推多远。专业黑客业务:渗透网站/网址 棋牌入侵破解 脚本搭建 ddos攻击/cc攻击 舆论删除/负面删除 BC博彩/投顾/股票/电子签/贷款/各类资料 学信网录入/改分/改档案 盘口改单/不中改中 域名劫持/Dns劫持 Pg赔率/爆率 Gov站点业务
技术小黄鸭🟡:@Xiaohyaa
visit_Attribute / visit_Subscript / inject_print_collector 是三个最典型的物化点(8.2 源码,注释为补注):inject_print_collector 只是 print 改写的第二步;第一步在 visit_Name 里——它把 print / printed 两个名字改写成对局部临时变量 _print 的访问(8.2 源码,注释为补注):步合起来(用
ast.unparse 还原 8.2 transformer 的实际产物):def f():
_print = _print_(_getattr_) ← 第二步:函数开头注入的赋值
_print._call_print('hi') ← 第一步:print 名字的改写
这正是 P4 的着力点。
_print = _print_(_getattr_) 在函数体执行之前求值:右边的裸名字 _print_ 解析到被 posonly 参数遮蔽的局部绑定(攻击者的 lambda),_getattr_ 在 f 内没有局部绑定、解析到真实的全局受信 hook。于是在受信 hook 一无所知的情况下,它自己作为实参被送进了攻击者的函数——这就是 P4 返回值里 <function build_policy.<locals>.policy_getattr at 0x...> 的由来。两个后续细节:攻击者 lambda 的返回值是 None,_print 即为 None,于是 print(...) 触发的 _print._call_print(...) 抛 AttributeError——P4 的 payload 因此用 try/except 包住 print;而 lambda 体内 out.append(...) 这类访问没有局部遮蔽,仍走真实全局 hook(实测 P4 的 access_log 为 [['getattr', 'append']])——所以 P4 的判定条件只要求"捕获到 hook 对象",不要求日志为空。专业黑客业务:渗透网站/网址 棋牌入侵破解 脚本搭建 ddos攻击/cc攻击 舆论删除/负面删除 BC博彩/投顾/股票/电子签/贷款/各类资料 学信网录入/改分/改档案 盘口改单/不中改中 域名劫持/Dns劫持 Pg赔率/爆率 Gov站点业务
技术小黄鸭🟡:@Xiaohyaa
参数在 AST 里是
顺带把"调用点是否完备"也钉死:参数检查的调用点全局只有
专业黑客业务:渗透网站/网址 棋牌入侵破解 脚本搭建 ddos攻击/cc攻击 舆论删除/负面删除 BC博彩/投顾/股票/电子签/贷款/各类资料 学信网录入/改分/改档案 盘口改单/不中改中 域名劫持/Dns劫持 Pg赔率/爆率 Gov站点业务
技术小黄鸭🟡:@Xiaohyaa
ast.arg(arg='_getattr_', ...),只有 node.arg 一个字符串属性,不是 ast.Name——visit_Name 根本看不见它。它唯一的检查入口就是 check_function_argument_names();这个函数漏了,后面就没有任何兜底。顺带把"调用点是否完备"也钉死:参数检查的调用点全局只有
visit_FunctionDef / visit_Lambda 两处。ast.arguments 节点在合法语法中只出现在 FunctionDef / AsyncFunctionDef / Lambda 三种宿主下,而 visit_AsyncFunctionDef 直接 not_allowed;visit_arguments / visit_arg 本身只是内容透传,不做检查。因此参数类绑定点不存在第三条漏网路径——缺口只可能在每个调用点内部的字段枚举里,posonlyargs 正是这样出现的。专业黑客业务:渗透网站/网址 棋牌入侵破解 脚本搭建 ddos攻击/cc攻击 舆论删除/负面删除 BC博彩/投顾/股票/电子签/贷款/各类资料 学信网录入/改分/改档案 盘口改单/不中改中 域名劫持/Dns劫持 Pg赔率/爆率 Gov站点业务
技术小黄鸭🟡:@Xiaohyaa
查询目标:8.2 transformer 源码中所有
运行方式(CodeQL CLI 2.26.1;先把查询保存为
结果:13 个物化点,对应 9 个唯一 guard 名:
专业黑客业务:渗透网站/网址 棋牌入侵破解 脚本搭建 ddos攻击/cc攻击 舆论删除/负面删除 BC博彩/投顾/股票/电子签/贷款/各类资料 学信网录入/改分/改档案 盘口改单/不中改中 域名劫持/Dns劫持 Pg赔率/爆率 Gov站点业务
技术小黄鸭🟡:@Xiaohyaa
ast.Name('<name>', ast.Load()) 形态的调用构造,<name> 限定为 9 个已知 guard 名:import python
string guardName() {
result in [
"_getattr_", "_getitem_", "_write_", "_print_",
"_getiter_", "_apply_", "_unpack_sequence_",
"_inplacevar_", "_iter_unpack_sequence_"
]
}
from Call c, string name, Function f
where
f = c.getScope().(Function) and
c.getFunc().(Attribute).getName() = "Name" and
c.getFunc().(Attribute).getObject().(Name).getId() = "ast" and
name = c.getArg(0).(StringLiteral).getText() and
name = guardName()
select c,
f.getQualifiedName() as enclosing_function,
name as injected_guard,
c.getLocation().getStartLine() as line
运行方式(CodeQL CLI 2.26.1;先把查询保存为
q5_guard_sinks.ql,同目录放一个声明依赖的 qlpack.yml,内容为 name: cve-2026-55830-rp、version: 0.0.1、dependencies: { codeql/python-all: 7.2.2 },然后 codeql pack install):# 建库:对 2.1 方案 C 的 8.2 源码快照
codeql database create rp8.2-db --language=python --source-root=rp-src-8.2
# 运行查询
codeql query run q5_guard_sinks.ql --database rp8.2-db
结果:13 个物化点,对应 9 个唯一 guard 名:
专业黑客业务:渗透网站/网址 棋牌入侵破解 脚本搭建 ddos攻击/cc攻击 舆论删除/负面删除 BC博彩/投顾/股票/电子签/贷款/各类资料 学信网录入/改分/改档案 盘口改单/不中改中 域名劫持/Dns劫持 Pg赔率/爆率 Gov站点业务
技术小黄鸭🟡:@Xiaohyaa
本地 diff 复核 8.2 → 8.3 的 transformer,
同一提交新增 3 个回归测试:
专业黑客业务:渗透网站/网址 棋牌入侵破解 脚本搭建 ddos攻击/cc攻击 舆论删除/负面删除 BC博彩/投顾/股票/电子签/贷款/各类资料 学信网录入/改分/改档案 盘口改单/不中改中 域名劫持/Dns劫持 Pg赔率/爆率 Gov站点业务
技术小黄鸭🟡:@Xiaohyaa
check_function_argument_names() 是唯一的源码差异:def check_function_argument_names(self, node):
+ for arg in node.args.posonlyargs:
+ self.check_name(node, arg.arg)
+
for arg in node.args.args:
self.check_name(node, arg.arg)
同一提交新增 3 个回归测试:
def test_positional_only_arg_with_underscore_is_rejected():
result = compile_restricted_exec("def foo(_bad, /): pass")
assert result.errors == (functiondef_err_msg,)
def test_positional_only_arg_with_default_underscore_is_rejected():
result = compile_restricted_exec("def foo(_bad=1, /): pass")
assert result.errors == (functiondef_err_msg,)
def test_positional_only_lambda_arg_with_underscore_is_rejected():
result = compile_restricted_exec("f = lambda _bad, /: None")
assert result.errors == (functiondef_err_msg,)
专业黑客业务:渗透网站/网址 棋牌入侵破解 脚本搭建 ddos攻击/cc攻击 舆论删除/负面删除 BC博彩/投顾/股票/电子签/贷款/各类资料 学信网录入/改分/改档案 盘口改单/不中改中 域名劫持/Dns劫持 Pg赔率/爆率 Gov站点业务
技术小黄鸭🟡:@Xiaohyaa
mock UDM 的实际响应逻辑如下。它只在
回归,不能将结果外推至 EAP-AKA':该 mock 不生成 EAP 向量。
mock UDM 固定返回以下认证向量:
这是脱敏 mock 向量,不代表真实运营商凭据,不触及真实订户或公共网络。
复现所需的 mock 语义、Compose 配置、请求命令、判定条件和清理命令均在第 4 节与第 6.4 节直接给出;不以单独的文件哈希表作为阅读前提。
专业黑客业务:渗透网站/网址 棋牌入侵破解 脚本搭建 ddos攻击/cc攻击 舆论删除/负面删除 BC博彩/投顾/股票/电子签/贷款/各类资料 学信网录入/改分/改档案 盘口改单/不中改中 域名劫持/Dns劫持 Pg赔率/爆率 Gov站点业务
技术小黄鸭🟡:@Xiaohyaa
POST .../security-information/generate-auth-data 时返回 5G_AKA 向量,且对POST .../auth-events 始终返回 201 Created。因此,本文只能对 5G-AKA 做端到端回归,不能将结果外推至 EAP-AKA':该 mock 不生成 EAP 向量。
mock UDM 固定返回以下认证向量:
supi = imsi-2089300007487
rand = b8a6c1d2e3f405162738495a6b7c8d9e
xresStar = 0123456789abcdef0123456789abcdef ← 泄露目标
autn = 5449ab1c9c91f9f60000000000000000
kausf = 00112233445566778899aabbccddeeff00112233445566778899aabbccddeeff
这是脱敏 mock 向量,不代表真实运营商凭据,不触及真实订户或公共网络。
复现所需的 mock 语义、Compose 配置、请求命令、判定条件和清理命令均在第 4 节与第 6.4 节直接给出;不以单独的文件哈希表作为阅读前提。
专业黑客业务:渗透网站/网址 棋牌入侵破解 脚本搭建 ddos攻击/cc攻击 舆论删除/负面删除 BC博彩/投顾/股票/电子签/贷款/各类资料 学信网录入/改分/改档案 盘口改单/不中改中 域名劫持/Dns劫持 Pg赔率/爆率 Gov站点业务
技术小黄鸭🟡:@Xiaohyaa
从 UDM 取得认证向量,字段为十六进制字符串:
对应源码同时展示了输入字段、派生过程、上下文保存和返回给调用方的字段:
专业黑客业务:渗透网站/网址 棋牌入侵破解 脚本搭建 ddos攻击/cc攻击 舆论删除/负面删除 BC博彩/投顾/股票/电子签/贷款/各类资料 学信网录入/改分/改档案 盘口改单/不中改中 域名劫持/Dns劫持 Pg赔率/爆率 Gov站点业务
技术小黄鸭🟡:@Xiaohyaa
Rand、Autn、XresStar、Kausf。HXRES* = SHA256(hexDecode(Rand || XresStar))[16:](internal/sbi/processor/ue_authentication.go:376、internal/sbi/processor/ue_authentication.go:392–393)。HXRES* 是由 RAND || XRES* 派生的期望响应摘要,并通过 5gAuthData 的 SBI 响应返回给 AMF/SEAF。源码仅能证明它不同于原始 XRES*,且会跨越这一 SBI 边界;不能据此笼统认定它是“公开或非机密数据”。UE 经无线/NAS 路径接收的是挑战材料,而非该 SBI 响应对象;本漏洞直接泄露的是原始 XRES*。Kseaf = KDF(Kausf, FC_FOR_KSEAF_DERIVATION, P0=servingNetworkName)(internal/sbi/processor/ue_authentication.go:414)。对应源码同时展示了输入字段、派生过程、上下文保存和返回给调用方的字段:
专业黑客业务:渗透网站/网址 棋牌入侵破解 脚本搭建 ddos攻击/cc攻击 舆论删除/负面删除 BC博彩/投顾/股票/电子签/贷款/各类资料 学信网录入/改分/改档案 盘口改单/不中改中 域名劫持/Dns劫持 Pg赔率/爆率 Gov站点业务
技术小黄鸭🟡:@Xiaohyaa
EAP-AKA' 路径(
从 UDM 取得
这三个关系在源码中分布于 PRF、MAC 计算和 EAP 确认函数,合并阅读如下:
专业黑客业务:渗透网站/网址 棋牌入侵破解 脚本搭建 ddos攻击/cc攻击 舆论删除/负面删除 BC博彩/投顾/股票/电子签/贷款/各类资料 学信网录入/改分/改档案 盘口改单/不中改中 域名劫持/Dns劫持 Pg赔率/爆率 Gov站点业务
技术小黄鸭🟡:@Xiaohyaa
EapAuthComfirmRequestProcedure / eapAkaPrimePrf / CalculateAtMAC):从 UDM 取得
Xres(明文,十六进制字符串)、IkPrime、CkPrime、Rand、Autn。K_aut = MK[16:48],其中 MK 由 eapAkaPrimePrf(ikPrime, ckPrime, identity) 派生(internal/sbi/processor/ue_authentication.go:752)。XMAC = HMAC-SHA256(K_aut, MACInput)[:16],MACInput 为攻击者提供的整个 EAP 包(其中 AT_MAC 字段已被解析器就地清零,internal/sbi/processor/ue_authentication.go:810–813)。这三个关系在源码中分布于 PRF、MAC 计算和 EAP 确认函数,合并阅读如下:
专业黑客业务:渗透网站/网址 棋牌入侵破解 脚本搭建 ddos攻击/cc攻击 舆论删除/负面删除 BC博彩/投顾/股票/电子签/贷款/各类资料 学信网录入/改分/改档案 盘口改单/不中改中 域名劫持/Dns劫持 Pg赔率/爆率 Gov站点业务
技术小黄鸭🟡:@Xiaohyaa
仓库虽然注册了删除路由,但两个与本文相关的 handler 都只返回
入池后字段的写入顺序(这既解释了 CodeQL 断点,也提示了潜在并发风险):
专业黑客业务:渗透网站/网址 棋牌入侵破解 脚本搭建 ddos攻击/cc攻击 舆论删除/负面删除 BC博彩/投顾/股票/电子签/贷款/各类资料 学信网录入/改分/改档案 盘口改单/不中改中 域名劫持/Dns劫持 Pg赔率/爆率 Gov站点业务
技术小黄鸭🟡:@Xiaohyaa
501 Not Implemented,所以不能把它们视为已实现的上下文消费机制:func (s *Server) HTTPDelete5gAkaAuthenticationResult(c *gin.Context) {
c.JSON(http.StatusNotImplemented, gin.H{})
}
func (s *Server) HTTPDeleteEapAuthenticationResult(c *gin.Context) {
c.JSON(http.StatusNotImplemented, gin.H{})
}入池后字段的写入顺序(这既解释了 CodeQL 断点,也提示了潜在并发风险):
ausfUeContext := ausf_context.NewAusfUeContext(ueid)
ausfUeContext.ServingNetworkName = snName
ausfUeContext.AuthStatus = models.AusfUeAuthenticationAuthResult_ONGOING
ausfUeContext.UdmUeauUrl = udmUrl
ausf_context.AddAusfUeContextToPool(ausfUeContext)
logger.UeAuthLog.Infof("Add SuciSupiPair (%s, %s) to map.\n", supiOrSuci, ueid)
ausf_context.AddSuciSupiPairToMap(supiOrSuci, ueid)
locationURI := self.Url + factory.AusfAuthResUriPrefix + "/ue-authentications/" + supiOrSuci
putLink := locationURI
switch authInfoResult.AuthType {
case models.UdmUeauAuthType__5_G_AKA:
ausfUeContext.XresStar = authInfoResult.AuthenticationVector.XresStar
ausfUeContext.Kausf = Kausf
ausfUeContext.Kseaf = hex.EncodeToString(Kseaf)
ausfUeContext.Rand = authInfoResult.AuthenticationVector.Rand
}
sync.Map 的 Store/Load 只保证映射操作本身的并发安全,不会同步存入指针所指字段的后续写入。若确认请求恰好在 internal/sbi/processor/ue_authentication.go:363–427 的窗口中取回对象,可能读到零值,亦可能形成数据竞争。本文未通过 -race 或并发实验将其确认为独立漏洞,因此仅将其列为需要单独验证的并发风险。专业黑客业务:渗透网站/网址 棋牌入侵破解 脚本搭建 ddos攻击/cc攻击 舆论删除/负面删除 BC博彩/投顾/股票/电子签/贷款/各类资料 学信网录入/改分/改档案 盘口改单/不中改中 域名劫持/Dns劫持 Pg赔率/爆率 Gov站点业务
技术小黄鸭🟡:@Xiaohyaa
OAuth2Required 的值不是本地常量,而是在 NRF 注册响应中动态设置:nf = res.NrfNfManagementNfProfile
oauth2 := false
if nf.CustomInfo != nil {
v, ok := nf.CustomInfo["oauth2"].(bool)
if ok {
oauth2 = v
logger.MainLog.Infoln("OAuth2 setting receive from NRF:", oauth2)
}
}
ausf_context.GetSelf().OAuth2Required = oauth2
if oauth2 && ausf_context.GetSelf().NrfCertPem == "" {
logger.CfgLog.Error("OAuth2 enable but no nrfCertPem provided in config.")
}
状态前提:两处 INFO 泄露点发生在不同阶段。
ue_authentication.go:394 在初始 POST /ue-authentications 生成向量时执行,无需后续确认请求;ue_authentication.go:578 和 ue_authentication.go:579 则要求向量已经生成、上下文可通过有效的 authCtxId 取回,且已收到 5G-AKA 确认请求。authCtxId 对应请求中的 supiOrSuci,其到 SUPI 的映射会在 ue_authentication.go:365 以 INFO 级别记录。由于 5G-AKA 确认入口不检查已有的成功或失败状态,实现中没有“仅允许使用未消费上下文”的门控。专业黑客业务:渗透网站/网址 棋牌入侵破解 脚本搭建 ddos攻击/cc攻击 舆论删除/负面删除 BC博彩/投顾/股票/电子签/贷款/各类资料 学信网录入/改分/改档案 盘口改单/不中改中 域名劫持/Dns劫持 Pg赔率/爆率 Gov站点业务
技术小黄鸭🟡:@Xiaohyaa
ast.arguments 未来可能新增的其它绑定字段——遍历仍是枚举式的。如果哪天 ast.arguments 再加字段,同类问题理论上可能重现。这是给同类项目的一个提醒,不是 8.3 的现存绕过。配套的缓解建议(无法立即升级时):
首选:升级
>= 8.3;临时预检:在调用 RestrictedPython 之前,对不可信源码做等价 AST 预检,拒绝所有
_ 前缀的 positional-only 参数(注意 compile_restricted_function(p=...) 的参数串 p 也可能可控——该 API 会把 p 拼进 def masked_function_name(%s) 再解析,同样能触发本漏洞):纵深防御:不注入不必要的能力;不对沙箱控制对象直接
pickle;定期审计注入 safe_globals / safe_builtins 的额外名字。专业黑客业务:渗透网站/网址 棋牌入侵破解 脚本搭建 ddos攻击/cc攻击 舆论删除/负面删除 BC博彩/投顾/股票/电子签/贷款/各类资料 学信网录入/改分/改档案 盘口改单/不中改中 域名劫持/Dns劫持 Pg赔率/爆率 Gov站点业务
技术小黄鸭🟡:@Xiaohyaa
名字规则的执行器
问题定位得非常干净:规则是对的,但规则没有应用到
注意
还有一个容易忽略的对照:transformer 其实覆写了
并在节点类型层面做了 fail-closed:不认识的节点类型一律拒绝。但
专业黑客业务:渗透网站/网址 棋牌入侵破解 脚本搭建 ddos攻击/cc攻击 舆论删除/负面删除 BC博彩/投顾/股票/电子签/贷款/各类资料 学信网录入/改分/改档案 盘口改单/不中改中 域名劫持/Dns劫持 Pg赔率/爆率 Gov站点业务
技术小黄鸭🟡:@Xiaohyaa
check_name() 本身没有问题——拒绝 _ 前缀(单独 _ 除外;allow_magic_methods=True 时额外放行 ALLOWED_FUNC_NAMES 白名单——__init__、__contains__ 及六个比较运算魔术方法,配合 node.col_offset != 0 把它们限定在缩进的类体位置,与本漏洞无关)、__roles__ 结尾和保留名。把它和调用它的参数遍历器并列看:问题定位得非常干净:规则是对的,但规则没有应用到
posonlyargs。防御逻辑早已存在,只是有一类输入没有接进来。注意
check_function_argument_names() 的写法:它手工枚举要检查的字段,而不是反射式地遍历 ast.arguments._fields。这是 CWE-184(不完整禁止列表)的教科书案例——语言演进新增字段时,枚举式代码不会自动获得覆盖。Python 3.8 引入 PEP 570,ast.arguments 的字段从 6 个变成 7 个,这个函数在沉默中落后了(语言层面 Python < 3.8 的解释器天然免疫:/ 语法在解析阶段就是 SyntaxError,攻击输入根本走不到校验器——不过 8.2 声明 requires-python >= 3.10,所有受支持的解释器都带着这个字段,这条旁注不构成任何实际缓解)。还有一个容易忽略的对照:transformer 其实覆写了
generic_visit,对没有专属 visit_* 方法的 AST 节点类型直接拒绝(8.2 源码,注释为原文):并在节点类型层面做了 fail-closed:不认识的节点类型一律拒绝。但
posonlyargs 恰好穿过了这道闸——它不是新节点类型,而是已知节点 ast.arguments 上的新字段,流经其中的 ast.arg 节点类型也早已存在(visit_arg 一直都在)。全程没有任何陌生节点类型出现,fail-closed 闸门从未触发:防住了节点级演进,防不住字段级演进。这为"为什么恰好只漏了这一处"补上更深一层的答案,也是第 6 节审计模式的机制注脚。专业黑客业务:渗透网站/网址 棋牌入侵破解 脚本搭建 ddos攻击/cc攻击 舆论删除/负面删除 BC博彩/投顾/股票/电子签/贷款/各类资料 学信网录入/改分/改档案 盘口改单/不中改中 域名劫持/Dns劫持 Pg赔率/爆率 Gov站点业务
技术小黄鸭🟡:@Xiaohyaa