前言
log4j2 宛如过年一般,趁此机会对没理清的 JNDI 协议仔细再捋一捋
log4j的payload很好记:${jndi:ldap/rmi://xxxxxx/exp}
那么,我们就需要研究一下是怎么来的。
首先来看rpc
RPC
RPC即 Remote Procedure Call
(远程过程调用),==是一种技术思想而并非一种规范的协议==,是一种通过网络从远程计算机请求服务的过程。
常见 RPC 技术和框架有:
-
应用级的服务框架:阿里的 Dubbo/Dubbox、Google gRPC、Spring Boot/Spring Cloud。
-
远程通信协议:RMI、Socket、SOAP(HTTP XML)、REST(HTTP JSON)。
-
通信框架:MINA 和 Netty。
一个rpc框架有以下必备条件:传输协议,序列化,注册中心,服务路由,负载均衡,IO框架,心跳机制,服务鉴权,服务隔离,服务治理,监控埋点。
-
Dubbo:国内最早开源的 RPC 框架,由阿里巴巴公司开发并于 2011 年末对外开源,仅支持 Java 语言。
-
Motan:微博内部使用的 RPC 框架,于 2016 年对外开源,仅支持 Java 语言。
-
Tars:腾讯内部使用的 RPC 框架,于 2017 年对外开源,仅支持 C++ 语言。
-
Spring Cloud:国外 Pivotal 公司 2014 年对外开源的 RPC 框架,仅支持 Java 语言
-
gRPC:Google 于 2015 年对外开源的跨语言 RPC 框架,支持多种语言。
-
Thrift:最初是由 Facebook 开发的内部系统跨语言的 RPC 框架,2007 年贡献给了 Apache 基金,成为 Apache 开源项目之一,支持多种语言。
联系
先直接给出结论
Spring Cloud是一种RPC框架,但是区别是它使用的是http协议的传输,整体技术和普通RPC如dubbo/thrift有很大区别,所以一般会分开说。
而Springcloud的核心是微服务,微服务中使用 RPC的思想尤为明显
我们熟知的一些漏洞,能够远程加载恶意类的,或多或少都有着使用微服务架构的影子,所以我们才需要对这一领域进行研究。
使用场景
-
在分布式场景中,我们一般要考虑调用问题
-
远程过程调用,要能够像本地过程调用一样方便,让使用者感受不到远程调用的逻辑
-
rpc的过程图
-
RPC:(Remote Procedure call) 即远程过程调用,它是一个计算机通信协议,RPC与语言无关
-
RMI:(Remote Method Invocation) 远程方法执行,是RPC的纯java实现方式
简单来说,就是一个节点请求另一个节点的服务
-
服务端需要暴露一个接口 (让客户端知道服务端有哪些可以请求的服务)
-
客户端需要把调用方法名,参数都传递到服务端
-
服务端接收客户端发来的方法名和参数,在自己的服务中找到对应的方法,然后去调用
-
服务器端把结果发送到客户端
通过网络传输字符串,对象等数据,使用时常伴随着序列化使用。
但是,光看这样,无法理解其中的细节,我们需要深入看一下RPC的架构
在一个典型 RPC 的使用场景中,包含了服务发现、负载、容错、网络传输、序列化等组件,其中“RPC 协议”就指明了程序如何进行网络传输和序列化。(这个在后面的demo中会介绍到)
微服务
这里先谈一下微服务,什么是微服务
-
微服务是系统架构上的一个风格,主旨是将一个原本独立的系统拆分成多个小型服务
-
这些小服务能够在各自独立的进程中运行,服务之间通过HTTP的 Restful API进行通信协作。
比方说,一个服务里面,就有一个apache + database
,
被拆分成的每一个小型服务都围绕着系统中的某一项或一些耦合度较高的业务功能进行构建,并且每个服务都维护着自身的数据存储、业务开发、自动化测试案例以及独立部署机制。
为什么要使用微服务
微服务架构有别于更为传统的单体式方案,可将应用拆分成多个核心功能。==每个功能都被称为一项服务,可以单独构建和部署,这意味着各项服务在工作(和出现故障)时不会相互影响==。这有助于您更好实现 DevOps 的技术,让持续集成和持续交付(CI/CD)[软件构造的知识点]更加利于实现。
按照官方的说法,实施微服务需要有:服务组件化,按业务阻止团队,做产品的态度,==轻量化的通信机制==,去中心化处理数据,去中心化管理数据,基础设施自动化,容错测试,容错设计,演进式设计。
其中,我们攻击具有JNDI漏洞的业务时,大部分是从轻量化的通信机制中入手的。
这里以微服务 zookeeper为例,看一下微服务的过程
那么,我们伪造一个 微服务中间的服务器,类似于伪造JDBC反序列化漏洞中mysql服务端一样,让客户端访问恶意的服务器去加载数据,就可以达到我们的目的。
关于jdk版本的问题
师傅们经过测试,log4j给出了可以打的一些环境版本
总结是:rmi 113之前可以用, ldap 191之前可以用
8u112 rmi可以利用
8u112 ldap可以利用
8u121 rmi失败
8u121 ldap可以利用
8u181 rmi失败
8u181 ldap可以利用
8u191 rmi失败
8u191 ldap失败
8u301 rmi失败
8u301 ldap失败
11.012 rmi失败
11.012 ldap失败
那么,为什么191和113是一个分水岭呢?
我们先来分析一个例子
LDAP+JNDI远程加载恶意类
ldap的jndi在==6u211、7u201、8u191、11.0.1==后也将默认的com.sun.jndi.ldap.object.trustURLCodebase
设置为了false,并且这些变动对应的分配了一个漏洞编号CVE-2018-3149。
这就是为什么师傅们说到191之后就不再适用了,因为如果我们想要在191之后的版本进行使用ldap进行加载恶意类,需要我们手动去设置参数com.sun.jndi.ldap.object.trustURLCodebase
设置为true
具体可以见 Vulfocus 靶场环境
docker pull vulfocus/log4j2-rce-2021-12-09:latest
中有代码这么写道
使用 docker copy 命令将 demo.jar
拷贝到主机进行反编译
可以看到师傅在jdk版本不满足的情况下设置了属性为 true
接下来,我们以一张图来理清思路
JNDI
JNDI(Java Naming and Directory Interface)
是Java提供的Java 命名和目录接口
。通过调用JNDI
的API
应用程序可以定位资源和其他程序对象。JNDI
是Java EE
的重要部分,需要注意的是它并不只是包含了DataSource(JDBC 数据源)
,JNDI
可访问的现有的目录及服务有:JDBC
、LDAP
、RMI
、DNS
、NIS
、CORBA
。
JNDI如上很多协议,但是我们这里只分析 LDAP和RMI
LDAP目录服务
LDAP全称是轻量级目录访问协议。
LDAP的服务处理工厂类是:com.sun.jndi.ldap.LdapCtxFactory,连接LDAP之前需要配置好远程的LDAP服务。
RMI
RMI的流程中,客户端和服务端之间传递的是一些序列化后的对象,这些对象在反序列化时,就会去寻找类。如果某一端反序列化时发现一个对象,那么就会去自己的CLASSPATH下寻找想对应的类;如果在本地没有找到这个类,就会去远程加载codebase中的类。
远程加载恶意类(攻击服务端)
RMI服务端远程加载恶意类
根据p师傅知识星球的内容可知rmi进行加载的时候,会涉及到codebase
,codebase是一个地址,指定jvm
从哪个地方去搜集类,和ClassPath,jdbc的url一样,通常是远程的URL,比如http,ftp等
如果我们指定codebase=http://example.com
,然后加载org.vulhu