DNSSEC 密钥标签碰撞

摘要 DNSSEC 通过数字签名保证 DNS 数据的完整性,其信任锚点由 DNSKEY 记录的密钥标签标识。解析器在验证签名时,根据授权签名者(Signer)字段中的密钥标签查找对应的 DNSKEY。然而,16 位的密钥标签空间仅有 65536 种可能,攻击者可以暴力生成一个与信任锚标签相同的恶意密钥,并将其植入精心构造的子域 NSEC3 记录中。部分递归解析器(如 Unbound)在解析路径上的密钥选择逻辑存在缺陷:当响应中包含与当前信任锚标签匹配的非权威 DNSKEY 时,可能错误地将其用于验证签名,导致攻击者伪造的签名被接受,整个 DNSSEC 验证降级为“无保护”状态。 DNSSEC 密钥标签与解析器信任模型 密钥标签的计算方法 DNSSEC 使用 DNSKEY 记录存储区的公钥,每个密钥由 16 位的密钥标签唯一标识(在 RRSIG 和 DS 记录中引用)。密钥标签的计算过程如下(RFC 4034 附录 B):将 DNSKEY 的 RDATA(标志位、协议、算法、公钥)按网络字节序拼接,然后逐 16 位累加,最后取低 16 位作为标签。算法简单,无密码学强度,仅作为快速索引。 由于算法可逆性弱,攻击者无法直接计算出与特定标签匹配的公钥,但可以反复生成密钥对,计算标签,直到碰出目标标签。对于 16 位空间,平均需要约 32768 次尝试,现代 CPU 在毫秒级内即可完成。因此,密钥标签的碰撞是现实可行的。 解析器的密钥选择路径 以 Unbound 为例,验证签名时的密钥选择流程: 解析器收到包含 RRSIG 的响应,提取 Signer 字段,其中含有密钥标签。…

剪贴板即信道

摘要 异步剪贴板 API 为 Web 应用提供了读、写系统剪贴板的能力,但 navigator.clipboard.write() 的异步执行与用户 paste 事件的同步触发之间,存在一个容易被忽略的原子性缺陷。攻击者可利用同一用户手势先后调用 write() 和 read(),通过 Promise.race 构造竞态条件,在浏览器实际写入新内容之前读取到剪贴板中尚未被覆盖的残留数据——而这些数据可能来自另一个域,包含密码管理器自动填充的凭据、2FA 种子或私钥。更危险的是,结合密码管理器的“复制密码”功能和 WebAuthn 条件 UI 的自动填充行为,窃取甚至不需要用户额外的粘贴操作。 异步剪贴板 API 的安全模型 从 execCommand 到 navigator.clipboard 在 navigator.clipboard 出现之前,页面只能通过 document.execCommand(‘copy’) 和 document.execCommand(‘paste’) 操作剪贴板,且后者仅在浏览器扩展或用户主动粘贴时可用。异步剪贴板 API 允许页面使用 readText() 和 writeText() 直接读取和写入文本,前提是获得用户许可(对于 read,通常要求页面可见且由用户手势触发)并且传输数据遵循安全上下文(HTTPS)。 关键设计:write() 返回一个 Promise,在数据被系统剪贴板确认后解析;read() 同样返回一个 Promise,在读取到当前剪贴板内容时解析。二者都是在“任务队列”中异步执行的,但在一次用户手势(如 click 事件)中,可以同时调用两个方法而不相互阻塞。 同一个点击下的 write 与 read 考虑以下代码: button.addEventListener('click', async () => { navigator.clipboard.writeText('A'.repeat(100)); const data = await navigator.clipboard.readText(); console.log(data); }); 开发者预期 readText() 会返回刚才写入的 ‘AAA…’,但实际结果取决于 UA 的事件循环调度:如果 writeText() 的内部实现是“先调度写入任务,再解析 Promise”,而 readText() 在写入任务实际执行之前就已经从系统剪贴板读取了内容,那么 data 将是调用 writeText()之前 剪贴板中的旧数据。这个顺序差异构成了可被利用的竞态窗口。 竞态条件的微观时序 任务队列的调度间隙 浏览器主线程执行模型: 用户点击,click 事件处理开始。 调用 writeText(),UA 创建一个“写剪贴板”任务并将其排入任务队列,同时返回一个 Promise。 继续执行,调用 readText(),UA 创建一个“读剪贴板”任务排入队列,返回…

C++ 元编程的定时炸弹

摘要 C++ 的模板与 constexpr 机制赋予开发者编译期计算的能力,其图灵完备性使得任意算法均可在编译阶段执行。然而,编译器的实现必须为这种计算设定资源上限——-ftemplate-depth 和 -fconstexpr-steps 等选项维持着编译期世界的脆弱平衡。攻击者在提交至 CI/CD 的源代码中暗藏精心构造的递归模板或 constexpr 循环,将合法代码转化为耗尽编译服务器 CPU 与内存的定时炸弹。更隐蔽的威胁在于:通过 __builtin_is_constant_evaluated 或编译器差异注入条件分支,同一份代码可在 Clang 下悄然通过,却在 GCC 上触发堆耗尽,实现对特定工具链的定向打击。 编译资源 模板递归与 constexpr 函数的计算模型 C++ 模板元编程通过递归实例化处理类型或值。例如,一个计算阶乘的模板: template <unsigned N> structFactorial { staticconstexprunsigned value = N * Factorial<N – 1>::value; }; template <> structFactorial<0> { staticconstexprunsigned value = 1; }; 编译器为每个不同的模板参数生成一个新的类特化,递归深度达到 N。constexpr 函数则允许在编译期执行常规 C++ 函数,但其求值同样受制于实现定义的步数限制。 这两种机制都是图灵完备的,但本质上是编译器的解释器——没有硬件强制执行严格的栈深度或时间片。编译器采用固定的内部计数器防止无限循环,这些计数器的默认值构成了攻击面。 编译器限制 各主流编译器均提供控制编译期计算深度的选项: 编译器 模板递归深度限制 constexpr 求值步数限制 默认值 最大可设置值 GCC -ftemplate-depth=n…

Nim 的 C-FFI 隐身衣

摘要 Nim 语言凭借其接近 C 的性能与 Python 般简洁的语法,正逐渐成为恶意软件开发者的新宠。其独特的编译期代码执行能力,使得恶意代码可以在编译时读取系统信息、生成仅在特定目标环境中激活的分支,并直接绑定 Win32 API——不依赖 LoadLibrary/GetProcAddress 的动态解析,也不在导入表中暴露敏感函数名。与 Go 的 cgo 和 Rust 的 cc crate 不同,Nim 生成的 C 中间产物在最终二进制中几乎不留痕迹,逆向工程师面对的是一张高度定制的“C-FFI 隐身衣”。 Nim编译管道与C-FFI绑定 Nim 编译器的工作流程 Nim 编译器(nim)并非直接生成机器码,而是默认通过 C 后端生成 C 源代码,再调用系统的 C 编译器(GCC、Clang 或 MSVC)完成最终编译。这一设计的核心优势在于:Nim 程序天然具备与 C 语言相同级别的底层访问能力,同时保留了高级语言的类型安全和元编程能力。 编译流程分为四个阶段: Nim 源码解析与语义分析:编译器将 .nim 文件解析为 AST,进行类型推导和宏展开。 中间表示生成:经过语义检查的 AST 被转换为 Nim 虚拟机指令或直接进入代码生成阶段。 C 代码生成:代码生成器遍历中间表示,输出对应的 C 代码。Windows 下通常输出 .nim.c 文件,包含所有函数的 C 实现和 Nim 运行时支持代码。 C…

PAC 签名无效引发的域信任降级攻击

摘要 Kerberos 票据中的特权属性证书承载着用户的组成员身份和安全标识符,其完整性由 KDC 的长期密钥签名保护。然而,当 PAC 签名验证失败时,Windows 域控制器的默认行为并非一律拒绝,而是在特定配置下(尤其是跨林信任场景中)采取降级策略——忽略 PAC 签名错误,转而使用 Active Directory 中存储的组成员身份重建用户的访问令牌。攻击者可构造 PAC 被篡改或签名缺失的 TGT,利用跨林信任的 SID 过滤与选择性认证盲区,将低权限用户提升为域管理员。 PAC 签名架构与信任边界 PAC 的双签名体系 Kerberos PAC 是微软对标准 Kerberos 协议的扩展,嵌入在 TGT 和 ST 的授权数据字段中。它包含用户的 SID、组成员身份 SID 列表以及用于权限判断的额外信息。为了防止用户或恶意服务篡改 PAC 内容,微软采用了双签名机制: 服务签名:使用目标服务账户的密钥对 PAC 内容进行签名,验证该 PAC 确由 KDC 为该服务签发。 KDC 签名:使用 KDC 自身密钥对 PAC 进行二次签名,提供额外的完整性保障,防止拥有服务密钥的内部人员伪造 PAC。 这两组签名数据存储在 PAC_SIGNATURE_DATA 结构中,分别包含签名类型(如 Kerberos、HMAC_SHA1_96)、签名算法标识符和签名值本身。 PAC 结构及签名位置:…

UBTI的攻击面测绘与 COM 劫持

摘要 Windows 计划任务早已超越“定时执行脚本”的原始范畴。自 Windows 10 1607 引入统一后台任务基础设施(UBTI)后,大量系统与第三方任务被悄然迁移至 COM 组件驱动的新架构,由 taskhostw.exe 进程承载。这一变革在提升可靠性的同时,也将攻击面从 XML 配置文件扩展到了 COM 注册表与 DLL 加载路径。攻击者不再需要写入恶意脚本,而仅需劫持一个计划任务所引用的 COM CLSID,即可将系统原生的合法任务转化为隐蔽的持久化触发器。 UBTI架构 传统计划任务模型 长久以来,Windows 计划任务的核心引擎是 Schedule 服务(schedsvc.dll),运行在 svchost.exe -k netsvcs 共享进程中。任务定义以 XML 格式存储于 C:WindowsSystem32Tasks 目录,管理员通过 schtasks.exe 命令行工具或任务计划程序 MMC 管理单元进行管理。执行时,传统任务启动一个独立的进程(如 cmd.exe /c script.bat),其生命周期完全可见于进程树中。 这种模型简单明了,但也存在明显局限:失败重试机制脆弱、任务隔离性差、对触发条件的响应不够精细。更关键的是,随着 Windows 向“系统即服务”演进,大量后台维护工作(如磁盘整理、系统诊断、Windows 更新后任务)需要更高级的执行容器。 统一后台任务基础设施 Windows 10 版本 1607 引入了统一后台任务基础设施(Unified Background Task Infrastructure,UBTI),作为“后台任务现代化”工程的核心组件。UBTI 并非替换计划任务调度器,而是为其增加了一套全新的执行路径。 在 UBTI 模型下,一个计划任务可以注册为COM 任务——其操作不是启动一个可执行文件,而是实例化一个实现了 ITaskHandler 接口的 COM 组件。该接口定义了两个核心方法: Start:接收任务触发信息,执行任务逻辑。 Stop:接收任务取消请求,执行清理。 任务的实际执行者不再是 cmd.exe 或用户指定的可执行文件,而是 COM 代理进程 taskhostw.exe。该进程以当前用户的会话权限运行(部分系统任务以 SYSTEM…