JNDI 如何做 Hook
1. 使用 Java Agent 动态修改字节码
这是最强大和最通用的 Hook 方法。Java Agent 可以在不修改源代码的情况下,在 JVM 运行时动态地修改类的字节码
- 原理:创建一个 Java Agent,并在 JVM 启动时通过
-javaagent
参数加载它。在 Agent 的premain
或agentmain
方法中,你可以使用 ASM、Javassist 或 Byte Buddy 等字节码操作库,找到InitialContext.lookup(name)
所在的类和方法 - Hook 实现:找到目标方法后,可以修改它的字节码,在其原始逻辑执行前或执行后插入你自己的代码
- 插入安全检查:在
lookup
方法的开头,插入一段代码来检查传入的 URL。你可以判断 URL 是否符合预设的白名单,或者直接拒绝所有远程 JNDI 请求 - 记录日志:将
lookup
方法的参数和调用堆栈记录下来,以便进行审计 - 修改返回对象:如果 URL 被判定为恶意,你可以修改
lookup
方法的返回值为一个安全的对象,而不是让其继续进行远程查找
- 插入安全检查:在
- 优点:非常灵活,可以 Hook 任何类的任何方法,无需访问源代码
- 缺点:需要深入理解 Java 字节码,且实现起来比较复杂。
2. 使用动态代理
动态代理是一种更高级的 Hook 方法,它通过 Java 的反射机制来创建接口的代理对象
- 原理:如果你知道应用程序使用的是某个 JNDI 接口(例如
Context
),你可以创建一个代理对象,这个代理对象会实现相同的接口,并在所有方法调用时,将调用转发给你自己的处理逻辑 - Hook 实现:
- 找到应用程序创建
InitialContext
的地方 - 用你自己的代理类替换
InitialContext
的实例 - 在代理类中,拦截
lookup(name)
方法的调用 - 在
lookup
方法的实现中,你可以先执行安全检查,然后再决定是否调用原始的InitialContext.lookup(name)
- 找到应用程序创建
- 优点:不需要字节码操作,相对简单
- 缺点:只能 Hook 接口,对于没有实现接口的类不起作用。此外,需要修改应用程序的某些部分来插入代理,不像 Java Agent 那样完全透明
3. 修改 JVM 参数
这是最简单的 Hook 方法,但功能也最有限
- 原理:一些 JVM 实现了特殊的参数来控制 JNDI 的行为。例如,在一些版本的 Java 中,可以通过设置
com.sun.jndi.rmi.object.trustURLCodebase=false
来阻止 RMI 客户端加载远程对象 - Hook 实现:只需在 JVM 启动命令中添加这些参数即可
- 优点:非常简单,无需编写代码
- 缺点:依赖于特定的 JVM 版本和参数,无法进行细粒度的控制,也不能用于日志记录等目的