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
__pycache__
/static/*
/secrets

29
app.py
View File

@ -8,6 +8,7 @@ from peewee import fn
import config
import utils
import redis
import requests
import logging
logging.basicConfig(level=logging.DEBUG)
@ -37,10 +38,13 @@ def root():
@app.route('/scoreboard/')
def scoreboard():
data = utils.get_complex("scoreboard")
if not data:
graphdata = utils.get_complex("graph")
if not data or not graphdata:
data = utils.calculate_scores()
utils.set_complex("scoreboard", data, 10)
return render_template("scoreboard.html", data=data)
graphdata = utils.calculate_graph(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"])
def login():
@ -64,12 +68,24 @@ def register():
if request.method == "GET":
return render_template("register.html")
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_email = request.form["team_email"]
team_elig = "team_eligibility" in request.form
affiliation = request.form["affiliation"]
team_key = utils.generate_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
flash("Team created.")
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_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])
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":
team_name = request.form["team_name"]
team_email = request.form["team_email"]

View File

@ -1,6 +1,15 @@
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."
tagline = "a cybersecurity competition created by TJHSST students"
competition_is_running = True
cdn = True
proxied_ip_header = "X-Forwarded-For"
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
from database import *
from datetime import datetime, timedelta
import sys
import yaml
import random
@ -26,7 +27,22 @@ elif operation == "gen-challenge":
n = int(sys.argv[2])
for i in range(n):
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))
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

View File

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

View File

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

View File

@ -36,10 +36,10 @@
<div class="col s12">
<div class="card blue darken-1">
<div class="card-content white-text">
<span class="card-title">Login information</span>
<p>Your team key is <code>{{ team.key }}</code>. Share this with your teammates,
and keep it in a safe place. <strong>You need your team key in order to log in.
</strong>If you lose it, an organizer can send it to your team email, which is shown below.</p>
<span class="card-title">Team key: <code>{{ team.key }}</code></span>
<p>Share this with your teammates, and keep it in a safe place. <strong>You need your team key
in order to log in.</strong> If you lose it, an organizer can send it to your team email,
which is shown below.</p>
</div>
</div>
</div>
@ -127,4 +127,24 @@
{% endif %}
<p class="right"><strong class="bigger">Your score is {{ team_score }}.</strong></p>
</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 %}

View File

@ -1,12 +1,31 @@
{% extends "base.html" %}
{% block head %}
<style>
body {
background-color: #1565C0
}
nav {
box-shadow: none;
}
</style>
{% endblock %}
{% block content %}
<h2>Login</h2>
<form method="POST">
<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">
<div class="input-field">
<input id="team-key" name="team_key" type="text" />
<label for="team-key">Team Key</label>
<input name="_csrf_token" type="hidden" value="{{ csrf_token() }}" />
</div>
<button class="btn waves-effect waves-light" type="submit">Login</button>
</form>
</form>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,4 +1,7 @@
{% extends "base.html" %}
{% block head %}
<script src='https://www.google.com/recaptcha/api.js'></script>
{% endblock %}
{% block content %}
<h2>Register a Team</h2>
<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>
<input id="team-eligibility" name="team_eligibility" type="checkbox" />
<label for="team-eligibility">Eligibility Certification</label>
<input name="_csrf_token" type="hidden" value="{{ csrf_token() }}" />
<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>
</form>
{% endblock %}

View File

@ -18,10 +18,12 @@
}
function hideIneligible() {
$(".teamrow.ineligible").removeClass("visible");
$("#toggle").html("Show ineligible teams");
recalcRanks();
}
function showIneligible() {
$(".teamrow.ineligible").addClass("visible");
$("#toggle").html("Hide ineligible teams");
recalcRanks();
}
function toggleIneligible() {
@ -36,15 +38,35 @@
{% endblock %}
{% block content %}
<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>
<thead>
<tr><th>Rank</th><th>Team</th><th>Affiliation</th><th>Score</th></tr>
</thead>
<tbody>
{% 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 %}
</tbody>
</table>
{% 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 config
import json
from datetime import datetime
from functools import wraps
from flask import request, session, redirect, url_for, flash, g
from database import Team, Challenge, ChallengeSolve, ScoreAdjustment
@ -37,17 +38,38 @@ def competition_running_required(f):
def calculate_scores():
solves = ChallengeSolve.select(ChallengeSolve, Challenge).join(Challenge)
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}
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:
scores[solve.team_id] += solve.challenge.points
team_solves[solve.team_id].append(solve)
for adjustment in adjustments:
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]])))]
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):
i = g.redis.get(key)
if i is None: