上周大家应该都被 log4j 的日志漏洞给刷屏了吧?
我看到这个漏洞的时候注意到它有“影响范围非常广,且上手难度低”的特点。
我就想验证一下上手难度到底有多低,于是我翻了很多文章,都是大同小异,说出漏洞了,很牛逼,赶紧修吧,晚了就玩完啦。然后配上一个唤起了计算器的截图,就结束了,也没有人告诉我到底怎么玩啊。
我也想唤起计算器,学(装)习(装)(逼)下。
于是我在网上查了一系列资料,四处摸索。从找资料到完成攻击大概花了一个小时左右的时间。
当时我就震惊了:这玩意上手难度确实很低啊!
本来开始我还是想写写关于漏洞的复现,但是写着写着打消了这个念头。
首先我即使复现了,也还是不太懂它的工作原理是什么。我只知道是可以通过 JNDI 的方式加载攻击者的类,而这个类由于是攻击者编写的,里面就可以搞很多事情了。
你想想,我能在你的代码环境里面执行我写的一些代码,哪怕我再不知道怎么攻击,我写个死循环,写个 sleep 语句也能玩死你了啊。
如果是实在想写出有价值的文章,研究的方向可以去看看 JNDI 相关的技术。
但是我觉得我卷不动了,所以就不写了。
其次我查了一下,还不能写的太详细,毕竟是漏洞,传播其复现方法也不太好,似乎还涉及到法律问题,所以大家知道怎么防范就好。
再说了,这是安全那一帮人干的事儿啊,我不想去卷他们的饭吃。
最后,其实我发现 B 站上已经有比较详细的视频演示了。贴个链接,只是我不知道这个视频还能被保留多久:
https://www.bilibili.com/video/BV1FL411E7g3
因为目前给出的解决方案,就是升级 jar 包嘛,所以这篇文章我主要给大家介绍一个插件吧,能在一定程度上提升大家的升级速度。
我平时也用它,用顺手了后,香的一比。
sb的日志
我没有骂人啊,我是想说 SpringBoot 的日志。
现在大家的应用都是基于 SpringBoot 去构建的,那么 SpringBoot 默认的日志框架是什么呢?
我也不知道,所以我准备搭建一个纯净的 SpringBoot 项目来看一眼。
又多纯净呢?
就是我通过 IDEA 构建新项目的时候,除了指定 SpringBoot 版本号为 2.6.1 外,没有引入其他的任何的包。
然后,我们看一下 pom 文件,东西非常的少:
确实纯净啊,和纯净水似的。
接着我们把项目启动起来:
你看啊,神奇的事情就出现了,我一行代码都没动呢,什么都还没配置,日志就自动打出来了。
所以之前我从来没有注意到,但是这次的事件让我想到了这个问题:
SpringBoot 默认的日志框架是什么呢?
我想到的第一个方法是这样的:
隔着搁哪的猜啥啊,能用代码解决的问题就少哔哔,加两行日志打印一下不就完事了吗?
从输出我可以知道,哦,原来用的是 logback 啊。
其他的方法
除了前面这个方法之外,还有什么办法能知道是 logback 呢?
于是我紧接着想到了查看 pom 依赖图。
在 IDEA 里面,有个一键查看 pom 依赖图的方式,特别简单。
就是在 maven 标签里面点这个图标就可以了:
如果你找不到这个图标,也可以在 pom 文件里面右键,然后依次选择 Maven->Show Dependencies:
然后你就会看到这样的一个界面,里面有 logback-core 包,说明引入了 logback 日志的实现:
有的小伙伴就要问了:我看依赖里面不是还有一个 log4j-api 吗?你凭什么说不是用的 log4j 呢?
老铁,只有 api 没有 core 包啊,核心实现都在 core 包里面的。
比如这次 log4j 爆出问题的代码,也就是 lookup 那块的逻辑,就都在 core 包里面。
但是为什么修复的建议是让我们同时排出 log4j-api 和 log4j-core 呢?
我个人浅显的认为是 core 都不用了, api 留着意义也不大吧。如果只有 log4j-api 的依赖,根据我掌握的攻击原理,是不会有问题的。
好了,先不扯远了,说回这个纯净的 SpringBoot 项目。
我前面给你说的看依赖图的方式,其实我一般不用。
为什么呢?
因为在真实的项目里面,依赖关系可能是极其复杂的,看起来密密麻麻的,你没有任何想看的欲望,比如给大家看看 dubbo 项目的 pom 依赖图,非常的刺激,非常的有冲击感啊:
到这里,我们先思考一个问题:你一般打开 pom 文件是干啥?
是不是在排查 jar 包冲突或者找 jar 包的时候需要用到?
所以在前面的页面里面可以按了 Ctrl F 之后进行搜索,就像这样:
但是你去用的时候其实真的还是很不好用的。
于是乎,我要给你介绍的插件就来了。
https://plugins.jetbrains.com/plugin/7179-maven-helper
这个插件,香的一笔啊。
怎么安装就不介绍了,注意自己对应的 IDEA 版本就行。
主要给大家看看它怎么用的。
安装好了之后,你再次打开 pom 文件,可以看到下面有个这个东西:
里面是长这样的:
我们主要关注这个地方,我把注释也给你写上:
这个里面,主要就是用到两个核心功能。
- 排查依赖冲突。
- 查询jar包依赖。
比如,我们看一下我们这个纯净的项目里面和 log 相关的包:
一目了然,我们也看到了前面提到的 logback-core 包。
实战演练
光说不练假把式,接下来给大家来个使用 maven-helper 的实战演练。
比如我们就拿 Dubbo 开刀,用起来其实非常简单的。
就拿这次 log4j 漏洞事件来说,我们要排查出项目里面所有的 log4j-api 和 log4j-core 包。
我选择 Dubbo 项目里面的这个模块来做演示吧:
比如先打开 autoconfigure 包下面的 pom 文件:
然后搜一下 log4j:
可以看到 log4j-api 是由 spring-boot-starter-logging 引入进来的。
在这里右键,然后点击 Exclude 即可排出:
刷新依赖,再次查询就没了:
就是这么的简单,其他的模块也都是这样,我就不一一演示了。
但是有个问题出现了, Dubbo 作为中间件,排出了之后就不能不管了呀,还得引入一个新的安全版本进来。
我们可以看一下 Dubbo 这次对于 log4j 漏洞提交的 pr:
https://github.com/apache/dubbo/pull/9378/files
可以看到排除了 log4j-api 同时也引入了一个新版本的 log4j-api。
版本号放在了父 pom 里面,实现了对版本的统一管理:
如果后续还需要升级 log4j-api 只需要调整父 pom 里面的版本号就行。
再来一个
再给大家来一个例子,演练一下。
这个例子借鉴于这个链接里面:
https://juejin.cn/post/6945220055399399455
首先是在前面纯净的 SpringBoot 项目中多引入了 Dubbo 的包,然后引入了 zookeeper 作为注册中心:
代码语言:javascript复制<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.9</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-zookeeper</artifactId>
<version>2.7.9</version>
</dependency>
</dependencies>
同时,记得在 application.properties 文件里面加上这行配置,否则启动不起来:
dubbo.application.name=xxx
启动的时候你可以看到这样的提醒日志:
怎么样,是不是很眼熟?在大多数的项目启动的时候都遇到过?
上面的提醒日志可以分为两组,一组是 SLF4J,一组是 log4j。
很多人都知道这个问题肯定是出现依赖冲突,日志框架混乱。
所以首先我们再看一下项目里面的日志依赖,看到有一个 log4j 的依赖冲突:
二话不说,先右键给它排掉,在 pom 文件里面就是这样体现的:
但是需要注意的是,我们不能直接就把 log4j 包删除了就不管了,这里引用了说明项目里面使用了 log4j,因此我们需要把 log4j 桥接到 slf4j 上,从而达到排除了 log4j ,但是日志还能正常打印的目的:
代码语言:javascript复制<!--增加log4j-slf4j -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>1.7.30</version>
</dependency>
再次启动项目,你就发现,只剩下 SLF4J 这一个问题了:
再次打开依赖分析:
可以发现项目同时拥有了 slf4j-log4j12 和 log4j-to-slf4j,另外还有个 logback 的存在。
所以,项目里面共存了两套 slf4j 的实现,分别是 log4j 和 logback。
其实这一点,从日志中也可以看出来:
日志提醒你了:Class path contains multiple SLF4J bindings.
两套实现,在项目启动的时候先加载那个就使用哪个,这是很不靠谱的事情。
根本的解决方案就是排除其中的一个。
比如我们还是使用默认的 logback,那就排除 slf4j-log4j 这个依赖。
再次启动项目,之前的冲突日志都没有了,舒服了:
好,那么如果我不想用 logback,就想用牛逼的 log4j 怎么办呢?
https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto.logging.log4j
修改一下 maven 就行:
从日志打印就知道了,现在的实现是 log4j。
然后,关于 log4j 这个漏洞,我想说,日志就干好日志的事儿,弄这么多复杂的功能干啥呢?完成 KPI 啊?
说真的,在这个漏洞被爆出来之前,我都不知道它还有这些高级的功能呢,我寻思好像也用不到啊。
感觉就很像是有人特意留下的后门,这就细思极恐了,以及脑补了一场大戏。