-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
181 additions
and
3 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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! |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 = "<html><body>\nWelcome to pwnpost, now with users!<hr>\n" | ||
username = flask.session.get("username", None) | ||
if username: | ||
page += """ | ||
<form action=draft method=post> | ||
Post:<textarea name=content>Write something!</textarea> | ||
<input type=checkbox name=publish>Publish | ||
<input type=submit value=Save> | ||
</form><br><a href=publish>Publish your drafts!</a><hr> | ||
""" | ||
|
||
for post in db.execute("SELECT * FROM posts").fetchall(): | ||
page += f"""<h2>Author: {post["author"]}</h2>""" | ||
if post["published"]: | ||
page += post["content"] + "<hr>\n" | ||
else: | ||
page += f"""(Draft post, showing first 12 characters):<br>{post["content"][:12]}<hr>""" | ||
else: | ||
page += """ | ||
<form action=login method=post> | ||
Username:<input type=text name=username> | ||
Password:<input type=text name=password> | ||
<input type=submit name=submit value=Login> | ||
</form><hr> | ||
""" | ||
|
||
return page + "</body></html>" | ||
|
||
app.secret_key = os.urandom(8) | ||
app.run("challenge.localhost", 8080 if os.geteuid() else 80) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
#!/opt/pwn.college/python | ||
|
||
import contextlib | ||
import urllib | ||
import time | ||
import sys | ||
import os | ||
|
||
from selenium import webdriver | ||
from selenium.webdriver.firefox.options import Options as FirefoxOptions | ||
from selenium.webdriver.firefox.service import Service as FirefoxService | ||
from selenium.webdriver.common.by import By | ||
from selenium.webdriver.support.wait import WebDriverWait | ||
from selenium.webdriver.support import expected_conditions as EC | ||
from selenium.common.exceptions import TimeoutException, WebDriverException | ||
|
||
os.environ["PATH"] = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" | ||
|
||
@contextlib.contextmanager | ||
def run_browser(): | ||
options = FirefoxOptions() | ||
options.add_argument("--headless") | ||
|
||
# workaround for ubuntu | ||
if os.path.exists("/snap/bin/geckodriver"): | ||
service = FirefoxService(executable_path="/snap/bin/geckodriver", log_path="/dev/null") | ||
else: | ||
service = FirefoxService(log_path="/dev/null") | ||
driver = webdriver.Firefox(service=service, options=options) | ||
|
||
try: | ||
yield driver | ||
finally: | ||
driver.quit() | ||
|
||
if len(sys.argv) <= 1: | ||
print(f"Usage: {sys.argv[0]} URL") | ||
sys.exit(1) | ||
|
||
url = sys.argv[1] | ||
url_parsed = urllib.parse.urlparse(url) | ||
try: | ||
assert url_parsed.hostname == "challenge.localhost", "hostname should be 'challenge.localhost'" | ||
assert url_parsed.port in {None, 80, 8080}, "port should be 80 or 8080" | ||
except AssertionError as e: | ||
print(f"Invalid URL: {e}") | ||
sys.exit(2) | ||
|
||
|
||
print("Visiting the URL!") | ||
with run_browser() as browser: | ||
try: | ||
browser.get(url) | ||
print("URL loaded...") | ||
|
||
print("Logging in as admin...") | ||
login = { "username": "admin", "password": open("/flag").read().strip() if url_parsed.port != 8080 else "pwn.college{fake_flag}" } | ||
for name, value in login.items(): | ||
field = browser.find_element(By.NAME, name) | ||
field.send_keys(value) | ||
submit_field = browser.find_element(By.NAME, "submit") | ||
submit_field.submit() | ||
|
||
WebDriverWait(browser, 10).until(EC.staleness_of(submit_field)) | ||
time.sleep(3) | ||
print("Visited! Go check if the attack worked!") | ||
except WebDriverException as e: | ||
if "can%E2%80%99t%20establish%20a%20connection" in str(e): | ||
print("Connection error! Is the service running?") | ||
else: | ||
print(f"Failure: {e}...") | ||
sys.exit(4) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters