diff --git a/Makefile b/Makefile index 084fd70..1957826 100644 --- a/Makefile +++ b/Makefile @@ -22,13 +22,16 @@ target: $(PYC) $(INSTALL) ctfd.py $(CTFDIR) $(INSTALL) -d $(WWWDIR) - $(INSTALL) index.html intro.html ctf.css $(WWWDIR) + $(INSTALL) index.html intro.html ctf.css grunge.png $(WWWDIR) $(FAKE) ln -s /var/lib/ctf/histogram.png $(WWWDIR) $(INSTALL) register.cgi scoreboard.cgi puzzler.cgi $(WWWDIR) $(INSTALL) -d $(DESTDIR)/var/service/ctf $(INSTALL) run.ctfd $(DESTDIR)/var/service/ctf/run + $(INSTALL) -d $(DESTDIR)/var/service/ctf/log + $(INSTALL) run.log.ctfd $(DESTDIR)/var/service/ctf/log/run + rm -rf $(WWWDIR)/puzzler $(INSTALL) -d $(WWWDIR)/puzzler ./mkpuzzles.py --htmldir=$(WWWDIR)/puzzler --keyfile=$(CTFDIR)/puzzler.keys diff --git a/config.py b/config.py index e7bbeda..3e6fd44 100755 --- a/config.py +++ b/config.py @@ -2,17 +2,25 @@ import os +team_colors = ['F0888A', '88BDF0', '00782B', '999900', 'EF9C00', + 'F4B5B7', 'E2EFFB', '89CA9D', 'FAF519', 'FFE7BB', + 'BA88F0', '8DCFF4', 'BEDFC4', 'FFFAB2', 'D7D7D7', + 'C5B9D7', '006189', '8DCB41', 'FFCC00', '898989'] + if 'home' in os.environ.get('SCRIPT_FILENAME', ''): # We're a CGI running out of someone's home directory config = {'global': {'data_dir': '.', 'base_url': '.', 'css_url': 'ctf.css', - 'diasbled_dir': 'disabled' + 'diasbled_dir': 'disabled', + 'flags_dir': 'flags', + 'house_team': 'dirtbags', + 'passwd': 'passwd', + 'team_colors': team_colors, }, 'puzzler': {'dir': 'puzzles', - 'ignore_dir': 'puzzler.ignore', 'cgi_url': 'puzzler.cgi', 'base_url': 'puzzler', 'keys_file': 'puzzler.keys', @@ -25,10 +33,13 @@ else: 'base_url': '/', 'css_url': '/ctf.css', 'disabled_dir': '/var/lib/ctf/disabled', + 'flags_dir': '/var/lib/ctf/flags', + 'house_team': 'dirtbags', + 'passwd': '/var/lib/ctf/passwd', + 'team_colors': team_colors, }, 'puzzler': {'dir': '/usr/lib/www/puzzler', - 'ignore_dir': '/var/lib/ctf/puzzler.ignore', 'cgi_url': '/puzzler.cgi', 'base_url': '/puzzler', 'keys_file': '/usr/lib/ctf/puzzler.keys', diff --git a/ctf.css b/ctf.css index fa04941..37bd7fe 100644 --- a/ctf.css +++ b/ctf.css @@ -1,7 +1,7 @@ /**** document ****/ html { - background: #222 url(http://dirtbags.net/images/grunge.png) no-repeat; + background: #222 url(grunge.png) repeat-x; } body { @@ -29,18 +29,24 @@ a:hover { } code, pre, .readme { - color: #fff; - background-color: #555; - margin: 1em; + color: #fff; + background-color: #555; + margin: 1em; } +th, td { + vertical-align: top; +} + +.scoreboard td { + height: 400px; +} /**** heading ****/ h1:first-child { text-transform: lowercase; font-size: 1.6em; - letter-spacing: -0.1em; background-color: #222; opacity: 0.9; padding: 3px; @@ -50,6 +56,7 @@ h1:first-child { h1:first-child:before { color: #fff; + letter-spacing: -0.1em; content: "Capture The Flag: "; } diff --git a/flagd.py b/flagd.py index 862afc7..2d1362a 100755 --- a/flagd.py +++ b/flagd.py @@ -7,15 +7,19 @@ import functools import time import hmac import optparse +import os import points import pointscli import teams +import config import traceback key = b'My First Shared Secret (tm)' def hexdigest(data): return hmac.new(key, data).hexdigest() +flags_dir = config.get('global', 'flags_dir') + class Submitter(asyncore.dispatcher): def __init__(self, host='localhost', port=6667): asyncore.dispatcher.__init__(self) @@ -110,8 +114,10 @@ class FlagServer(asynchat.async_chat): def set_flag(self, team): self.flag = team - if self.cat: - self.submitter.set_flag(self.cat, team) + self.submitter.set_flag(self.cat, team) + f = open(os.path.join(flags_dir, self.cat), 'w') + if team: + f.write(team) def found_terminator(self): data = b''.join(self.inbuf) diff --git a/game.py b/game.py index 5104226..9dcd251 100755 --- a/game.py +++ b/game.py @@ -59,9 +59,11 @@ class Flagger(asynchat.async_chat): asynchat.async_chat.handle_error(self) def set_flag(self, team): - if not team: - team = 'dirtbags' - self.push(team.encode('utf-8') + b'\n') + if team: + eteam = team.encode('utf-8') + else: + eteam = b'' + self.push(eteam + b'\n') self.flag = team diff --git a/grunge.png b/grunge.png new file mode 100644 index 0000000..2b98730 Binary files /dev/null and b/grunge.png differ diff --git a/histogram.py b/histogram.py index 434ac68..65309d0 100755 --- a/histogram.py +++ b/histogram.py @@ -4,6 +4,7 @@ import points import time import os import tempfile +import teams import config pngout = config.datafile('histogram.png') @@ -16,8 +17,7 @@ def main(s=None): s = points.Storage() plotparts = [] - teams = s.teams() - teamcolors = points.colors(teams) + teams = s.teams catscores = {} for cat in s.categories(): @@ -27,7 +27,7 @@ def main(s=None): fn = scoresfile.name i = 2 for team in teams: - plotparts.append('"%s" using 1:%d with lines linewidth 2 linetype rgb "#%s"' % (fn, i, teamcolors[team])) + plotparts.append('"%s" using 1:%d with lines linewidth 2 linetype rgb "#%s"' % (fn, i, teams.color(team))) scores[team] = 0 i += 1 diff --git a/intro.html b/intro.html index 0eea942..f1220cb 100644 --- a/intro.html +++ b/intro.html @@ -10,7 +10,7 @@

Introduction

- Welcome to Capture The Flag, blah blah blah. + Welcome to Capture The Flag.

What This Is

@@ -35,7 +35,9 @@

Important Rules

+

Scoring

+ +

+ The contest is made up of multiple categories. Each + category is worth one point toward the total score; the percentage + of the total points held by your team is the percentage of one + point your team has for that category. The team that has 30% of + the points in each of five categories has 1.5 points, whereas the + team that has 80% of the points in only one category has 0.8 + points. +

+ +

+ There are two kinds of categories: flags, + and puzzles. +

+ +

Flags

+ +

+ Flag categories are challenges with a notion of a winner + or service availability. In these categories, the + flag-holder (the winner, or each team with a running service) + makes 1 point per minute for as long as they hold the flag. If + there is a single flag-holder, and the flag changes hands, a point + is awarded to the new winner at the moment the flag moves. +

+ +

Puzzles

+ +

+ Most of the categories come in the form of + multiple puzzles: for each puzzle presented, a key + (answer) must be found to recieve the amount of points that puzzle + is worth. Any team may answer any puzzle question at any time. A + new puzzle is revealed when a team correctly answers the + highest-valued puzzle in that category. +

+

Hints

@@ -63,15 +104,16 @@ points, though. For puzzles, you will lose ΒΌ of the points for that puzzle even if you never solve the puzzle. For other events, the staff member will decide how many points it will - cost. You can try to bribe us or otherwise fanagle information - out of us, or other contestants. It's a hacking contest. + cost. You can try to bribe or otherwise fanagle information out + of us or other contestants. It's a hacking contest.

About Us

- We are the dirtbags. You might - be better at this than we are, but you're not running the contest. + We are the dirtbags. People + pay us money to do the sorts of things you'll be doing in this + contest.

diff --git a/points.py b/points.py index b818691..2f419c9 100755 --- a/points.py +++ b/points.py @@ -93,7 +93,7 @@ class Storage: def __init__(self, fn=None): if not fn: fn = config.datafile('scores.dat') - self.points_by_team = {} + self.teams = set() self.points_by_cat = {} self.points_by_cat_team = {} self.log = [] @@ -128,7 +128,7 @@ class Storage: def add(self, req, write=True): when, cat, team, score = req - incdict(self.points_by_team, team, score) + self.teams.add(team) incdict(self.points_by_cat, cat, score) incdict(self.points_by_cat_team, (cat, team), score) self.log.append(req) @@ -146,31 +146,23 @@ class Storage: def categories(self): return sorted(self.points_by_cat) - def teams(self): - return sorted(self.points_by_team) + def get_teams(self): + return sorted(self.teams) def cat_points(self, cat): return self.points_by_cat.get(cat, 0) def team_points(self, team): - return self.points_by_team.get(team, 0) + points = 0 + for cat, tot in self.points_by_cat.items(): + team_points = self.team_points_in_cat(cat, team) + points += team_points / float(tot) + return points def team_points_in_cat(self, cat, team): return self.points_by_cat_team.get((cat, team), 0) -## -## Colors -## -def colors(teams): - colors = ['F0888A', '88BDF0', '00782B', '999900', 'EF9C00', - 'F4B5B7', 'E2EFFB', '89CA9D', 'FAF519', 'FFE7BB', - 'BA88F0', '8DCFF4', 'BEDFC4', 'FFFAB2', 'D7D7D7', - 'C5B9D7', '006189', '8DCB41', 'FFCC00', '898989'] - return dict(zip(teams, colors)) - - - ## ## Testing @@ -198,9 +190,8 @@ def test(): s.add((now, 'cat1', 'zebras', 20)) s.add((now, 'cat1', 'aardvarks', 10)) s.add((now, 'merf', 'aardvarks', 50)) - assert s.teams() == ['aardvarks', 'zebras'] + assert s.get_teams() == ['aardvarks', 'zebras'] assert s.categories() == ['cat1', 'merf'] - assert s.team_points('aardvarks') == 60 assert s.cat_points('cat1') == 30 assert s.team_points_in_cat('cat1', 'aardvarks') == 10 assert s.team_points_in_cat('merf', 'zebras') == 0 diff --git a/run.ctfd b/run.ctfd index fa143a3..c55f5b3 100755 --- a/run.ctfd +++ b/run.ctfd @@ -1,4 +1,4 @@ #! /bin/sh -/usr/lib/ctf/ctfd.py 2>&1 | logger -t ctfd +exec /usr/lib/ctf/ctfd.py diff --git a/scoreboard.py b/scoreboard.py index e6936d4..f09684f 100755 --- a/scoreboard.py +++ b/scoreboard.py @@ -1,42 +1,64 @@ #!/usr/bin/env python3 import cgitb; cgitb.enable() +import os import config +import teams import points +flags_dir = config.get('global', 'flags_dir') +house_team = config.get('global', 'house_team') + def main(): s = points.Storage() - teams = s.teams() categories = [(cat, s.cat_points(cat)) for cat in s.categories()] - teamcolors = points.colors(teams) print('Content-type: text/html') print() - print(''' - CTF Scoreboard + Scoreboard

Scoreboard

''' % config.base_url) - print('') + print('
') print('') + print('') for cat, score in categories: - print('' % (cat, score)) + print('') print('') print('') + print('') for cat, total in categories: - print('
Overall%s (%d)') + print(' %s (%d)' % (cat, score)) + try: + fn = os.path.join(flags_dir, cat) + team = open(fn).read() or house_team + print('
') + print(' flag: %s' + % (cat, teams.color(team), team)) + except IOError: + pass + print('
    ') + totals = [] + for team in s.teams: + total = s.team_points(team) + totals.append((total, team)) + for total, team in sorted(totals, reverse=True): + print('
  1. %s (%0.3f)
  2. ' + % (teams.color(team), team, total)) + print('
') - scores = sorted([(s.team_points_in_cat(cat, team), team) for team in teams]) + print('') + scores = sorted([(s.team_points_in_cat(cat, team), team) for team in s.teams]) for score, team in scores: - color = teamcolors[team] + color = teams.color(team) print('
' % (float(score * 100)/total, color)) print(' %s: %d' % (cat, team, score)) print('
') diff --git a/teams.py b/teams.py index a7be5de..4cba5ca 100755 --- a/teams.py +++ b/teams.py @@ -2,12 +2,13 @@ import fcntl import time +import config import os from urllib.parse import quote, unquote -house = 'dirtbags' - -passwdfn = '/var/lib/ctf/passwd' +house = config.get('global', 'house_team') +passwdfn = config.get('global', 'passwd') +team_colors = config.get('global', 'team_colors') teams = {} built = 0 @@ -25,7 +26,9 @@ def build_teams(): for line in f: line = line.strip() team, passwd = [unquote(v) for v in line.strip().split('\t')] - teams[team] = passwd + color = team_colors.pop(0) + team_colors.append(color) + teams[team] = (passwd, color) except IOError: pass built = time.time() @@ -35,7 +38,7 @@ def validate(team): def chkpasswd(team, passwd): validate(team) - if teams.get(team) == passwd: + if teams.get(team, [None, None])[0] == passwd: return True else: return False @@ -51,3 +54,15 @@ def add(team, passwd): fcntl.lockf(f, fcntl.LOCK_EX) f.seek(0, 2) f.write('%s\t%s\n' % (quote(team), quote(passwd))) + +def color(team): + t = teams.get(team) + if not t: + validate(team) + t = teams.get(team) + if not t: + return '888888' + return t[1] + + +