log4j2日志漏洞解决

日志的作用

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

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日志漏洞解决

新版

log4j2日志漏洞解决

修复后的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
log4j2日志漏洞解决

(2)修改配置 log4j2.formatMsgNoLookups=True
log4j2日志漏洞解决
在应用程序入口logger初始化之前,写入代码

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

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

(3)将系统环境变量 FORMAT_MESSAGES_PATTERN_DISABLE_LOOKUPS设置为 true
log4j2日志漏洞解决

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依赖包为例:
log4j2日志漏洞解决

  • 第一步:打开pom.xml
    ctrl+鼠标左键 单击artifactId内容spring-boot-starter-logging进入如下界面:
    原始配置:
    log4j2日志漏洞解决
  • 第二步:将2.*版本更改成2.15.0
    强制修改后:
    log4j2日志漏洞解决
    第三步:Reimport
    输出结果:org.example.App - user ${java:version}

结论:有效

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

方案一:根据1中的方式一在bat中加入如下设置:
set _JAVA_OPTIONS=-Dlog4j2.formatMsgNoLookups=true
方案二:对于jar包与主程序分离的项目,可以直接将正式环境2.15.0的jar更名后直接覆盖原来的版本。
方案三:对于jar包与主程序统一打包的项目,在开发环境做对应更改后重新打包部署。

上一篇:第一次项目-Greedy Snake (super version)源代码


下一篇:破解UltraEdit64 Version 28.20.0.92 技术分享。