Skip to content

Commit

Permalink
exfil
Browse files Browse the repository at this point in the history
  • Loading branch information
zardus committed Sep 2, 2024
1 parent 01505f8 commit 2160ac9
Show file tree
Hide file tree
Showing 4 changed files with 210 additions and 0 deletions.
2 changes: 2 additions & 0 deletions web-security/module.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ challenges:
name: XSS 5
- id: xss-rf-post
name: XSS 6
- id: xss-exfil-cookie
name: XSS 7
- id: level-11
name: CSRF 1
description: Exploit a cross site request forgery vulnerability
Expand Down
14 changes: 14 additions & 0 deletions web-security/xss-exfil-cookie/DESCRIPTION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Depending on the attacker's goals, what they might actually be after is the victim's entire account.
For example, attackers might use XSS to exfiltrate victim authentication data and then use this data to take over the victim's account.

Authentication data is often stored via browser cookies, such as what happened in `Authentication Bypass 2` (but, typically, much more secure).
If an attacker can leak these cookies, the result can be disastrous for the victim.

This level stores the authentication data for the logged in user in a cookie.
You must use XSS to leak this cookie so that you can, in turn, use it in a request to impersonate the `admin` user.
This exfiltration will happen over HTTP to a server that you run, and everything you need is available via JavaScript's `fetch()` and its ability to access (some) site cookies.

----
**HINT:**
By "server that you run", we really mean that listening on a port with `nc` will be sufficient.
Look at the `-l` and `-v` options to `nc`.
117 changes: 117 additions & 0 deletions web-security/xss-exfil-cookie/server
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
#!/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.geteuid() == 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[-10:]])
# 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")

response = flask.redirect("/")
response.set_cookie('auth', username+"|"+password)
return response

@app.route("/draft", methods=["POST"])
def challenge_draft():
username, password = flask.request.cookies.get("auth", "|").split("|")
user = db.execute("SELECT * FROM users WHERE username = ? AND password = ?", (username, password)).fetchone()
if not user:
flask.abort(403, "Invalid username or password")

if username == "admin":
flask.abort(400, "pwnpost no longer supports admin posting due to rampant flag disclosure")

content = flask.request.form.get("content", "")
# https://www.sqlite.org/lang_insert.html
db.execute(
"INSERT INTO posts (content, author, published) VALUES (?, ?, ?)",
(content, username, bool(flask.request.form.get("publish")))
)
return flask.redirect("/")

@app.route("/publish", methods=["POST"])
def challenge_publish():
username, password = flask.request.cookies.get("auth", "|").split("|")
user = db.execute("SELECT * FROM users WHERE username = ? AND password = ?", (username, password)).fetchone()
if not user:
flask.abort(403, "Invalid username or password")

if username == "admin":
flask.abort(400, "pwnpost no longer supports admin posting due to rampant flag disclosure")

# https://www.sqlite.org/lang_update.html
db.execute("UPDATE posts SET published = TRUE WHERE author = ?", [username])
return flask.redirect("/")

@app.route("/", methods=["GET"])
def challenge_get():
page = "<html><body>\nWelcome to pwnpost, now with users!<hr>\n"
username, password = flask.request.cookies.get("auth", "|").split("|")
user = db.execute("SELECT * FROM users WHERE username = ? AND password = ?", (username, password)).fetchone()
if user:
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>
<form action=publish method=post><input type=submit value="Publish All Drafts"></form><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"
elif post["author"] == username:
page += "<b>YOUR DRAFT POST:</b> " + 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)
77 changes: 77 additions & 0 deletions web-security/xss-exfil-cookie/victim
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#!/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.setuid(os.geteuid())
os.environ.clear()
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}")[-10:]
}
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)

0 comments on commit 2160ac9

Please sign in to comment.