Skip to content

Commit

Permalink
V3.3 (#308)
Browse files Browse the repository at this point in the history
* Scoreboard Performance #296

* #259 Added to Stats and Game Objects page

* Recaptcha #233 fix

* Remove deactivated key

* #301 Completed CTFtime feed

* Import defaults, increase category length

* __str__ should return str

* XML Import configuration

* Default password warning

* flake8 cleanup

* Readme - Juice Shop CTF
  • Loading branch information
eljeffeg authored Nov 23, 2019
1 parent 8368a63 commit 1f96a9b
Show file tree
Hide file tree
Showing 46 changed files with 1,375 additions and 296 deletions.
8 changes: 8 additions & 0 deletions .flake8
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[flake8]
ignore = E203, E266, E501, W503, F403, F401, F405
max-line-length = 88
max-complexity = 18
select = B,C,E,F,W,T4,B9
per-file-ignores =
setup/create_database.py:E402
models/__init__.py:E402
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ Additional platform [screenshots](https://github.com/moloch--/RootTheBox/wiki/Sc
- Options for Penalties, Hints, Attempts, Level Bonuses, Dynamic Scoring, Categories and more
- Built-in team based file/text sharing and Admin game material distirbution
- Chat support with [Rocket Chat](https://rocket.chat/) integration
- [CTF Time](https://ctftime.org/) compatible JSON scoreboard feed
- Supports [OWASP Juice Shop CTF](https://github.com/bkimminich/juice-shop-ctf) export
- Freeze scoreboard at a specific time allowing for end game countdown
- Optional Story Mode - Supports intro dialog, capture Flag or Section dialog w/graphics
- Optional [in-game Botnets](https://github.com/moloch--/RootTheBox/wiki/Features) or wall of sheep displaying cracked passwords
Expand Down
34 changes: 34 additions & 0 deletions alembic/versions/71aa3c94b7f5_juiceshop_updates.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""JuiceShop Updates
Revision ID: 71aa3c94b7f5
Revises: 18d11f218dfe
Create Date: 2019-11-14 08:52:52.530520
"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = "71aa3c94b7f5"
down_revision = "18d11f218dfe"
branch_labels = None
depends_on = None


def upgrade():
op.alter_column(
"category",
"_category",
existing_type=sa.VARCHAR(length=24),
type_=sa.VARCHAR(length=64),
)


def downgrade():
op.alter_column(
"category",
"_category",
existing_type=sa.VARCHAR(length=64),
type_=sa.VARCHAR(length=24),
)
2 changes: 1 addition & 1 deletion handlers/AdminHandlers/AdminGameHandlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ def add_source_code(self):
box = Box.by_uuid(self.get_argument("box_uuid", ""))
if box is not None:
file_count = len(self.request.files["source_archive"])
if not "source_archive" in self.request.files and 0 < file_count:
if "source_archive" not in self.request.files and 0 < file_count:
raise ValidationError("No file data")
else:
price = self.get_argument("price", "")
Expand Down
169 changes: 154 additions & 15 deletions handlers/AdminHandlers/AdminGameObjectHandlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import re
import json

from past.utils import old_div
from handlers.BaseHandlers import BaseHandler
from models.Box import Box, FlagsSubmissionType
from models.Corporation import Corporation
Expand Down Expand Up @@ -179,7 +180,15 @@ def create_box(self):
if Box.by_name(self.get_argument("name", "")) is not None:
raise ValidationError("Box name already exists")
elif Corporation.by_uuid(corp_uuid) is None:
raise ValidationError("Corporation does not exist")
if len(Corporation.all()) == 0:
# Create a empty Corp
corporation = Corporation()
corporation.name = ""
self.dbsession.add(corporation)
self.dbsession.commit()
corp_uuid = corporation.uuid
else:
raise ValidationError("Corporation does not exist")
elif GameLevel.by_number(game_level) is None:
raise ValidationError("Game level does not exist")
else:
Expand Down Expand Up @@ -309,7 +318,7 @@ def _mkflag(self, flag_type, is_file=False):
if box is None:
raise ValidationError("Box does not exist")
if is_file:
if not hasattr(self.request, "files") or not "flag" in self.request.files:
if not hasattr(self.request, "files") or "flag" not in self.request.files:
raise ValidationError("No file in request")
token = self.request.files["flag"][0]["body"]
else:
Expand Down Expand Up @@ -343,7 +352,7 @@ def _mkflag(self, flag_type, is_file=False):
def add_attachments(self, flag):
""" Add uploaded files as attachments to flags """
if hasattr(self.request, "files"):
if not "attachments" in self.request.files:
if "attachments" not in self.request.files:
return
for attachment in self.request.files["attachments"]:
flag_attachment = FlagAttachment(file_name=attachment["name"])
Expand Down Expand Up @@ -373,10 +382,89 @@ def get(self, *args, **kwargs):
"notifications": "admin/view/notifications.html",
}
if len(args) and args[0] in uri:
self.render(uri[args[0]], errors=None)
self.render(uri[args[0]], errors=None, success=None)
else:
self.render("public/404.html")

""" Restore points or update tokens from an failed attempt """

@restrict_ip_address
@authenticated
@authorized(ADMIN_PERMISSION)
def post(self, *args, **kwargs):
if args[0] == "statistics" or args[0] == "game_objects":
uri = {
"game_objects": "admin/view/game_objects.html",
"statistics": "admin/view/statistics.html",
}
flag_uuid = self.get_argument("flag_uuid", "")
team_uuid = self.get_argument("team_uuid", "")
flag = Flag.by_uuid(flag_uuid)
team = Team.by_uuid(team_uuid)
errors = []
success = []
if flag:
point_restore = self.get_argument("point_restore", "")
accept_answer = self.get_argument("accept_answer", "")
answer_token = self.get_argument("answer_token", "")
if point_restore == "on" and options.penalize_flag_value and team:
value = int(flag.value * (options.flag_penalty_cost * 0.01))
team.money += value
penalty = Penalty.by_team_token(flag, team, answer_token)
if penalty:
self.dbsession.delete(penalty)
self.dbsession.add(team)
self.event_manager.admin_score_update(
team,
"%s penalty reversed - score has been updated." % team.name,
value,
)
if flag not in team.flags:
team.flags.append(flag)
team.money += flag.value
self.dbsession.add(team)
if self.config.dynamic_flag_value:
depreciation = float(
old_div(self.config.flag_value_decrease, 100.0)
)
flag.value = int(flag.value - (flag.value * depreciation))
self.dbsession.add(flag)
self.event_manager.flag_captured(team, flag)
self._check_level(flag, team)
success.append("%s awarded %d" % (team.name, value))
if (
accept_answer == "on"
and (flag.type == "static" or flag.type == "regex")
and not flag.capture(answer_token)
):
flag.type = "regex"
if flag.token.startswith("(") and flag.token.endwith(")"):
token = "%s|(%s)" % (flag.token, answer_token)
else:
token = "(%s)|(%s)" % (flag.token, answer_token)
if len(token) < 256:
flag.token = token
self.dbsession.add(flag)
success.append(
"Token succesfully added for Flag %s" % flag.name
)
else:
errors.append("Flag token too long. Can not expand token.")
self.dbsession.commit()
self.render(uri[args[0]], errors=errors, success=success)
else:
self.render("public/404.html")

def _check_level(self, flag, team):
if len(team.level_flags(flag.game_level.number)) == len(flag.game_level.flags):
next_level = next(flag.game_level)
if next_level is not None:
logging.info("Next level is %r" % next_level)
if next_level not in team.game_levels:
logging.info("Team completed level, unlocking the next level")
team.game_levels.append(next_level)
self.dbsession.add(team)


class AdminEditHandler(BaseHandler):

Expand Down Expand Up @@ -440,7 +528,9 @@ def edit_corporations(self):
self.dbsession.commit()
self.redirect("/admin/view/game_objects")
except ValidationError as error:
self.render("admin/view/game_objects.html", errors=[str(error)])
self.render(
"admin/view/game_objects.html", success=None, errors=[str(error)]
)

def edit_category(self):
""" Updates category object in the database """
Expand Down Expand Up @@ -561,7 +651,9 @@ def edit_boxes(self):
self.dbsession.commit()
self.redirect("/admin/view/game_objects#%s" % box.uuid)
except ValidationError as error:
self.render("admin/view/game_objects.html", errors=[str(error)])
self.render(
"admin/view/game_objects.html", success=None, errors=[str(error)]
)

def edit_flag_order(self):
""" Edit flag order in the database """
Expand Down Expand Up @@ -636,7 +728,9 @@ def edit_flags(self):
self.edit_choices(flag, self.request.arguments)
self.redirect("/admin/view/game_objects#%s" % box.uuid)
except ValidationError as error:
self.render("admin/view/game_objects.html", errors=["%s" % error])
self.render(
"admin/view/game_objects.html", success=None, errors=["%s" % error]
)

def edit_choices(self, flag, arguments):
""" Edit flag multiple choice items """
Expand Down Expand Up @@ -685,7 +779,9 @@ def edit_ip(self):
else:
raise ValidationError("IP address is already in use")
except ValidationError as error:
self.render("admin/view/game_objects.html", errors=[str(error)])
self.render(
"admin/view/game_objects.html", success=None, errors=[str(error)]
)

def edit_game_level(self):
""" Update game level objects """
Expand Down Expand Up @@ -763,7 +859,9 @@ def edit_hint(self):
self.dbsession.commit()
self.redirect("/admin/view/game_objects#%s" % box.uuid)
except ValidationError as error:
self.render("admin/view/game_objects.html", errors=[str(error)])
self.render(
"admin/view/game_objects.html", success=None, errors=[str(error)]
)

def edit_market_item(self):
""" Change a market item's price """
Expand Down Expand Up @@ -818,7 +916,9 @@ def del_ip(self):
% (self.get_argument("ip_uuid", ""),)
)
self.render(
"admin/view/game_objects.html", errors=["IP does not exist in database"]
"admin/view/game_objects.html",
success=None,
errors=["IP does not exist in database"],
)

def del_flag(self):
Expand All @@ -836,6 +936,7 @@ def del_flag(self):
)
self.render(
"admin/view/game_objects.html",
success=None,
errors=["Flag does not exist in database."],
)

Expand All @@ -850,6 +951,7 @@ def del_hint(self):
else:
self.render(
"admin/view/game_objects.html",
success=None,
errors=["Hint does not exist in database."],
)

Expand All @@ -864,6 +966,7 @@ def del_corp(self):
else:
self.render(
"admin/view/game_objects.html",
success=None,
errors=["Corporation does not exist in database."],
)

Expand Down Expand Up @@ -892,6 +995,7 @@ def del_box(self):
else:
self.render(
"admin/view/game_objects.html",
success=None,
errors=["Box does not exist in database."],
)

Expand Down Expand Up @@ -949,6 +1053,7 @@ def post(self, *args, **kwargs):
flaginfo = [
{
"name": flag.name,
"description": flag.description,
"token": flag.token,
"price": "$" + str(flag.value),
}
Expand All @@ -966,11 +1071,7 @@ def post(self, *args, **kwargs):
team = Team.by_id(item[0])
if team:
captures.append({"name": team.name})
attempts = []
for item in Penalty.by_flag_id(flag.id):
team = Team.by_id(item.team_id)
if team:
attempts.append({"name": team.name, "token": item.token})
attempts = self.attempts(flag)
hints = []
for item in Hint.taken_by_flag(flag.id):
team = Team.by_id(item.team_id)
Expand Down Expand Up @@ -1000,6 +1101,44 @@ def post(self, *args, **kwargs):
self.write({"Error": "Invalid object type."})
self.finish()

def attempts(self, flag):
attempts = []
teamcount = {}
for item in Penalty.by_flag_id(flag.id):
team = Team.by_id(item.team_id)
if team.id in teamcount:
teamcount.update({team.id: teamcount[team.id] + 1})
else:
teamcount.update({team.id: 1})
if team:
if team.id in teamcount:
teamcount.update({team.id: teamcount[team.id] + 1})
else:
teamcount.update({team.id: 1})
entries = {
"name": team.name,
"token": item.token,
"flag": flag.uuid,
"team": team.uuid,
"type": flag.type,
}
if options.penalize_flag_value:
penalty = "-"
if options.banking:
penalty += "$"
if (
teamcount[team.id] < options.flag_start_penalty
or teamcount[team.id] > options.flag_stop_penalty
):
penalty += "0"
else:
penalty += str(
int(flag.value * (options.flag_penalty_cost * 0.01))
)
entries.update({"penalty": penalty})
attempts.append(entries)
return attempts


class AdminTestTokenHandler(BaseHandler):

Expand Down
3 changes: 2 additions & 1 deletion handlers/BaseHandlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
import logging
import memcache
import traceback
import datetime, time
import datetime
import time

from models import dbsession, chatsession
from models.User import User
Expand Down
2 changes: 1 addition & 1 deletion handlers/MarketHandlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def post(self, *args, **kwargs):
""" Called to purchase an item """
uuid = self.get_argument("uuid", "")
item = MarketItem.by_uuid(uuid)
if not item is None:
if item is not None:
user = self.get_current_user()
team = Team.by_id(user.team.id) # Refresh object
if user.has_item(item.name):
Expand Down
Loading

0 comments on commit 1f96a9b

Please sign in to comment.