nnonkey k1n9的博客

当你为错过太阳而哭泣时,你也要再错过群星了——泰戈尔​

java安全之动态字节码加载进阶

前言

我们学习了基础之后来看看进阶的内容,它是如何在java安全中发挥作用的,主要内容为反序列化利用中常用到的用来动态加载字节码执行命令的两个JDK原生类。
我只能说难到爆了,太痛了,但是原理至上,狠狠的理解一波

分析

倘若能够找到一些类,调用他们的一些方法时会触发底层的类加载机制,调用ClassLoader#defineClass(),那我们可以通过控制参数,传入恶意字节码。
那我们就要找到是谁会调用defineclass
我们在Elipse或者idea都可以,但是有一个细节,我们需要找到官方的这个方法,然后再查找用法
比如这个代码中

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

public class DefineClass_ {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException,
            InvocationTargetException, InstantiationException {
        Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
        defineClass.setAccessible(true);

        byte[] code = Base64.getDecoder().decode("");
        ClassLoader c1 = ClassLoader.getSystemClassLoader();
        Class<?> c = (Class<?>) defineClass.invoke(c1, "Test", code, 0, code.length);
        c.newInstance();
    }
}

这个Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);如果我们查找用法只会在本类中出现,所以我们要找自带的这个方法,比如我们进入到Classloader这人类,我们就会发现这个方法,然后查找用法
2024-03-01T05:51:53.png
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl的内部类TransletClassLoader

我们查找到

for (int i = 0; i < classCount; i++) {
                _class[i] = loader.defineClass(_bytecodes[i], pd);
                final Class<?> superClass = _class[i].getSuperclass();

使用了loader类中的defineClass()这个方法,我们再看loader是什么东西,向上划,发现

TransletClassLoader loader =
                AccessController.doPrivileged(new PrivilegedAction<TransletClassLoader>()

是内部类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;
        }

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

        Class<?> defineClass(final byte[] b, ProtectionDomain pd) {
            return defineClass(null, b, 0, b.length, pd);
        }
    }

首先是继承了ClassLoader,重写了loadclass和defineclass,相当于一个自己定义的加载器,其实这里的defineClass()也就是访问属性变为default,最终调用的还是ClassLoader#defineClass()。因为Java 中的方法调用是基于继承关系的。当子类中调用一个方法时,如果子类本身没有实现这个方法,那么会去父类中寻找是否有对应的方法。如果找到了,就会直接调用父类的方法。这就是所谓的方法重写(override)。在这种情况下,虽然我们在自定义类加载器中定义了 defineClass() 方法,但由于其访问权限设为默认(package-private),在父类 ClassLoader 中也存在同名的 defineClass() 方法,因此最终调用的是父类中的方法。
我们找找链子是怎么调用到TemplatesImpl.TransletClassLoader的
首先需要调用defineTransletClasses()方法,然后2024-03-01T06:19:06.png有三个地方调用了这个方法,我们跟过去然后就。。。。能力不够反正最后是这样,有几个问题,为什么都找同类的?

TemplatesImpl.TransletClassLoader#defineClass()<--TemplatesImpl#defineTransletClasses()<--TemplatesImpl#getTransletInstance()<--TemplatesImpl#newTransformer()<--TemplatesImpl.getOutputProperties()

然后我们来分析一下代码
看一下这个类的一些属性
2024-03-01T06:27:35.png
看注释一知半解吧
然后我们看看一下上面提到的方法
defineTransletClasses()
2024-03-01T06:33:27.png
相关字段:
2024-03-01T06:42:27.png
_bytecodes为定义translet类和辅助类的二维字节码数组,ABSTRACT_TRANSLET为translet类(主类)的超类名,即com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet。
这个代码读取难度很高,反正是需要

getTransletInstance()
2024-03-01T06:58:28.png
相关字段的含义:
2024-03-01T06:58:39.png

_name 主类名称,若未知则为默认名null;_class为一个包含了translet 类的Class对象的Class数组,默认为null;_transletIndex为主类translet类在数组_class[]和_bytecodes中的索引,默认为-1。

简单明了,getTransletInstance()的作用就是实例化一个translet类的实例对象并返回:

AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
...
return translet;

newTransformer()
2024-03-01T06:59:13.png
该方法就是实现了Templates接口定义的newTransformer()方法。实例化一个TransformerImpl对象赋给transformer,然后返回。实例化时调用getTransletInstance(),且还需要传入其他三个参数:
2024-03-01T06:59:24.png
_outputProperties是一个Properties对象,_indentNumber表示输出缩进添加的空格数,_tfactory 是一个TransformerFactoryImpl对象,默认为null。

defineTransletClasses()的作用是从二维字节码数组_bytecodes获取主类translet类和辅助类各自的Class对象,并将主类的Class对象存放在_class[_transletIndex],辅助类的Class对象存放在_auxClasses(Map<String, Class<?>>)中。

怎么获取的呢?_bytecodes的长度就是_class数组的长度也是Class对象的个数classCount(这里将二维数组化为了一维数组)。这段代码:

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

个数大于1,那么就会有辅助类,这段代码:

if (classCount > 1) {
_auxClasses = new HashMap<>();
}

最后通过for循环遍历每个Class对象,若超类名为ABSTRACT_TRANSLET字段代表的类名,则该Class对象即为定义主类translet类的字节码,此时的i即为_transletIndex;若不是,则存入_auxClasses中,这段代码:

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

// Check if this is the main class
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {

   _transletIndex = i;

}
else {

   _auxClasses.put(_class[i].getName(), _class[i]);

}
}

核心简单来说就是,TemplatesImpl对象在getTransletInstance()时,会先调用defineTransletClasses()获取主类translet类的Class对象,然后实例化。其中defineTransletClasses()中调用内部自定义的TransletClassLoader#defineClass()加载字节码来获取translet类的Class对象,而该字节码是我们可控输入的。这就是漏洞的原因

POC编写

根据上面的详细分析,要使得newTransformer()时加载恶意字节码,应传入的参数及要求:

_name,只要是不为空的String 类型    

_tfactory  不能为null,得是一个TransformerFactoryImpl对象,因为实例化TransletClassLoader会调用_tfactory.getExternalExtensionsMap(),为null会出错:

TransletClassLoader loader = (TransletClassLoader)
AccessController.doPrivileged(new PrivilegedAction() {

   public Object run() {
       return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
  }

});

_bytecodes  为实现抽象类AbstractTranslet的类的字节码。

我们先写一个实现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 Test extends AbstractTranslet {
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

}

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

}

static {

   System.out.println("调用了静态代码块");

}

{

   System.out.println("调用了普通代码块");

}

public Test() {

   System.out.println("调用无参构造器");

}
}

将其使用javac编译,再base64编码

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.TransformerConfigurationException;
import java.lang.reflect.Field;
import java.util.Base64;

public class TemplatesImpl_POC {
   public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, TransformerConfigurationException {
       byte[] code = Base64.getDecoder().decode("yv66vgAAADQAJgoACAAVCQAWABcIABgKABkAGggAGwgAHAcAHQcAHgEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAfAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgEACDxjbGluaXQ+AQAKU291cmNlRmlsZQEACVRlc3QuamF2YQwAEAARBwAgDAAhACIBABjosIPnlKjkuobmma7pgJrku6PnoIHlnZcHACMMACQAJQEAFeiwg+eUqOaXoOWPguaehOmAoOWZqAEAGOiwg+eUqOS6humdmeaAgeS7o+eggeWdlwEABFRlc3QBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWACEABwAIAAAAAAAEAAEACQAKAAIACwAAABkAAAADAAAAAbEAAAABAAwAAAAGAAEAAAALAA0AAAAEAAEADgABAAkADwACAAsAAAAZAAAABAAAAAGxAAAAAQAMAAAABgABAAAAEAANAAAABAABAA4AAQAQABEAAQALAAAAOQACAAEAAAAVKrcAAbIAAhIDtgAEsgACEgW2AASxAAAAAQAMAAAAEgAEAAAAGgAEABcADAAbABQAHAAIABIAEQABAAsAAAAlAAIAAAAAAAmyAAISBrYABLEAAAABAAwAAAAKAAIAAAATAAgAFAABABMAAAACABQ=");
       TemplatesImpl templates = new TemplatesImpl();

       setFieldValue(templates,"_name","xxx");
       setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
       setFieldValue(templates,"_bytecodes",new byte[][]{code});

       templates.newTransformer();
  }

   public static void setFieldValue(Object obj,String field,Object value) throws NoSuchFieldException, IllegalAccessException {
       Class<?> clazz = obj.getClass();
       Field fieldName = clazz.getDeclaredField(field);
       fieldName.setAccessible(true);
       fieldName.set(obj,value);
  }
}
    由于三个属性都是private且无public方法设置其值 ,故写了一个setFieldValue()反射设置值。因为获取实例时是newInstance(),故而静态代码块,普通代码块,无参构造器中的代码都会被执行。
本原创文章未经允许不得转载 | 当前页面:nnonkey k1n9的博客 » java安全之动态字节码加载进阶

评论