-
Notifications
You must be signed in to change notification settings - Fork 14
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
4 changed files
with
113 additions
and
3 deletions.
There are no files selected for viewing
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
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,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! |
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,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 = "<html><body>\nWelcome to pwnpost, the anonymous posting service. Post away!\n" | ||
page += "<form method=post>Post:<input type=text name=content></form>\n" | ||
|
||
for post in db.execute("SELECT content FROM posts").fetchall(): | ||
page += "<hr>" + post["content"] + "\n" | ||
|
||
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,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"<input[^<]*>", requests.get(url, timeout=1).text)) | ||
if num_inputs <= 1: | ||
print("You did not inject an <input> 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?") |