4. OPC UA匿名登录与节点遍历测试
OPC UA 匿名登录与节点遍历测试
OPC UA 服务器通常通过 SecurityPolicy 与 UserTokenPolicy 控制访问。当服务器允许 Anonymous 用户令牌且未对地址空间做权限裁剪时,任何能访问 4840 端口的客户端都可以读取过程变量。本例使用 opcua-asyncio 客户端完成匿名连接、地址空间遍历与关键变量读取,并据此评估未授权数据暴露风险。
测试前提:目标 OPC UA 服务器已开启匿名访问(
Anonymous 令牌策略),且测试方已获得授权。若服务器仅允许用户名/密码或证书认证,匿名连接会返回 BadIdentityTokenInvalid 或 BadSecurityPolicyRejected,此时应记录为“匿名访问被拒绝”,而非继续尝试绕过。连接与遍历实现
核心流程分三步:建立匿名会话 → 从 Objects 节点递归遍历 → 对命中变量节点执行 read_value。以下脚本可直接运行,关键参数在顶部集中定义。
import asyncio
from asyncua import Client, ua
ENDPOINT = "opc.tcp://192.168.10.20:4840"
MAX_DEPTH = 4
KEYWORDS = ("temp", "press", "flow", "level", "speed", "rpm", "valve")
async def walk(node, depth, hits):
if depth > MAX_DEPTH:
return
for child in await node.get_children():
try:
bn = await child.read_browse_name()
name = bn.Name
if any(k in name.lower() for k in KEYWORDS):
try:
val = await child.read_value()
hits.append((child.nodeid.to_string(), name, val))
except ua.UaStatusCodeError as e:
hits.append((child.nodeid.to_string(), name, f"<read denied: {e.code}>"))
await walk(child, depth + 1, hits)
except ua.UaStatusCodeError:
continue
async def main():
async with Client(url=ENDPOINT) as client:
# 匿名登录:不设置 user 与 password
print("session:", client.session)
objects = client.get_objects_node()
hits = []
await walk(objects, 0, hits)
print(f"matched variables: {len(hits)}")
for nid, name, val in hits:
print(f"{nid:<40} {name:<24} {val}")
asyncio.run(main())
脚本中 Client(url=...) 未传入 user/password,即使用匿名令牌;get_objects_node() 返回 Objects 文件夹作为遍历根;MAX_DEPTH 限制递归深度,避免在大型地址空间中无限展开;KEYWORDS 用于筛选过程变量,减少无关节点输出。
结果判读与风险分级
运行后观察三类信号:会话是否建立、命中变量数量、读取是否被拒绝。若匿名会话成功且能读到过程变量,说明服务器未对匿名用户做地址空间裁剪,属于未授权数据暴露;若会话建立但读取返回 BadUserAccessDenied,说明认证层放行但节点级权限生效,风险较低;若匿名连接直接失败,则匿名访问已被禁用。
风险分级与处置建议
| 观察结果 | 风险等级 | 处置建议 |
|---|---|---|
| 匿名会话成功,读到过程变量 | 高 | 禁用匿名令牌,或对匿名用户裁剪地址空间,仅暴露必要只读节点 |
| 匿名会话成功,读取返回 BadUserAccessDenied | 中 | 保留节点级权限,同时审计匿名会话是否可枚举节点结构 |
| 匿名连接被拒绝 | 低 | 维持现有认证策略,定期复核 UserTokenPolicy 配置 |
遍历深度过大或节点数量过多时,脚本可能长时间运行并产生大量请求。建议先用
MAX_DEPTH=2 试探,确认服务器响应正常后再逐步加深;对生产服务器应控制请求频率,避免影响正常通信。验证要点
- 确认
Client构造时未传入user与password,否则测的是凭据登录而非匿名访问。 - 记录
client.session是否成功建立,作为匿名策略是否放行的第一判据。 - 对命中变量逐一核对
nodeid与BrowseName,避免把服务器自身诊断节点误判为过程变量。 - 若读取返回状态码异常,保留原始
UaStatusCodeError.code,用于区分“认证拒绝”与“节点权限拒绝”。