From ed034ab36e25fc263badc30c2ef8f062b095d861 Mon Sep 17 00:00:00 2001 From: Dan Goldsmith Date: Sun, 7 Mar 2021 18:31:36 +0000 Subject: [PATCH 1/9] Web TRainer moved to its own Directory --- {Week6_SQLi/Trainer => Web_Trainer}/docker-compose.yaml | 0 Week6_SQLi/WebTrainer.md | 2 ++ 2 files changed, 2 insertions(+) rename {Week6_SQLi/Trainer => Web_Trainer}/docker-compose.yaml (100%) create mode 100644 Week6_SQLi/WebTrainer.md diff --git a/Week6_SQLi/Trainer/docker-compose.yaml b/Web_Trainer/docker-compose.yaml similarity index 100% rename from Week6_SQLi/Trainer/docker-compose.yaml rename to Web_Trainer/docker-compose.yaml diff --git a/Week6_SQLi/WebTrainer.md b/Week6_SQLi/WebTrainer.md new file mode 100644 index 0000000..03f58b9 --- /dev/null +++ b/Week6_SQLi/WebTrainer.md @@ -0,0 +1,2 @@ +The Web trainer has now moved to its own folder. +(As we are using it for several weeks) From 4faf2bddf72814a757970063e204c5c7491b9b80 Mon Sep 17 00:00:00 2001 From: Dan Goldsmith Date: Sun, 7 Mar 2021 19:55:57 +0000 Subject: [PATCH 2/9] Squashed 'Week8_Includes/Deserial/' content from commit 20e0f86 git-subtree-dir: Week8_Includes/Deserial git-subtree-split: 20e0f862648768cfcfa61fc8c6102c2e731987af --- .gitignore | 2 + NodeChallenge/docker-compose.yaml | 9 +++ NodeChallenge/webapp/Dockerfile | 16 +++++ NodeChallenge/webapp/package.json | 16 +++++ NodeChallenge/webapp/server.js | 25 +++++++ NodeChallenge/webapp/serverflag.txt | 1 + PickleDemo/docker-compose.yaml | 12 ++++ PickleDemo/webapp/Dockerfile | 21 ++++++ PickleDemo/webapp/REQUIREMENTS.txt | 3 + PickleDemo/webapp/app.py | 91 ++++++++++++++++++++++++++ PickleDemo/webapp/serverflag.txt | 1 + PickleDemo/webapp/templates/index.html | 59 +++++++++++++++++ PickleDemo/webapp/templates/load.html | 38 +++++++++++ PickleDemo/webapp/templates/pay.html | 44 +++++++++++++ README.md | 26 ++++++++ YamlDemo/docker-compose.yaml | 12 ++++ YamlDemo/webapp/Dockerfile | 20 ++++++ YamlDemo/webapp/REQUIREMENTS.txt | 3 + YamlDemo/webapp/app.py | 91 ++++++++++++++++++++++++++ YamlDemo/webapp/serverflag.txt | 1 + YamlDemo/webapp/templates/index.html | 59 +++++++++++++++++ YamlDemo/webapp/templates/load.html | 38 +++++++++++ YamlDemo/webapp/templates/pay.html | 44 +++++++++++++ 23 files changed, 632 insertions(+) create mode 100644 .gitignore create mode 100644 NodeChallenge/docker-compose.yaml create mode 100644 NodeChallenge/webapp/Dockerfile create mode 100644 NodeChallenge/webapp/package.json create mode 100644 NodeChallenge/webapp/server.js create mode 100644 NodeChallenge/webapp/serverflag.txt create mode 100644 PickleDemo/docker-compose.yaml create mode 100644 PickleDemo/webapp/Dockerfile create mode 100644 PickleDemo/webapp/REQUIREMENTS.txt create mode 100644 PickleDemo/webapp/app.py create mode 100644 PickleDemo/webapp/serverflag.txt create mode 100644 PickleDemo/webapp/templates/index.html create mode 100644 PickleDemo/webapp/templates/load.html create mode 100644 PickleDemo/webapp/templates/pay.html create mode 100644 README.md create mode 100644 YamlDemo/docker-compose.yaml create mode 100644 YamlDemo/webapp/Dockerfile create mode 100644 YamlDemo/webapp/REQUIREMENTS.txt create mode 100644 YamlDemo/webapp/app.py create mode 100644 YamlDemo/webapp/serverflag.txt create mode 100644 YamlDemo/webapp/templates/index.html create mode 100644 YamlDemo/webapp/templates/load.html create mode 100644 YamlDemo/webapp/templates/pay.html diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9b5e2e8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*~ +__pycache__ diff --git a/NodeChallenge/docker-compose.yaml b/NodeChallenge/docker-compose.yaml new file mode 100644 index 0000000..5ec25e2 --- /dev/null +++ b/NodeChallenge/docker-compose.yaml @@ -0,0 +1,9 @@ +version: "3.7" +services: + node: + build: + context: webapp + ports: + - "5000:3000" + expose: + - 5000 diff --git a/NodeChallenge/webapp/Dockerfile b/NodeChallenge/webapp/Dockerfile new file mode 100644 index 0000000..c7bcd50 --- /dev/null +++ b/NodeChallenge/webapp/Dockerfile @@ -0,0 +1,16 @@ +FROM node:buster-slim + +WORKDIR /opt/app + +#Requirements +ADD package.json /opt/app +ADD server.js /opt/app + +RUN npm install + +EXPOSE 3000 + +RUN apt-get update && apt-get install -y ncat +ADD serverflag.txt / + +CMD ["node", "server.js"] \ No newline at end of file diff --git a/NodeChallenge/webapp/package.json b/NodeChallenge/webapp/package.json new file mode 100644 index 0000000..2ef2d6e --- /dev/null +++ b/NodeChallenge/webapp/package.json @@ -0,0 +1,16 @@ +{ + "name": "Vulnerable_Deserialisation_App", + "version": "1.0.0", + "description": "Node.js on Docker", + "author": "First Last ", + "main": "server.js", + "scripts": { + "start": "node server.js" + }, + "dependencies": { + "express": "^4.16.1", + "cookie-parser": "latest", + "escape-html": "latest", + "node-serialize": "latest" + } +} diff --git a/NodeChallenge/webapp/server.js b/NodeChallenge/webapp/server.js new file mode 100644 index 0000000..88d0dcb --- /dev/null +++ b/NodeChallenge/webapp/server.js @@ -0,0 +1,25 @@ +var express = require('express'); +var cookieParser = require('cookie-parser'); +var escape = require('escape-html'); +var serialize = require('node-serialize'); +var app = express(); +app.use(cookieParser()) + +app.get('/', function(req, res) { + if (req.cookies.profile) { + var str = new Buffer(req.cookies.profile, 'base64').toString(); + var obj = serialize.unserialize(str); + if (obj.username) { + res.send("Hello " + escape(obj.username)); + } + } else { + res.cookie('profile', "eyJ1c2VybmFtZSI6IkFkaXR5YSIsImNvdW50cnkiOiJpbmRpYSIsImNpdHkiOiJEZWxoaSJ9", { + maxAge: 900000, + httpOnly: true + }); + } + res.send("Hello World"); +}); +app.listen(3000); +console.log("Listening on port 3000..."); + diff --git a/NodeChallenge/webapp/serverflag.txt b/NodeChallenge/webapp/serverflag.txt new file mode 100644 index 0000000..3088637 --- /dev/null +++ b/NodeChallenge/webapp/serverflag.txt @@ -0,0 +1 @@ +245{NodeRCEExample} diff --git a/PickleDemo/docker-compose.yaml b/PickleDemo/docker-compose.yaml new file mode 100644 index 0000000..ccea637 --- /dev/null +++ b/PickleDemo/docker-compose.yaml @@ -0,0 +1,12 @@ +version: "3.7" +services: + flask: + build: + context: webapp + ports: + - "5000:5000" + expose: + - 5000 + environment: + - FLASK_ENV=development + - FLASK_APP=/opt/webapp/app.py diff --git a/PickleDemo/webapp/Dockerfile b/PickleDemo/webapp/Dockerfile new file mode 100644 index 0000000..f25bd47 --- /dev/null +++ b/PickleDemo/webapp/Dockerfile @@ -0,0 +1,21 @@ +FROM cueh/flask + +USER root +#Netcat for the Shell +RUN apt-get update && apt-get install -y ncat + +WORKDIR /opt + +#Copy Files +ADD REQUIREMENTS.txt /opt + +#Install Requirements +RUN pip install -r REQUIREMENTS.txt + +ADD serverflag.txt / + +ADD app.py /opt/webapp/ +ADD templates/* /opt/webapp/templates/ + +CMD ["flask", "run", "--host=0.0.0.0"] + diff --git a/PickleDemo/webapp/REQUIREMENTS.txt b/PickleDemo/webapp/REQUIREMENTS.txt new file mode 100644 index 0000000..0b85942 --- /dev/null +++ b/PickleDemo/webapp/REQUIREMENTS.txt @@ -0,0 +1,3 @@ +pyyaml +flask + diff --git a/PickleDemo/webapp/app.py b/PickleDemo/webapp/app.py new file mode 100644 index 0000000..ef22315 --- /dev/null +++ b/PickleDemo/webapp/app.py @@ -0,0 +1,91 @@ +""" +Flask Based Rework of our app + +So much easier with a web interface +""" +import flask +from flask import request +import pickle + +class ShoppingItem: + def __init__(self, name, cost, number = 1): + self.name = name + self.cost = cost + self.number = number + +class ShoppingList: + def __init__(self): + self.shoppingList = [] + + def addItem(self, item): + self.shoppingList.append(item) + + def calcCost(self): + totalCost = 0 + for item in self.shoppingList: + totalCost += item.cost * item.number + + return totalCost + + +def createList(): + """Helper Function to create a list""" + theList = ShoppingList() + + item = ShoppingItem("foo widget", 100) + theList.addItem(item) + item = ShoppingItem("bar widget", 10, 5) + theList.addItem(item) + return theList + + +# ------------ AND THE APP ITSELF ----------- +app = flask.Flask(__name__) +app.theList = createList() + +@app.route('/') +def main(): + return flask.render_template('index.html', theList = app.theList) + +@app.route("/save") +def save(): + #We can save the basket to a file + with open("/opt/webapp/backup.pkl","wb") as fd: + out = pickle.dump(app.theList, fd) + #fd.write(out) + + return flask.send_file('backup.pkl', + mimetype='application/octet-stream', + attachment_filename='backup.pkl', + as_attachment=True) + + +@app.route("/load", methods=["GET","POST"]) +def load(): + message = None + if request.method == "POST": + print (request.files) + if 'uploadFile' in request.files: + + theFile = request.files['uploadFile'] + try: + data = pickle.load(theFile)#, Loader=yaml.Loader) + app.theList = data + message = "Upload Successful" + except Exception as ex: + message = "Error Loading List {0}".format(ex) + + + else: + message = "You must select a file to upload" + + + return flask.render_template('load.html', message=message) + +@app.route("/pay") +def pay(): + + totalCost = app.theList.calcCost() + + return flask.render_template("pay.html", cost=totalCost) + diff --git a/PickleDemo/webapp/serverflag.txt b/PickleDemo/webapp/serverflag.txt new file mode 100644 index 0000000..891e7f9 --- /dev/null +++ b/PickleDemo/webapp/serverflag.txt @@ -0,0 +1 @@ +CUEH{"R0tten_P1ckLes"} diff --git a/PickleDemo/webapp/templates/index.html b/PickleDemo/webapp/templates/index.html new file mode 100644 index 0000000..9e2d9cf --- /dev/null +++ b/PickleDemo/webapp/templates/index.html @@ -0,0 +1,59 @@ + + + + + + + The Dodgy Shopper + + + + + +
+

Dodgy PICKLE Shopper v2

+ +

Buy Widgets and Stuff

+ +

Shopping Basket

+ + + + + + + + + + + + {% for item in theList.shoppingList %} + + + + + + + {% endfor %} + + + + + + + +
ItemCostQuantityTotal Cost
{{item.name}}£{{item.cost}}{{item.number}}£{{ item.cost * item.number}}
TOTAL£{{theList.calcCost()}}
+ + + + Pay +
+ + diff --git a/PickleDemo/webapp/templates/load.html b/PickleDemo/webapp/templates/load.html new file mode 100644 index 0000000..525ba2d --- /dev/null +++ b/PickleDemo/webapp/templates/load.html @@ -0,0 +1,38 @@ + + + + + + + The Dodgy Shopper + + + + + +
+ +

Dodgy PICKLE Shopper

+

Back to Home

+
+

Buy Widgets and Stuff

+ +

Load Saved Basket

+ +
+
+ + +
+ +
+ + {% if message %} +
{{message}}
+ {% endif %} +
+ + + + + diff --git a/PickleDemo/webapp/templates/pay.html b/PickleDemo/webapp/templates/pay.html new file mode 100644 index 0000000..d9f4386 --- /dev/null +++ b/PickleDemo/webapp/templates/pay.html @@ -0,0 +1,44 @@ + + + + + + + The Dodgy Shopper + + + + + + +
+ +

Dodgy PICKLE Shopper

+

Back to Home

+
+ +

Buy Widgets and Stuff

+ +

PAY FOR STUFF

+ + {% if cost > 0 %} +

You Owe £{{ cost }}

+
+
+ + + +
+ +
+ + {% else %} +
You Win, Have a flag CUEH{M@nual_P1kl_Mods}
+ {% endif %} + +
+ + + + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..abdb9cd --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +# Deserialisation Trainers + +Various Training / Demos for Insecure Deserialisation + +## YAML Demo + +This service is vulnerable to insecure deserialisation through PyYaml +and will let you try out the example given in the course materials. + +### Starting Stopping the service + +Start + +``` +$ cd YamlDemo +$ docker-compose up +``` + +Stop + +``` +$ cd YamlDemo +$ docker-compose down +``` + + diff --git a/YamlDemo/docker-compose.yaml b/YamlDemo/docker-compose.yaml new file mode 100644 index 0000000..ccea637 --- /dev/null +++ b/YamlDemo/docker-compose.yaml @@ -0,0 +1,12 @@ +version: "3.7" +services: + flask: + build: + context: webapp + ports: + - "5000:5000" + expose: + - 5000 + environment: + - FLASK_ENV=development + - FLASK_APP=/opt/webapp/app.py diff --git a/YamlDemo/webapp/Dockerfile b/YamlDemo/webapp/Dockerfile new file mode 100644 index 0000000..f5a6357 --- /dev/null +++ b/YamlDemo/webapp/Dockerfile @@ -0,0 +1,20 @@ +FROM cueh/flask + +USER root +#Netcat for the Shell +RUN apt-get update && apt-get install -y ncat + +WORKDIR /opt + +#Copy Files +ADD REQUIREMENTS.txt /opt + +#Install Requirements +RUN pip install -r REQUIREMENTS.txt + +ADD serverflag.txt / + +ADD app.py /opt/webapp/ +ADD templates/* /opt/webapp/templates/ + +CMD ["flask", "run", "--host=0.0.0.0"] diff --git a/YamlDemo/webapp/REQUIREMENTS.txt b/YamlDemo/webapp/REQUIREMENTS.txt new file mode 100644 index 0000000..cf7cb50 --- /dev/null +++ b/YamlDemo/webapp/REQUIREMENTS.txt @@ -0,0 +1,3 @@ +pyyaml + + diff --git a/YamlDemo/webapp/app.py b/YamlDemo/webapp/app.py new file mode 100644 index 0000000..58c0e5e --- /dev/null +++ b/YamlDemo/webapp/app.py @@ -0,0 +1,91 @@ +""" +Flask Based Rework of our app + +So much easier with a web interface +""" +import flask +import yaml +from flask import request + +class ShoppingItem: + def __init__(self, name, cost, number = 1): + self.name = name + self.cost = cost + self.number = number + +class ShoppingList: + def __init__(self): + self.shoppingList = [] + + def addItem(self, item): + self.shoppingList.append(item) + + def calcCost(self): + totalCost = 0 + for item in self.shoppingList: + totalCost += item.cost * item.number + + return totalCost + + +def createList(): + """Helper Function to create a list""" + theList = ShoppingList() + + item = ShoppingItem("foo widget", 100) + theList.addItem(item) + item = ShoppingItem("bar widget", 10, 5) + theList.addItem(item) + return theList + + +# ------------ AND THE APP ITSELF ----------- +app = flask.Flask(__name__) +app.theList = createList() + +@app.route('/') +def main(): + return flask.render_template('index.html', theList = app.theList) + +@app.route("/save") +def save(): + #We can save the basket to a file + with open("/opt/webapp/backup.yaml","w") as fd: + out = yaml.dump(app.theList) + fd.write(out) + + return flask.send_file('backup.yaml', + mimetype='text/yaml', + attachment_filename='backup.yaml', + as_attachment=True) + + +@app.route("/load", methods=["GET","POST"]) +def load(): + message = None + if request.method == "POST": + print (request.files) + if 'uploadFile' in request.files: + + theFile = request.files['uploadFile'] + try: + data = yaml.load(theFile, Loader=yaml.Loader) + app.theList = data + message = "Upload Successful" + except Exception as ex: + message = "Error Loading List {0}".format(ex) + + + else: + message = "You must select a file to upload" + + + return flask.render_template('load.html', message=message) + +@app.route("/pay") +def pay(): + + totalCost = app.theList.calcCost() + + return flask.render_template("pay.html", cost=totalCost) + diff --git a/YamlDemo/webapp/serverflag.txt b/YamlDemo/webapp/serverflag.txt new file mode 100644 index 0000000..cc4bf4d --- /dev/null +++ b/YamlDemo/webapp/serverflag.txt @@ -0,0 +1 @@ +CUEH{Yam1_B4sed_Sh3lls} diff --git a/YamlDemo/webapp/templates/index.html b/YamlDemo/webapp/templates/index.html new file mode 100644 index 0000000..9b25b9a --- /dev/null +++ b/YamlDemo/webapp/templates/index.html @@ -0,0 +1,59 @@ + + + + + + + The Dodgy Shopper + + + + + +
+

Dodgy YAML Shopper

+ +

Buy Widgets and Stuff

+ +

Shopping Basket

+ + + + + + + + + + + + {% for item in theList.shoppingList %} + + + + + + + {% endfor %} + + + + + + + +
ItemCostQuantityTotal Cost
{{item.name}}£{{item.cost}}{{item.number}}£{{ item.cost * item.number}}
TOTAL£{{theList.calcCost()}}
+ + + + Pay +
+ + diff --git a/YamlDemo/webapp/templates/load.html b/YamlDemo/webapp/templates/load.html new file mode 100644 index 0000000..fb3f8d6 --- /dev/null +++ b/YamlDemo/webapp/templates/load.html @@ -0,0 +1,38 @@ + + + + + + + The Dodgy Shopper + + + + + +
+ +

Dodgy YAML Shopper

+

Back to Home

+
+

Buy Widgets and Stuff

+ +

Load Saved Basket

+ +
+
+ + +
+ +
+ + {% if message %} +
{{message}}
+ {% endif %} +
+ + + + + diff --git a/YamlDemo/webapp/templates/pay.html b/YamlDemo/webapp/templates/pay.html new file mode 100644 index 0000000..7e33afc --- /dev/null +++ b/YamlDemo/webapp/templates/pay.html @@ -0,0 +1,44 @@ + + + + + + + The Dodgy Shopper + + + + + + +
+ +

Dodgy YAML Shopper

+

Back to Home

+
+ +

Buy Widgets and Stuff

+ +

PAY FOR STUFF

+ + {% if cost > 0 %} +

You Owe £{{ cost }}

+
+
+ + + +
+ +
+ + {% else %} +
You Win, Have a flag CUEH{YAML_1s_Fun}
+ {% endif %} + +
+ + + + + From 78783c6009e1161da71d4c583a156c1b7d4a9171 Mon Sep 17 00:00:00 2001 From: Dan Goldsmith Date: Sun, 7 Mar 2021 19:57:18 +0000 Subject: [PATCH 3/9] Deserialsation added --- README.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/README.md b/README.md index d20cf17..7508c50 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,31 @@ # 245-Labs Lab Materials for 245CT + + + +## Subtrees + + +Adding Remote + +``` +git remote add -f +``` + + +Adding Subreop + +``` +git subtree add --prefix --squash +``` + +Updating + +``` +git fetch +git subtree pull --prefix --squash +``` + + + From 3d0823a2543ad8346ad9a46b47eb544e2795cc54 Mon Sep 17 00:00:00 2001 From: Dan Goldsmith Date: Mon, 8 Mar 2021 18:20:30 +0000 Subject: [PATCH 4/9] Squashed 'Week7_XSS/Challenges/' changes from 688722e..37fb165 37fb165 Merge pull request #10 from aa9863/Flags 9ebe015 More index updates 2559fde Index page updated 6dd1410 Badge fd23b27 LEvels cna now have flags 020691c Test for Flags added 835ca19 Readme Tidy b91799c Update Readme 336a9eb Merge pull request #8 from aa9863/Testing e8d753a Intergration tests added 5bf383f Merge pull request #7 from aa9863/Testing 42f438d Testing for Main views and Driver added 763ea16 Initial set of unittests for flask added 36ebe8b Refactor so we have the app in a seperate module for testing ee6b5e2 Convert Selenoid into a Class 33e09be Break app into seperate File 6a90be1 New LEvesl added 06451b5 Changelog added 49261e3 Merge pull request #6 from aa9863/FilterRefactor 7b888c0 Changelog added, and Readme complete f5ef9a4 Readme update e7b2dc8 Rework Nav menus 13790ab Refactor Levels into their own file 507dd1b Update nav so ar shows levels c60312d Add stop condition to level listing 3b0296d Added Simple Filter bfc0b2c New style levels added for Training and level 0 d254ec3 Basic for Training level added 5d3d34b Test calling via Dictionary 1f049ab Break filters into a seperate file cffa227 Change Levels to remove need for level numbers 5cfcabd Fix Render so it loads bootstrap (as I am an Ijit 17eadb7 Levels 7 and 8 added git-subtree-dir: Week7_XSS/Challenges git-subtree-split: 37fb16535213c6db064cd5adad43b90b4b88081a --- .gitignore | 4 +- CHANGES.md | 17 + README.md | 27 +- docker-compose.yml | 4 +- webapp/setup.py | 15 + webapp/xss_trainer/__init__.py | 8 + webapp/xss_trainer/app.py | 341 ------------------ webapp/xss_trainer/driver.py | 75 ---- webapp/xss_trainer/levels/__init__.py | 3 + webapp/xss_trainer/levels/baseLevels.py | 141 ++++++++ webapp/xss_trainer/levels/meta.py | 71 ++++ webapp/xss_trainer/meta.py | 27 ++ webapp/xss_trainer/templates/index.html | 13 +- webapp/xss_trainer/templates/levelBase.html | 30 +- .../levels/{level5.html => BasicPreg.html} | 0 .../levels/{level4.html => BasicRegexp.html} | 0 .../templates/levels/BootstrapTags.html | 70 ++++ .../levels/{level2.html => ClientSide.html} | 0 .../{level7.html => MarkdownOutput.html} | 2 + .../{level6.html => ScriptTagFilter.html} | 0 .../{level3.html => SimpleReplace.html} | 0 .../templates/levels/TagAttributes.html | 55 +++ .../templates/levels/levelTemplate.html | 4 + .../levels/{level1.html => noFilter.html} | 0 webapp/xss_trainer/templates/nav.html | 54 +-- webapp/xss_trainer/templates/render.html | 2 + webapp/xss_trainer/test/test_driver.py | 28 ++ webapp/xss_trainer/test/test_flask.py | 114 ++++++ webapp/xss_trainer/test/test_web.py | 92 +++++ webapp/xss_trainer/views.py | 191 ++++++++++ webapp/xss_trainer/webdriver.py | 101 ++++++ 31 files changed, 1013 insertions(+), 476 deletions(-) create mode 100644 CHANGES.md create mode 100644 webapp/setup.py create mode 100644 webapp/xss_trainer/__init__.py delete mode 100644 webapp/xss_trainer/app.py delete mode 100644 webapp/xss_trainer/driver.py create mode 100644 webapp/xss_trainer/levels/__init__.py create mode 100644 webapp/xss_trainer/levels/baseLevels.py create mode 100644 webapp/xss_trainer/levels/meta.py create mode 100644 webapp/xss_trainer/meta.py rename webapp/xss_trainer/templates/levels/{level5.html => BasicPreg.html} (100%) rename webapp/xss_trainer/templates/levels/{level4.html => BasicRegexp.html} (100%) create mode 100644 webapp/xss_trainer/templates/levels/BootstrapTags.html rename webapp/xss_trainer/templates/levels/{level2.html => ClientSide.html} (100%) rename webapp/xss_trainer/templates/levels/{level7.html => MarkdownOutput.html} (92%) rename webapp/xss_trainer/templates/levels/{level6.html => ScriptTagFilter.html} (100%) rename webapp/xss_trainer/templates/levels/{level3.html => SimpleReplace.html} (100%) create mode 100644 webapp/xss_trainer/templates/levels/TagAttributes.html rename webapp/xss_trainer/templates/levels/{level1.html => noFilter.html} (100%) create mode 100644 webapp/xss_trainer/test/test_driver.py create mode 100644 webapp/xss_trainer/test/test_flask.py create mode 100644 webapp/xss_trainer/test/test_web.py create mode 100644 webapp/xss_trainer/views.py create mode 100644 webapp/xss_trainer/webdriver.py diff --git a/.gitignore b/.gitignore index c24e901..ed72fea 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ +env/ +*egg-info *~ -__pycache__ \ No newline at end of file +__pycache__ diff --git a/CHANGES.md b/CHANGES.md new file mode 100644 index 0000000..d437d98 --- /dev/null +++ b/CHANGES.md @@ -0,0 +1,17 @@ +# Version 0.2.1 + + - Levels can now give out flags + + +# Version 0.2 + + - Rework So filters are in a seperate module to the app + - Unit Tests and other fun stuff added + +## Version 0.1 + + - Initial Version. + - Flask based frontend + - Basic XSS detecton through selenium + - PHP script execution + diff --git a/README.md b/README.md index 5880079..6d46d68 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,10 @@ Trainer for 245CT and XSS attacks +[![made-with-python](https://img.shields.io/badge/Made%20with-Python-1f425f.svg)](https://www.python.org/) +[![made-with-Markdown](https://img.shields.io/badge/Made%20with-Markdown-1f425f.svg)](http://commonmark.org) +[![Version-0.2](https://img.shields.io/badge/Version-0.2-green.svg)](https://shields.io/) + ## Plan Standalone Documentation and Levels for XSS attacks @@ -24,17 +28,19 @@ Standalone Documentation and Levels for XSS attacks ## Levels -| Level | Lang | Notes | -|-------|--------|---------------| -| 0 | Python | Training | -| 1 | Python | Training | -| 2 | Python | CLient Side | -| 3 | Python | Basic Replace | -| 4 | Python | Regexp | -| 5 | PHP | Preg_replace | +| Level | Lang | Notes | +|-------|--------|------------------------------------| +| 0 | Python | Training | +| 1 | Python | Training | +| 2 | Python | CLient Side | +| 3 | Python | Basic Replace | +| 4 | Python | Regexp | +| 5 | PHP | Preg_replace | | 6 | Python | Stop Execute if "script" in string | +| 7 | Python | Markdown | +| 8 | HTML | Attributes | + - ## Notes This might be useful for text @@ -45,3 +51,6 @@ https://pythonhosted.org/Flask-Markdown/ https://github.com/andymccurdy/redis-py + + +[![forthebadge](https://forthebadge.com/images/badges/powered-by-electricity.svg)](https://forthebadge.com) diff --git a/docker-compose.yml b/docker-compose.yml index d492979..16d420b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,8 +11,8 @@ services: - "./webapp/xss_trainer:/opt/xss_trainer" environment: - FLASK_ENV=development - - FLASK_APP=/opt/xss_trainer/app.py - + #- FLASK_APP=/opt/xss_trainer/app.py + - FLASK_APP=xss_trainer selenoid: image: cueh/selenoid diff --git a/webapp/setup.py b/webapp/setup.py new file mode 100644 index 0000000..8453990 --- /dev/null +++ b/webapp/setup.py @@ -0,0 +1,15 @@ +from setuptools import setup, find_packages + +setup( + name="245_XSSTrainer", + author="Dan Goldsmith", + author_email="djgoldsmith@googlemail.com", + version="0.0.2", + packages=find_packages(), + include_package_data=True, + install_requires = ["selenium", + "requests", + "flask-redis", + "jinja-markdown"] + +) diff --git a/webapp/xss_trainer/__init__.py b/webapp/xss_trainer/__init__.py new file mode 100644 index 0000000..e871e3b --- /dev/null +++ b/webapp/xss_trainer/__init__.py @@ -0,0 +1,8 @@ +""" +Break the Flask Initialisation into a separate file +""" + +#Create the App +import xss_trainer.meta +#And import the main application +from xss_trainer.views import * diff --git a/webapp/xss_trainer/app.py b/webapp/xss_trainer/app.py deleted file mode 100644 index 7c40e21..0000000 --- a/webapp/xss_trainer/app.py +++ /dev/null @@ -1,341 +0,0 @@ -""" -Very simple Flask App. For Testing -""" - - -import html -import urllib.parse -import logging - -import re - -import flask -from flask import Flask, session -import flask - -from flask_redis import FlaskRedis - -from jinja_markdown import MarkdownExtension -import markdown - -#Well that fills me with confidence.... -#from flask.ext.session import Session -#from flask_session import Session -import redis - -# My Selenium Driver -import driver - -MAX_LEVEL = 6 - -REDIS_URL = "redis://redis:6379/0" -SECRET_KEY = b"foobarbaz" - -app = flask.Flask(__name__) -app.config.update( - REDIS_URL = REDIS_URL, - #SESSION_TYPE= "redis", - #SESSION_REDIS = redis.from_url(REDIS_URL), - SESSION_COOKIE_SAMESITE='Lax', - ) - -app.config["SECRET_KEY"] = SECRET_KEY - -app.jinja_env.add_extension(MarkdownExtension) - -redis_client = FlaskRedis(app) -#Session(app) - -#Last Request -lastRequest = {} - -LEVELS = [(0,"Training"), - (1,"No Filter"), - (2,"ClientSide Filter"), - (3, "Simple Filter"), - (4,"Regexp Filter"), - (5,"PHP Filter"), - (6,"Script Filter")] -# (7,"Output")] - -import subprocess - -@app.route('/') -def main(): - """ - Render the homepage - """ - #flask.session["bleh"] = b"Bleh" - if "level" not in flask.session: - flask.session["level"] = 0 - - level = flask.session.get("level") - return flask.render_template('index.html', - level=level, - navLevels = LEVELS) - -@app.route("/reset") -def reset(): - """ - Clear the Session - """ - flask.session.clear() - return flask.redirect(flask.url_for("main")) - - -@app.route("/konami") -def konami(): - flask.session["level"] = 100 - return flask.redirect(flask.url_for("main")) - - -@app.route("/testPhp") -def test(): - """ - Testing - """ - #with open("/tmp/script.php","w") as fd: - theStr = ['$input = ""', - '$output = preg_replace("/", "") - - elif level == 4: - regexp = re.compile("<\/?script>", re.IGNORECASE) - payload = regexp.sub("", data) - - elif level == 5: - #PHP version of Level 4 - theStr = ['$input = "{0}"'.format(data), - '$output = preg_replace("/"# - #app.logger.info("Last Request was") - return flask.render_template("render.html", - payload=thePayload) - - -# --------------- TESTING CODEZ --------------- - - diff --git a/webapp/xss_trainer/driver.py b/webapp/xss_trainer/driver.py deleted file mode 100644 index 48a1fbd..0000000 --- a/webapp/xss_trainer/driver.py +++ /dev/null @@ -1,75 +0,0 @@ -""" -Class to check for XSS using Selenium - -https://stackoverflow.com/questions/42950789/selenium-open-local-files -""" - - -import logging - -from selenium import webdriver -from selenium.webdriver.common.desired_capabilities import DesiredCapabilities -from selenium.webdriver.support.ui import WebDriverWait -from selenium.webdriver.support import expected_conditions as EC -from selenium.common.exceptions import TimeoutException -import selenium - -#Create the options we will pass to the driver - -opt = webdriver.ChromeOptions() -opt.headless = True - - -def _fetchPage(thePage): - """ - Lets stick this in one method - As I am reusing it for the getPage and checkPage - """ - logging.debug("Creating Driver") - driver = webdriver.Remote(command_executor='http://selenoid:4444/wd/hub', - desired_capabilities=DesiredCapabilities.CHROME, - options = opt) - - logging.debug("Fetch Page") - driver.get(thePage) - - #Check for XSS - hasAlert = False - logging.warning("Looking for Alert") - try: - alert = driver.switch_to.alert - alert.accept() - logging.debug("alert accepted") - hasAlert = True - except selenium.common.exceptions.NoAlertPresentException: - logging.info("No Alert") - - #This needs to be after we have handled any alerts - pageText = driver.page_source - - driver.close() - driver.quit() - - return pageText, hasAlert - -def getPage(thePage): - """ - Get a page and return its source - """ - pageText, hasAlert = _fetchPage(thePage) - - return pageText - - -def checkPage(thePage): - """ - Get a page and check for XSS - """ - - pageText, hasAlert = _fetchPage(thePage) - return hasAlert - - - - - diff --git a/webapp/xss_trainer/levels/__init__.py b/webapp/xss_trainer/levels/__init__.py new file mode 100644 index 0000000..0dceaba --- /dev/null +++ b/webapp/xss_trainer/levels/__init__.py @@ -0,0 +1,3 @@ +""" +Allow levels to be imprted as a module +""" diff --git a/webapp/xss_trainer/levels/baseLevels.py b/webapp/xss_trainer/levels/baseLevels.py new file mode 100644 index 0000000..5a45b5e --- /dev/null +++ b/webapp/xss_trainer/levels/baseLevels.py @@ -0,0 +1,141 @@ +""" +A File to hold all of our Filters +""" + +import re +import html + +import flask +import markdown + +#import xss_trainer.app as app + +import xss_trainer.levels.meta as meta + + +class Training(meta.BaseLevel): + """ + Our initial Training Level + """ + levelname = "Training" + template = "intro.html" + +class NoFilter(meta.BaseLevel): + """ + Apply no filter to the input + """ + levelname = "No Filter" + template = "noFilter.html" + flag = "CUEH{Made_It_Past_Level_1}" + +class ClientFilter(meta.BaseLevel): + """ + In this level we protect by making sure the user submits an email + """ + levelname = "Client Side Filter" + template = "ClientSide.html" + +class SimpleReplace(meta.BaseLevel): + """ + Replace just ", "") + return payload + + +class BasicRegexp(meta.BaseLevel): + """ + Classic regexp replace python version + """ + levelname = "Basic Regexp" + template = "BasicRegexp.html" + + def sanitise(self, data): + regexp = re.compile("<\/?script>", re.IGNORECASE) + payload = regexp.sub("", data) + return payload + +class BasicPHPRegexp(meta.BaseLevel): + """ + Clasic PHP based simple preg_replace + """ + + levelname = "Basic preg_replace" + template = "BasicPreg.html" + + def sanitise(self, data): + + theStr = ['$input = "{0}"'.format(data), + '$output = preg_replace("/" - +
+ + + + +
+ + @@ -85,8 +90,10 @@ {% endif %} -

Your Current level is {{ session.level }} This page is level {{ level }}

- {% if session.level > level %} +

Your Current Max Level is {{ session.level }} This page is level {{ level+1 }} / {{maxlevel}}

+ {% if level+1 == maxlevel %} +
You have Reached the End
+ {% elif session.level > level %} Next Level {% else %} Complete Task To Get Next Level @@ -94,10 +101,13 @@ - + + + {% block scripts %} {% endblock scripts %} - + + diff --git a/webapp/xss_trainer/templates/levels/level5.html b/webapp/xss_trainer/templates/levels/BasicPreg.html similarity index 100% rename from webapp/xss_trainer/templates/levels/level5.html rename to webapp/xss_trainer/templates/levels/BasicPreg.html diff --git a/webapp/xss_trainer/templates/levels/level4.html b/webapp/xss_trainer/templates/levels/BasicRegexp.html similarity index 100% rename from webapp/xss_trainer/templates/levels/level4.html rename to webapp/xss_trainer/templates/levels/BasicRegexp.html diff --git a/webapp/xss_trainer/templates/levels/BootstrapTags.html b/webapp/xss_trainer/templates/levels/BootstrapTags.html new file mode 100644 index 0000000..c96da9c --- /dev/null +++ b/webapp/xss_trainer/templates/levels/BootstrapTags.html @@ -0,0 +1,70 @@ +{% extends "levelBase.html" %} + +{% block content %} + +

Level {{ level }}

+ +{% markdown %} + +This time we are going to use a decent filter on the input. +But there may be somewhere else we can inject the payload. + +??? hint + + Whats the framework here? + + Think about string escapes in python. + + + + +### Filter + +```python +def filter(data): + alertLevel = flask.request.args.get("style", "primary") + clean = html.escape(data) + + return clean, alertLevel +``` + +{% endmarkdown %} +{% endblock content%} + + +{% block defaultForm %} +
+
+

Vulnerable Form

+
+
+
+
+
+ + +
+
+ + +
+ +
+
+
+
+{% endblock defaultForm %} + + +{% block scripts %} + +{% endblock %} diff --git a/webapp/xss_trainer/templates/levels/level2.html b/webapp/xss_trainer/templates/levels/ClientSide.html similarity index 100% rename from webapp/xss_trainer/templates/levels/level2.html rename to webapp/xss_trainer/templates/levels/ClientSide.html diff --git a/webapp/xss_trainer/templates/levels/level7.html b/webapp/xss_trainer/templates/levels/MarkdownOutput.html similarity index 92% rename from webapp/xss_trainer/templates/levels/level7.html rename to webapp/xss_trainer/templates/levels/MarkdownOutput.html index 311ee2c..0e9e6d8 100644 --- a/webapp/xss_trainer/templates/levels/level7.html +++ b/webapp/xss_trainer/templates/levels/MarkdownOutput.html @@ -21,6 +21,8 @@ at the output format ```python def filter(data): clean = html.escape(data) + payload = markdown.markdown(clean, extensions=['legacy_attrs']) + return payload ``` {% endmarkdown %} diff --git a/webapp/xss_trainer/templates/levels/level6.html b/webapp/xss_trainer/templates/levels/ScriptTagFilter.html similarity index 100% rename from webapp/xss_trainer/templates/levels/level6.html rename to webapp/xss_trainer/templates/levels/ScriptTagFilter.html diff --git a/webapp/xss_trainer/templates/levels/level3.html b/webapp/xss_trainer/templates/levels/SimpleReplace.html similarity index 100% rename from webapp/xss_trainer/templates/levels/level3.html rename to webapp/xss_trainer/templates/levels/SimpleReplace.html diff --git a/webapp/xss_trainer/templates/levels/TagAttributes.html b/webapp/xss_trainer/templates/levels/TagAttributes.html new file mode 100644 index 0000000..11131dc --- /dev/null +++ b/webapp/xss_trainer/templates/levels/TagAttributes.html @@ -0,0 +1,55 @@ +{% extends "levelBase.html" %} + +{% block content %} + +

Level {{ level }}

+ +{% markdown %} + +This example creates a new tag on the page. + +??? hint + + We get free rein with the attributes here. + See what gets created and pop a shell + +### Filter + +```python +def filter(data): + attributes = flask.request.form.get("attributes", "") + clean = html.escape(data, quote=True) + +``` + + +{% endmarkdown %} +{% endblock content%} + +{# Overload the default form with a defaultForm block #} +{% block defaultForm %} +
+
+

Vulnerable Form

+
+
+
+
+
+ + +
+ +
+ + +
+ +
+
+
+
+{% endblock defaultForm %} + diff --git a/webapp/xss_trainer/templates/levels/levelTemplate.html b/webapp/xss_trainer/templates/levels/levelTemplate.html index 3b51d39..dbb2a7b 100644 --- a/webapp/xss_trainer/templates/levels/levelTemplate.html +++ b/webapp/xss_trainer/templates/levels/levelTemplate.html @@ -15,6 +15,10 @@ def filter(data): return "Whatever" ``` +??? hint + + Some Kind of Hint + {% endmarkdown %} {% endblock content%} diff --git a/webapp/xss_trainer/templates/levels/level1.html b/webapp/xss_trainer/templates/levels/noFilter.html similarity index 100% rename from webapp/xss_trainer/templates/levels/level1.html rename to webapp/xss_trainer/templates/levels/noFilter.html diff --git a/webapp/xss_trainer/templates/nav.html b/webapp/xss_trainer/templates/nav.html index 110176e..c88baf0 100644 --- a/webapp/xss_trainer/templates/nav.html +++ b/webapp/xss_trainer/templates/nav.html @@ -1,31 +1,3 @@ -{# -#} -