diff --git a/web-security/level-10/.config b/web-security/level-10/.config deleted file mode 100644 index f599e28..0000000 --- a/web-security/level-10/.config +++ /dev/null @@ -1 +0,0 @@ -10 diff --git a/web-security/level-10/DESCRIPTION.md b/web-security/level-10/DESCRIPTION.md new file mode 100644 index 0000000..f17fc9f --- /dev/null +++ b/web-security/level-10/DESCRIPTION.md @@ -0,0 +1,7 @@ +Actual XSS exploits try to achieve something more than `alert("PWNED")`. +A very common goal is to use the ability to execute JavaScript inside a victim's browser to initiate new HTTP requests masquerading as the victim. +This can be done in a number of ways, including using JavaScript's `fetch()` function. + +This challenge implements a more complex application, and you will need to retrieve the flag out of the `admin` user's unpublished draft post. +After XSS-injecting the `admin`, you must use the injection to make an HTTP request (as the `admin` user) to enable you to read the flag. +Good luck! diff --git a/web-security/level-10/run b/web-security/level-10/run deleted file mode 120000 index 84ba55b..0000000 --- a/web-security/level-10/run +++ /dev/null @@ -1 +0,0 @@ -../run \ No newline at end of file diff --git a/web-security/level-10/server b/web-security/level-10/server new file mode 100755 index 0000000..942c062 --- /dev/null +++ b/web-security/level-10/server @@ -0,0 +1,102 @@ +#!/opt/pwn.college/python + +import tempfile +import sqlite3 +import flask +import os + +app = flask.Flask(__name__) + +class TemporaryDB: + def __init__(self): + self.db_file = tempfile.NamedTemporaryFile("x", suffix=".db") + + def execute(self, sql, parameters=()): + connection = sqlite3.connect(self.db_file.name) + connection.row_factory = sqlite3.Row + cursor = connection.cursor() + result = cursor.execute(sql, parameters) + connection.commit() + return result + +flag = open("/flag").read().strip() if os.getuid() == 0 else "pwn.college{fake_flag}" + +db = TemporaryDB() +# https://www.sqlite.org/lang_createtable.html +db.execute("""CREATE TABLE IF NOT EXISTS posts AS SELECT ? AS content, "admin" AS author, FALSE AS published""", [flag]) +db.execute("""CREATE TABLE IF NOT EXISTS users AS SELECT "admin" AS username, ? as password""", [flag]) +# https://www.sqlite.org/lang_insert.html +db.execute("""INSERT INTO users SELECT "guest" as username, "password" as password""") +db.execute("""INSERT INTO users SELECT "hacker" as username, "1337" as password""") + +@app.route("/login", methods=["POST"]) +def challenge_login(): + username = flask.request.form.get("username") + password = flask.request.form.get("password") + if not username: + flask.abort(400, "Missing `username` form parameter") + if not password: + flask.abort(400, "Missing `password` form parameter") + + # https://www.sqlite.org/lang_select.html + user = db.execute("SELECT * FROM users WHERE username = ? AND password = ?", (username, password)).fetchone() + if not user: + flask.abort(403, "Invalid username or password") + + flask.session["username"] = username + return flask.redirect("/") + +@app.route("/draft", methods=["POST"]) +def challenge_draft(): + if "username" not in flask.session: + flask.abort(403, "Log in first!") + + content = flask.request.form.get("content", "") + # https://www.sqlite.org/lang_insert.html + db.execute( + "INSERT INTO posts (content, author, published) VALUES (?, ?, ?)", + (content, flask.session.get("username"), bool(flask.request.form.get("publish"))) + ) + return flask.redirect("/") + +@app.route("/publish", methods=["GET"]) +def challenge_publish(): + if "username" not in flask.session: + flask.abort(403, "Log in first!") + + # https://www.sqlite.org/lang_update.html + db.execute("UPDATE posts SET published = TRUE WHERE author = ?", [flask.session.get("username")]) + return flask.redirect("/") + +@app.route("/", methods=["GET"]) +def challenge_get(): + page = "
\nWelcome to pwnpost, now with users!