log4j2日志漏洞解决

  • Post author:
  • Post category:其他




日志的作用

日志记录主要用来监视代码中变量的变化情况,周期性的记录到文件中供其他应用进行统计分析工作;跟踪代码运行时轨迹,作为日后审计的依据;担当集成开发环境中的调试器的作用,向文件或控制台打印代码的调试信息。因此,对于程序员来说,日志记录非常重要。



log4j2

Apache Log4j2是一个基于Java的日志记录工具。该工具重写了Log4j框架,并且引入了大量丰富的特性。该日志框架被大量用于业务系统开发,用来记录日志信息。大多数情况下,开发者可能会将用户输入导致的错误信息写入日志中。此次漏洞的出现,正是由用于 Log4j 2 提供的 lookup 功能造成的,该功能允许开发者通过一些协议去读取相应环境中的配置。

但在实现的过程中,并未对输入进行严格的判断,只要外部用户输入的数据会被日志记录,即可造成远程代码执行,从而造成漏洞的发生。简单来说,就是在打印日志时,如果发现日志内容中包含关键词 ${,那么这个里面包含的内容会当做变量来进行替换,导致攻击者可以任意执行命令。

比如:

logger.info("client ip: {}", clientIp)



影响版本 :


2.0 ≤ Apache Log4j <= 2.14.1



影响判断方式:

用户只需排查Java应用是否引入 log4j-api , log4j-core 两个jar。若存在应用使用,极大可能会受到影响。



影响规模

这一次漏洞的影响面之所以如此之大,主要还是log4j2的使用面实在是太广了。

  • 一方面现在Java技术栈在Web、后端开发、大数据等领域应用非常广泛,国内除了阿里巴巴、京东、美团等一大片以Java为主要技术栈的公司外,还有多如牛毛的中小企业选择Java。
  • 另一方面,还有好多像kafka、elasticsearch、flink这样的大量中间件都是用Java语言开发的。

    在上面这些开发过程中,大量使用了log4j2作为日志输出。只要一个不留神,输出的日志有外部输入混进来,那直接就是远程代码执行rce



漏洞根源

lookup,顾名思义就是查找、搜索的意思,那在log4j2中,就是允许在输出日志的时候,通过某种方式去查找要输出的内容。lookup相当于是一个接口。

  • JNDI即

    Java Naming and Directory Interface

    (JAVA命名和目录接口),它提供一个目录系统,并将服务名称与对象关联起来,从而使得开发人员在开发过程中可以使用名称来访问对象。

简单粗暴理解:有一个类似于字典的数据源,你可以通过JNDI接口,传一个name进去,就能获取到对象了。

  • LDAP即

    Lightweight Directory Access Protocol

    (轻量级目录访问协议),目录是一个为查询、浏览和搜索而优化的专业分布式数据库,它呈树状结构组织数据,就好象Linux/Unix系统中的文件目录一样。目录数据库和关系数据库不同,它有优异的读性能,但写性能差,并且没有事务处理、回滚等复杂功能,不适于存储修改频繁的数据。所以目录天生是用来查询的,就好像它的名字一样。

看不懂?看不懂就对了!

这个东西用在统一身份认证领域比较多,但今天也不是这篇文章的重点。你只需要简单粗暴理解:有一个类似于字典的数据源,你可以通过LDAP协议,传一个name进去,就能获取到数据。

  • JNDI还支持一个叫命名引用(Naming References)的方式,可以通过远程下载一个class文件,然后下载后加载起来构建对象。



旧版

在这里插入图片描述



新版

在这里插入图片描述

修复后的log4j2在JNDI lookup中增加了很多的限制:

  • 1.默认不再支持二次跳转(也就是命名引用)的方式获取对象
  • 2.只有在

    log4j2.allowedLdapClasses

    列表中指定的class才能获取
  • 3.只有远程地址是本地地址或者在

    log4j2.allowedLdapHosts

    列表中指定的地址才能获取



log4j2漏洞的解决

验证代码准备:

在项目登录接口中增加类似如下代码:

@PostMapping("login")

public R<?> login(@RequestBody LoginBody form) {

	//form.getUsername()="${java:version}"

	logger.info("登录:{}",form.getUsername());

	// 用户登录

	LoginUser userInfo = sysLoginService.login(form.getUsername(), form.getPassword());

	// 获取登录token

	return R.ok(tokenService.createToken(userInfo));

}

public class App {

	private static Logger logger = LogManager.getLogger(App.class);

	public static void main(String[] args) {

		logger.info("user {}", "${java:version}");

		System.out.println("Hello World!");

	}

}

存在漏洞时输出结果:org.example.App – user Java version 1.8.0_181

无漏洞时的输出结果:org.example.App – user ${java:version}




1. 紧急缓解措施

(1)修改 jvm 参数

-Dlog4j2.formatMsgNoLookups=true


在这里插入图片描述

(2)修改配置

log4j2.formatMsgNoLookups=True


在这里插入图片描述

在应用程序入口logger初始化之前,写入代码

System.setProperty("log4j2.formatMsgNoLookups", "true");

输出结果:org.example.App – user ${java:version}

(3)将系统环境变量

FORMAT_MESSAGES_PATTERN_DISABLE_LOOKUPS

设置为

true


在这里插入图片描述




2. 下载最新的log4J

如2.15.0版本,直接将本地仓库中2.0至2.14.1版本里的jar,用2.15.0更名后替换

  • 第一步:在Project视图中展开External Libraries找到Log4J相关jar依赖,并进行如下操作:对应jar上右键->copy->Absolute Path 复制到文件管理器中打开

  • 第二步:将2.15.0中对应jar复制一份更名成2.14.1中jar的名称并覆盖2.14.1中的jar

    输出结果:org.example.App – user ${java:version}

结论:有效




3、从项目里强制修改直接依赖或间接依赖的Log4J版本

以Idea Maven项目spring-boot-starter-logging依赖包为例:

在这里插入图片描述

  • 第一步:打开pom.xml

    ctrl+鼠标左键 单击artifactId内容spring-boot-starter-logging进入如下界面:

    原始配置:

    在这里插入图片描述
  • 第二步:将2.*版本更改成2.15.0

    强制修改后:

    在这里插入图片描述

    第三步:Reimport

    输出结果:org.example.App – user ${java:version}

结论:有效



对于正式环境有以下解决方案:


方案一

:根据1中的方式一在bat中加入如下设置:

set _JAVA_OPTIONS=-Dlog4j2.formatMsgNoLookups=true


方案二

:对于jar包与主程序分离的项目,可以直接将正式环境2.15.0的jar更名后直接覆盖原来的版本。


方案三

:对于jar包与主程序统一打包的项目,在开发环境做对应更改后重新打包部署。



版权声明:本文为wang9820原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。