lots of things!

master
Fox Wilson 2015-11-08 14:04:02 -05:00
parent 3e2590090d
commit d93bc0d438
11 changed files with 166 additions and 25 deletions

1
.gitignore vendored
View File

@ -5,3 +5,4 @@ dump.rdb
/problems /problems
__pycache__ __pycache__
/static/* /static/*
/secrets

29
app.py
View File

@ -8,6 +8,7 @@ from peewee import fn
import config import config
import utils import utils
import redis import redis
import requests
import logging import logging
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
@ -37,10 +38,13 @@ def root():
@app.route('/scoreboard/') @app.route('/scoreboard/')
def scoreboard(): def scoreboard():
data = utils.get_complex("scoreboard") data = utils.get_complex("scoreboard")
if not data: graphdata = utils.get_complex("graph")
if not data or not graphdata:
data = utils.calculate_scores() data = utils.calculate_scores()
utils.set_complex("scoreboard", data, 10) graphdata = utils.calculate_graph(data)
return render_template("scoreboard.html", data=data) utils.set_complex("scoreboard", data, 120)
utils.set_complex("graph", graphdata, 120)
return render_template("scoreboard.html", data=data, graphdata=graphdata)
@app.route('/login/', methods=["GET", "POST"]) @app.route('/login/', methods=["GET", "POST"])
def login(): def login():
@ -64,12 +68,24 @@ def register():
if request.method == "GET": if request.method == "GET":
return render_template("register.html") return render_template("register.html")
elif request.method == "POST": elif request.method == "POST":
if "g-recaptcha-response" not in request.form:
flash("Please complete the CAPTCHA.")
return render_template("register.html")
captcha_response = request.form["g-recaptcha-response"]
verify_data = dict(secret=config.secret.recaptcha_secret, response=captcha_response, remoteip=utils.get_ip())
result = requests.post("https://www.google.com/recaptcha/api/siteverify", verify_data).json()["success"]
if not result:
flash("Invalid CAPTCHA response.")
return render_template("register.html")
team_name = request.form["team_name"] team_name = request.form["team_name"]
team_email = request.form["team_email"] team_email = request.form["team_email"]
team_elig = "team_eligibility" in request.form team_elig = "team_eligibility" in request.form
affiliation = request.form["affiliation"] affiliation = request.form["affiliation"]
team_key = utils.generate_team_key() team_key = utils.generate_team_key()
team = Team.create(name=team_name, email=team_email, eligible=team_elig, affiliation=affiliation, key=team_key) team = Team.create(name=team_name, email=team_email, eligible=team_elig, affiliation=affiliation, key=team_key)
TeamAccess.create(team=team, ip=utils.get_ip(), time=datetime.now())
session["team_id"] = team.id session["team_id"] = team.id
flash("Team created.") flash("Team created.")
return redirect(url_for('dashboard')) return redirect(url_for('dashboard'))
@ -98,7 +114,12 @@ def dashboard():
team_solves = ChallengeSolve.select(ChallengeSolve, Challenge).join(Challenge).where(ChallengeSolve.team == g.team) team_solves = ChallengeSolve.select(ChallengeSolve, Challenge).join(Challenge).where(ChallengeSolve.team == g.team)
team_adjustments = ScoreAdjustment.select().where(ScoreAdjustment.team == g.team) team_adjustments = ScoreAdjustment.select().where(ScoreAdjustment.team == g.team)
team_score = sum([i.challenge.points for i in team_solves] + [i.value for i in team_adjustments]) team_score = sum([i.challenge.points for i in team_solves] + [i.value for i in team_adjustments])
return render_template("dashboard.html", team_solves=team_solves, team_adjustments=team_adjustments, team_score=team_score) first_login = False
if g.team.first_login:
first_login = True
g.team.first_login = False
g.team.save()
return render_template("dashboard.html", team_solves=team_solves, team_adjustments=team_adjustments, team_score=team_score, first_login=first_login)
elif request.method == "POST": elif request.method == "POST":
team_name = request.form["team_name"] team_name = request.form["team_name"]
team_email = request.form["team_email"] team_email = request.form["team_email"]

View File

@ -1,6 +1,15 @@
ctf_name = "TJCTF" ctf_name = "TJCTF"
eligibility = "In order to be eligible for prizes, all members of your team must be in high school, and you must not have more than four team members." eligibility = "In order to be eligible for prizes, all members of your team must be in high school, and you must not have more than four team members."
tagline = "a cybersecurity competition created by TJHSST students"
competition_is_running = True competition_is_running = True
cdn = True cdn = True
proxied_ip_header = "X-Forwarded-For" proxied_ip_header = "X-Forwarded-For"
flag_rl = 10 flag_rl = 10
teams_on_graph = 10
# Don't touch these. Instead, copy secrets.example to secrets and edit that.
import yaml
from collections import namedtuple
with open("secrets") as f:
_secret = yaml.load(f)
secret = namedtuple('SecretsDict', _secret.keys())(**_secret)

18
ctftool
View File

@ -1,5 +1,6 @@
#!/usr/bin/python3 #!/usr/bin/python3
from database import * from database import *
from datetime import datetime, timedelta
import sys import sys
import yaml import yaml
import random import random
@ -26,7 +27,22 @@ elif operation == "gen-challenge":
n = int(sys.argv[2]) n = int(sys.argv[2])
for i in range(n): for i in range(n):
name = str(random.randint(0, 999999999)) name = str(random.randint(0, 999999999))
chal = Challenge.create(name="Challenge {}".format(name), category="Generated", description="Free flags. it's the number in the problem name lorem ipsum dolor sit whatever i don't even care", points=random.randint(50, 400), flag=name) chal = Challenge.create(name="Challenge".format(name), category="Generated", description="Lorem ipsum, dolor sit amet. The flag is {}".format(name), points=random.randint(50, 400), flag=name)
print("Challenge added with id {}".format(chal.id)) print("Challenge added with id {}".format(chal.id))
elif operation == "gen-team":
n = int(sys.argv[2])
chals = list(Challenge.select())
ctz = datetime.now()
diff = timedelta(minutes=5)
for i in range(n):
name = "Team {}".format(i + 1)
t = Team.create(name=name, email="none@none.com", affiliation="Autogenerated", eligible=True, key="")
t.key = "autogen{}".format(t.id)
t.save()
print("Team added with id {}".format(t.id))
for i in random.sample(chals, 5):
ChallengeSolve.create(team=t, challenge=i, time=ctz)
ctz = ctz + (diff * random.randint(-10, 10))
# vim: syntax=python:ft=python # vim: syntax=python:ft=python

View File

@ -10,6 +10,7 @@ class Team(BaseModel):
email = CharField() email = CharField()
affiliation = CharField() affiliation = CharField()
eligible = BooleanField() eligible = BooleanField()
first_login = BooleanField(default=True)
key = CharField() key = CharField()
def solved(self, challenge): def solved(self, challenge):

View File

@ -3,6 +3,7 @@
<title>{{ config.ctf_name }} :: {% block title %}Home{% endblock %}</title> <title>{{ config.ctf_name }} :: {% block title %}Home{% endblock %}</title>
{% if config.cdn %} {% if config.cdn %}
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.1/css/materialize.min.css" /> <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.1/css/materialize.min.css" />
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/vis/4.9.0/vis.min.css" />
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" /> <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
{% else %} {% else %}
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='materialize.min.css') }}" /> <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='materialize.min.css') }}" />
@ -16,8 +17,10 @@
<div class="container"> <div class="container">
<div class="nav-wrapper"> <div class="nav-wrapper">
<ul class="left"> <ul class="left">
{% if config.competition_is_running and logged_in %} {% if config.competition_is_running %}
{% if logged_in %}
<li><a href="{{ url_for('challenges') }}">Challenges</a></li> <li><a href="{{ url_for('challenges') }}">Challenges</a></li>
{% endif %}
<li><a href="{{ url_for('scoreboard') }}">Scoreboard</a></li> <li><a href="{{ url_for('scoreboard') }}">Scoreboard</a></li>
{% endif %} {% endif %}
</ul> </ul>
@ -43,6 +46,7 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.1/js/materialize.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.1/js/materialize.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-timeago/1.4.3/jquery.timeago.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-timeago/1.4.3/jquery.timeago.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vis/4.9.0/vis.min.js"></script>
<script>$("abbr.time").timeago();</script> <script>$("abbr.time").timeago();</script>
{% else %} {% else %}
<script src="{{ url_for('static', filename='jquery.min.js') }}"></script> <script src="{{ url_for('static', filename='jquery.min.js') }}"></script>
@ -53,5 +57,7 @@
Materialize.toast({{ message | tojson }}, 4000); Materialize.toast({{ message | tojson }}, 4000);
{% endfor %} {% endfor %}
</script> </script>
{% block postscript %}
{% endblock %}
</body> </body>
</html> </html>

View File

@ -36,10 +36,10 @@
<div class="col s12"> <div class="col s12">
<div class="card blue darken-1"> <div class="card blue darken-1">
<div class="card-content white-text"> <div class="card-content white-text">
<span class="card-title">Login information</span> <span class="card-title">Team key: <code>{{ team.key }}</code></span>
<p>Your team key is <code>{{ team.key }}</code>. Share this with your teammates, <p>Share this with your teammates, and keep it in a safe place. <strong>You need your team key
and keep it in a safe place. <strong>You need your team key in order to log in. in order to log in.</strong> If you lose it, an organizer can send it to your team email,
</strong>If you lose it, an organizer can send it to your team email, which is shown below.</p> which is shown below.</p>
</div> </div>
</div> </div>
</div> </div>
@ -127,4 +127,24 @@
{% endif %} {% endif %}
<p class="right"><strong class="bigger">Your score is {{ team_score }}.</strong></p> <p class="right"><strong class="bigger">Your score is {{ team_score }}.</strong></p>
</section> </section>
<div id="firstlogin" class="modal">
<div class="modal-content">
<h4>Welcome to {{ config.ctf_name }}!</h4>
<p>Hi, {{ team.name }}! Welcome to {{ config.ctf_name }}, {{ config.tagline }}.</p>
<p>First off, please check the email you signed up with. You must confirm your account to solve problems. If needed, you can change the Team Email or resend confirmation on this page.</p>
<p>Your Team Key will be under Login Information. It was also sent to your team email. This is the only way to log in, so distribute it to your team members.</p>
<p>Once that's done, we recommend you read the Rules and FAQ. Then, do some Practice to warm up for the competition, or join the Chat to meet your fellow competitors and ask questions. The competition will begin soon enough, but in the meanwhile, explore the site!</p>
</div>
<div class="modal-footer">
<a href="#!" class="modal-action modal-close waves-effect waves-green btn-flat">Dismiss</a>
</div>
</div>
{% endblock %}
{% block postscript %}
{% if first_login %}
<script>$("#firstlogin").openModal();</script>
{% endif %}
{% endblock %} {% endblock %}

View File

@ -1,6 +1,20 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block head %}
<style>
body {
background-color: #1565C0
}
nav {
box-shadow: none;
}
</style>
{% endblock %}
{% block content %} {% block content %}
<h2>Login</h2> <div class="row" style="margin-top: 30px;">
<div class="col s8 offset-s2">
<div class="card gray">
<div class="card-content center">
<span class="card-title black-text">Login</span>
<form method="POST"> <form method="POST">
<div class="input-field"> <div class="input-field">
<input id="team-key" name="team_key" type="text" /> <input id="team-key" name="team_key" type="text" />
@ -9,4 +23,9 @@
</div> </div>
<button class="btn waves-effect waves-light" type="submit">Login</button> <button class="btn waves-effect waves-light" type="submit">Login</button>
</form> </form>
</div>
</div>
</div>
</div>
</div>
{% endblock %} {% endblock %}

View File

@ -1,4 +1,7 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block head %}
<script src='https://www.google.com/recaptcha/api.js'></script>
{% endblock %}
{% block content %} {% block content %}
<h2>Register a Team</h2> <h2>Register a Team</h2>
<p>After registering, you will be directed to your team's dashboard. This will <p>After registering, you will be directed to your team's dashboard. This will
@ -26,8 +29,9 @@ team members.</strong></p>
status before sending you prizes.</p> status before sending you prizes.</p>
<input id="team-eligibility" name="team_eligibility" type="checkbox" /> <input id="team-eligibility" name="team_eligibility" type="checkbox" />
<label for="team-eligibility">Eligibility Certification</label> <label for="team-eligibility">Eligibility Certification</label>
<input name="_csrf_token" type="hidden" value="{{ csrf_token() }}" />
<br /><br /> <br /><br />
<div class="g-recaptcha" data-sitekey="{{ config.secret.recaptcha_key }}"></div>
<input name="_csrf_token" type="hidden" value="{{ csrf_token() }}" />
<button class="btn waves-effect waves-light" type="submit">Create team</button> <button class="btn waves-effect waves-light" type="submit">Create team</button>
</form> </form>
{% endblock %} {% endblock %}

View File

@ -18,10 +18,12 @@
} }
function hideIneligible() { function hideIneligible() {
$(".teamrow.ineligible").removeClass("visible"); $(".teamrow.ineligible").removeClass("visible");
$("#toggle").html("Show ineligible teams");
recalcRanks(); recalcRanks();
} }
function showIneligible() { function showIneligible() {
$(".teamrow.ineligible").addClass("visible"); $(".teamrow.ineligible").addClass("visible");
$("#toggle").html("Hide ineligible teams");
recalcRanks(); recalcRanks();
} }
function toggleIneligible() { function toggleIneligible() {
@ -36,15 +38,35 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<h2>Scoreboard</h2> <h2>Scoreboard</h2>
<a href="javascript:toggleIneligible()">Toggle ineligible team display</a> <div id="chart"></div>
<a href="javascript:toggleIneligible()" id="toggle">Hide ineligible teams</a>
<table> <table>
<thead> <thead>
<tr><th>Rank</th><th>Team</th><th>Affiliation</th><th>Score</th></tr> <tr><th>Rank</th><th>Team</th><th>Affiliation</th><th>Score</th></tr>
</thead> </thead>
<tbody> <tbody>
{% for eligible, teamid, team, affiliation, score in data %} {% for eligible, teamid, team, affiliation, score in data %}
<tr class="teamrow {% if not eligible %}in{% endif %}eligible {% if teamid == session.team_id %}blue lighten-3 {% endif %}visible"><td class="rank">{{ rank }}</td><td>{{ team }}</td><td>{{ affiliation }}</td><td>{{ score }}</td></tr> <tr class="teamrow {% if not eligible %}in{% endif %}eligible {% if eligible and teamid == session.team_id %}blue lighten-3 {% endif %}visible"><td class="rank">{{ rank }}</td><td>{{ team }}</td><td>{{ affiliation }}</td><td>{{ score }}</td></tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
{% endblock %} {% endblock %}
{% block postscript %}
<script>
var graphData = {{ graphdata | tojson }};
var groupn = 0;
var groups = new vis.DataSet();
var items = [];
graphData.forEach(function(team) {
var teamName = team[0];
var scoreEvents = team[1];
groups.add({ id: groupn, content: teamName, options: { drawPoints: false, interpolation: false } });
scoreEvents.forEach(function(evt) {
items.push({ x: evt[0], y: evt[1], group: groupn });
});
groupn++;
});
var dataset = new vis.DataSet(items);
var graph = new vis.Graph2d(document.getElementById('chart'), dataset, groups, {legend: true, dataAxis: {left: {range: {min: 0}}}});
</script>
{% endblock %}

View File

@ -1,6 +1,7 @@
import random import random
import config import config
import json import json
from datetime import datetime
from functools import wraps from functools import wraps
from flask import request, session, redirect, url_for, flash, g from flask import request, session, redirect, url_for, flash, g
from database import Team, Challenge, ChallengeSolve, ScoreAdjustment from database import Team, Challenge, ChallengeSolve, ScoreAdjustment
@ -37,17 +38,38 @@ def competition_running_required(f):
def calculate_scores(): def calculate_scores():
solves = ChallengeSolve.select(ChallengeSolve, Challenge).join(Challenge) solves = ChallengeSolve.select(ChallengeSolve, Challenge).join(Challenge)
adjustments = ScoreAdjustment.select() adjustments = ScoreAdjustment.select()
teams = Team.select(Team.id, Team, ChallengeSolve).join(ChallengeSolve) teams = Team.select()
team_solves = {team.id: [] for team in teams}
team_mapping = {team.id: team for team in teams} team_mapping = {team.id: team for team in teams}
scores = {team.id: 0 for team in teams} scores = {team.id: 0 for team in teams}
most_recent_solve = {team.id: max([i.time for i in team.solves]) for team in teams}
for solve in solves: for solve in solves:
scores[solve.team_id] += solve.challenge.points scores[solve.team_id] += solve.challenge.points
team_solves[solve.team_id].append(solve)
for adjustment in adjustments: for adjustment in adjustments:
scores[adjustment.team_id] += adjustment.value scores[adjustment.team_id] += adjustment.value
most_recent_solve = {tid: max([i.time for i in team_solves[tid]]) for tid in team_solves if team_solves[tid]}
scores = {i: j for i, j in scores.items() if i in most_recent_solve}
return [(team_mapping[i[0]].eligible, i[0], team_mapping[i[0]].name, team_mapping[i[0]].affiliation, i[1]) for idx, i in enumerate(sorted(scores.items(), key=lambda k: (-k[1], most_recent_solve[k[0]])))] return [(team_mapping[i[0]].eligible, i[0], team_mapping[i[0]].name, team_mapping[i[0]].affiliation, i[1]) for idx, i in enumerate(sorted(scores.items(), key=lambda k: (-k[1], most_recent_solve[k[0]])))]
def calculate_graph(scoredata):
solves = list(ChallengeSolve.select(ChallengeSolve, Challenge).join(Challenge))
adjustments = list(ScoreAdjustment.select())
scoredata = [i for i in scoredata if i[0]] # Only eligible teams are on the score graph
graph_data = []
for eligible, tid, name, affiliation, score in scoredata[:config.teams_on_graph]:
our_solves = [i for i in solves if i.team_id == tid]
team_data = []
s = sum([i.value for i in adjustments if i.team_id == tid])
for i in sorted(our_solves, key=lambda k: k.time):
team_data.append((str(i.time), s))
s += i.challenge.points
team_data.append((str(i.time), s))
team_data.append((str(datetime.now()), score))
graph_data.append((name, team_data))
return graph_data
def get_complex(key): def get_complex(key):
i = g.redis.get(key) i = g.redis.get(key)
if i is None: if i is None: