diff --git a/web-security/module.yml b/web-security/module.yml index 02a81fd..745be7b 100644 --- a/web-security/module.yml +++ b/web-security/module.yml @@ -30,14 +30,16 @@ challenges: name: SQLi 4 - id: level-7 name: SQLi 5 -- id: level-8 +- id: xss-stored-html name: XSS 1 +- id: level-8 + name: XSS 2 description: Exploit a cross site scripting vulnerability - id: level-9 - name: XSS 2 + name: XSS 3 description: Exploit a cross site scripting vulnerability with more complicated context - id: level-10 - name: XSS 3 + name: XSS 4 description: Exploit a cross site scripting vulnerability to cause a user action - id: level-11 name: SSRF 1 diff --git a/web-security/xss-stored-html/DESCRIPTION.md b/web-security/xss-stored-html/DESCRIPTION.md new file mode 100644 index 0000000..ef193fc --- /dev/null +++ b/web-security/xss-stored-html/DESCRIPTION.md @@ -0,0 +1,33 @@ +Semantic gaps can occur (and lead to security issues) at the interface of any two technologies. +So far, we have seen them happen between: + +- A web application and the file system, leading to path traversal. +- A web application and the command line shell, leading to command injection. +- A web application and the database, leading to SQL injection. + +One part of the web application story that we have not yet looked at is the _web browser_. +We will remedy that oversight with this challenge. + +A modern web browser is an extraordinarily complex piece of software. +It renders [HTML](https://en.wikipedia.org/wiki/HTML), executes [JavaScript](https://en.wikipedia.org/wiki/JavaScript), parses [CSS](https://en.wikipedia.org/wiki/CSS), lets you access pwn.college, and much much more. +Specifically important to our purposes is the HTML that you have seen being generated by every challenge in this module. +When the web application generated paths, we ended up with path traversals. +When the web application generated shell commands, we ended up with shell injections. +When the web application generated SQL queries, we ended up with SQL injections. +Do we really think HTML will fare any better? +Of course not. + +The class of vulnerabilities in which injections occur into client-side web data (such as HTML) is called _Cross Site Scripting_, or XSS for short (to avoid the name collision with Cascading Style Sheets). +Unlike the previous injections, where the victim was the web server itself, the victims of XSS are _other users of the web application_. +In a typical XSS exploit, an attacker will cause their own code to be injected into (typically) the HTML produced by a web application and viewed by a victim user. +This will then allow the attacker to gain some control within the victim's browser, leading to a number of potential downstream shenanigans. + +This challenge is a very first step in this direction. +As before, you will have the `/challenge/server` web server. +This challenge explores something called _Stored XSS_, which means that data that you store on the server (in this case, posts in a forum) will end up being shown to a victim user. +Thus, we need a victim to view these posts! +You will now have a `/challenge/victim` program that simulates a victim user visiting the web server. + +Set up your attack and invoke `/challenge/victim` with the URL that will trigger the Stored XSS. +In this level, all you have to do is inject a textbox. +If our victim script sees three textboxes, we will give you the flag! diff --git a/web-security/xss-stored-html/server b/web-security/xss-stored-html/server new file mode 100755 index 0000000..866e260 --- /dev/null +++ b/web-security/xss-stored-html/server @@ -0,0 +1,43 @@ +#!/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 + +db = TemporaryDB() +# https://www.sqlite.org/lang_createtable.html +db.execute("""CREATE TABLE IF NOT EXISTS posts AS SELECT "First Post!" AS content""") + +@app.route("/", methods=["POST"]) +def challenge_post(): + content = flask.request.form.get("content", "") + db.execute("INSERT INTO posts VALUES (?)", [content]) + return flask.redirect(flask.request.path) + +@app.route("/", methods=["GET"]) +def challenge_get(): + page = "\nWelcome to pwnpost, the anonymous posting service. Post away!\n" + page += "
Post:
\n" + + for post in db.execute("SELECT content FROM posts").fetchall(): + page += "
" + post["content"] + "\n" + + return page + "" + +app.secret_key = os.urandom(8) +app.run("challenge.localhost", 8080 if os.geteuid() else 80) diff --git a/web-security/xss-stored-html/victim b/web-security/xss-stored-html/victim new file mode 100755 index 0000000..9b4404a --- /dev/null +++ b/web-security/xss-stored-html/victim @@ -0,0 +1,32 @@ +#!/opt/pwn.college/python + +import requests +import urllib +import sys +import re + +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) + +if len(sys.argv) <= 1: + print(f"Usage: {sys.argv[0]} URL") + sys.exit(1) + +print("Visiting the URL...") +try: + num_inputs = len(re.findall(r"", requests.get(url, timeout=1).text)) + if num_inputs <= 1: + print("You did not inject an textbox...") + elif url_parsed.port == 8080: + print("You got it! Now do it for real (on port 80).") + else: + print("You got it! Here is your flag:") + print(open("/flag").read()) +except requests.exceptions.ConnectionError: + print("Connection error... Is the service running?")