Mostly cosmetic changes

This commit is contained in:
Neale Pickett 2009-10-01 12:17:03 -06:00
parent f82d78bd6a
commit da11d73276
12 changed files with 158 additions and 59 deletions

View File

@ -22,13 +22,16 @@ target: $(PYC)
$(INSTALL) ctfd.py $(CTFDIR) $(INSTALL) ctfd.py $(CTFDIR)
$(INSTALL) -d $(WWWDIR) $(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) $(FAKE) ln -s /var/lib/ctf/histogram.png $(WWWDIR)
$(INSTALL) register.cgi scoreboard.cgi puzzler.cgi $(WWWDIR) $(INSTALL) register.cgi scoreboard.cgi puzzler.cgi $(WWWDIR)
$(INSTALL) -d $(DESTDIR)/var/service/ctf $(INSTALL) -d $(DESTDIR)/var/service/ctf
$(INSTALL) run.ctfd $(DESTDIR)/var/service/ctf/run $(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 rm -rf $(WWWDIR)/puzzler
$(INSTALL) -d $(WWWDIR)/puzzler $(INSTALL) -d $(WWWDIR)/puzzler
./mkpuzzles.py --htmldir=$(WWWDIR)/puzzler --keyfile=$(CTFDIR)/puzzler.keys ./mkpuzzles.py --htmldir=$(WWWDIR)/puzzler --keyfile=$(CTFDIR)/puzzler.keys

View File

@ -2,17 +2,25 @@
import os 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', ''): if 'home' in os.environ.get('SCRIPT_FILENAME', ''):
# We're a CGI running out of someone's home directory # We're a CGI running out of someone's home directory
config = {'global': config = {'global':
{'data_dir': '.', {'data_dir': '.',
'base_url': '.', 'base_url': '.',
'css_url': 'ctf.css', 'css_url': 'ctf.css',
'diasbled_dir': 'disabled' 'diasbled_dir': 'disabled',
'flags_dir': 'flags',
'house_team': 'dirtbags',
'passwd': 'passwd',
'team_colors': team_colors,
}, },
'puzzler': 'puzzler':
{'dir': 'puzzles', {'dir': 'puzzles',
'ignore_dir': 'puzzler.ignore',
'cgi_url': 'puzzler.cgi', 'cgi_url': 'puzzler.cgi',
'base_url': 'puzzler', 'base_url': 'puzzler',
'keys_file': 'puzzler.keys', 'keys_file': 'puzzler.keys',
@ -25,10 +33,13 @@ else:
'base_url': '/', 'base_url': '/',
'css_url': '/ctf.css', 'css_url': '/ctf.css',
'disabled_dir': '/var/lib/ctf/disabled', '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': 'puzzler':
{'dir': '/usr/lib/www/puzzler', {'dir': '/usr/lib/www/puzzler',
'ignore_dir': '/var/lib/ctf/puzzler.ignore',
'cgi_url': '/puzzler.cgi', 'cgi_url': '/puzzler.cgi',
'base_url': '/puzzler', 'base_url': '/puzzler',
'keys_file': '/usr/lib/ctf/puzzler.keys', 'keys_file': '/usr/lib/ctf/puzzler.keys',

11
ctf.css
View File

@ -1,7 +1,7 @@
/**** document ****/ /**** document ****/
html { html {
background: #222 url(http://dirtbags.net/images/grunge.png) no-repeat; background: #222 url(grunge.png) repeat-x;
} }
body { body {
@ -34,13 +34,19 @@ code, pre, .readme {
margin: 1em; margin: 1em;
} }
th, td {
vertical-align: top;
}
.scoreboard td {
height: 400px;
}
/**** heading ****/ /**** heading ****/
h1:first-child { h1:first-child {
text-transform: lowercase; text-transform: lowercase;
font-size: 1.6em; font-size: 1.6em;
letter-spacing: -0.1em;
background-color: #222; background-color: #222;
opacity: 0.9; opacity: 0.9;
padding: 3px; padding: 3px;
@ -50,6 +56,7 @@ h1:first-child {
h1:first-child:before { h1:first-child:before {
color: #fff; color: #fff;
letter-spacing: -0.1em;
content: "Capture The Flag: "; content: "Capture The Flag: ";
} }

View File

@ -7,15 +7,19 @@ import functools
import time import time
import hmac import hmac
import optparse import optparse
import os
import points import points
import pointscli import pointscli
import teams import teams
import config
import traceback import traceback
key = b'My First Shared Secret (tm)' key = b'My First Shared Secret (tm)'
def hexdigest(data): def hexdigest(data):
return hmac.new(key, data).hexdigest() return hmac.new(key, data).hexdigest()
flags_dir = config.get('global', 'flags_dir')
class Submitter(asyncore.dispatcher): class Submitter(asyncore.dispatcher):
def __init__(self, host='localhost', port=6667): def __init__(self, host='localhost', port=6667):
asyncore.dispatcher.__init__(self) asyncore.dispatcher.__init__(self)
@ -110,8 +114,10 @@ class FlagServer(asynchat.async_chat):
def set_flag(self, team): def set_flag(self, team):
self.flag = 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): def found_terminator(self):
data = b''.join(self.inbuf) data = b''.join(self.inbuf)

View File

@ -59,9 +59,11 @@ class Flagger(asynchat.async_chat):
asynchat.async_chat.handle_error(self) asynchat.async_chat.handle_error(self)
def set_flag(self, team): def set_flag(self, team):
if not team: if team:
team = 'dirtbags' eteam = team.encode('utf-8')
self.push(team.encode('utf-8') + b'\n') else:
eteam = b''
self.push(eteam + b'\n')
self.flag = team self.flag = team

BIN
grunge.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

@ -4,6 +4,7 @@ import points
import time import time
import os import os
import tempfile import tempfile
import teams
import config import config
pngout = config.datafile('histogram.png') pngout = config.datafile('histogram.png')
@ -16,8 +17,7 @@ def main(s=None):
s = points.Storage() s = points.Storage()
plotparts = [] plotparts = []
teams = s.teams() teams = s.teams
teamcolors = points.colors(teams)
catscores = {} catscores = {}
for cat in s.categories(): for cat in s.categories():
@ -27,7 +27,7 @@ def main(s=None):
fn = scoresfile.name fn = scoresfile.name
i = 2 i = 2
for team in teams: 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 scores[team] = 0
i += 1 i += 1

View File

@ -10,7 +10,7 @@
<h1>Introduction</h1> <h1>Introduction</h1>
<p> <p>
Welcome to Capture The Flag, blah blah blah. Welcome to Capture The Flag.
</p> </p>
<h2>What This Is</h2> <h2>What This Is</h2>
@ -35,7 +35,9 @@
<h3>Important Rules</h3> <h3>Important Rules</h3>
<ul> <ul>
<li>The contest network is 10.<i>x</i>.<i>x</i>.<i>x</i>. <b>Do <li>The contest network is 10.<i>x</i>.<i>x</i>.<i>x</i>. <b>Do
not attack machines outside the contest network</b>.</li> not attack machines outside the contest network</b>. All
federal, state, and school laws still apply to the outside
network.</li>
<li>If the "outside network" requires you to plug into a different <li>If the "outside network" requires you to plug into a different
switch, do not connect any machine that has been on the contest switch, do not connect any machine that has been on the contest
network.</li> network.</li>
@ -52,10 +54,49 @@
<li>If IRC is up, you should use it to communicate with the <li>If IRC is up, you should use it to communicate with the
contest staff. Staff will have operator status in #ctf.</li> contest staff. Staff will have operator status in #ctf.</li>
<li>If you think something is wrong with the game, you are <li>If you think something is wrong with the game, you are
expected to demonstrate the problem and explain why you think expected to demonstrate the problem and explain what you think
it's a problem.</li> is the correct behavior.</li>
</ul> </ul>
<h2>Scoring</h2>
<p>
The contest is made up of multiple <em>categories</em>. 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.
</p>
<p>
There are two kinds of categories: <em>flags</em>,
and <em>puzzles</em>.
</p>
<h3>Flags</h3>
<p>
Flag categories are challenges with a notion of a <em>winner</em>
or <em>service availability</em>. 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.
</p>
<h3>Puzzles</h3>
<p>
Most of the categories come in the form of
multiple <em>puzzles</em>: 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.
</p>
<h2>Hints</h2> <h2>Hints</h2>
<p> <p>
@ -63,15 +104,16 @@
points, though. For puzzles, you will lose ¼ of the points for points, though. For puzzles, you will lose ¼ of the points for
that puzzle <em>even if you never solve the puzzle</em>. For that puzzle <em>even if you never solve the puzzle</em>. For
other events, the staff member will decide how many points it will other events, the staff member will decide how many points it will
cost. You can try to bribe us or otherwise fanagle information cost. You can try to bribe or otherwise fanagle information out
out of us, or other contestants. <em>It's a hacking contest.</em> of us or other contestants. <em>It's a hacking contest.</em>
</p> </p>
<h2>About Us</h2> <h2>About Us</h2>
<p> <p>
We are the <a href="http://dirtbags.net/">dirtbags</a>. You might We are the <a href="http://dirtbags.net/">dirtbags</a>. People
be better at this than we are, but you're not running the contest. pay us money to do the sorts of things you'll be doing in this
contest.
</p> </p>
</body> </body>

View File

@ -93,7 +93,7 @@ class Storage:
def __init__(self, fn=None): def __init__(self, fn=None):
if not fn: if not fn:
fn = config.datafile('scores.dat') fn = config.datafile('scores.dat')
self.points_by_team = {} self.teams = set()
self.points_by_cat = {} self.points_by_cat = {}
self.points_by_cat_team = {} self.points_by_cat_team = {}
self.log = [] self.log = []
@ -128,7 +128,7 @@ class Storage:
def add(self, req, write=True): def add(self, req, write=True):
when, cat, team, score = req 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, cat, score)
incdict(self.points_by_cat_team, (cat, team), score) incdict(self.points_by_cat_team, (cat, team), score)
self.log.append(req) self.log.append(req)
@ -146,31 +146,23 @@ class Storage:
def categories(self): def categories(self):
return sorted(self.points_by_cat) return sorted(self.points_by_cat)
def teams(self): def get_teams(self):
return sorted(self.points_by_team) return sorted(self.teams)
def cat_points(self, cat): def cat_points(self, cat):
return self.points_by_cat.get(cat, 0) return self.points_by_cat.get(cat, 0)
def team_points(self, team): 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): def team_points_in_cat(self, cat, team):
return self.points_by_cat_team.get((cat, team), 0) 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 ## Testing
@ -198,9 +190,8 @@ def test():
s.add((now, 'cat1', 'zebras', 20)) s.add((now, 'cat1', 'zebras', 20))
s.add((now, 'cat1', 'aardvarks', 10)) s.add((now, 'cat1', 'aardvarks', 10))
s.add((now, 'merf', 'aardvarks', 50)) s.add((now, 'merf', 'aardvarks', 50))
assert s.teams() == ['aardvarks', 'zebras'] assert s.get_teams() == ['aardvarks', 'zebras']
assert s.categories() == ['cat1', 'merf'] assert s.categories() == ['cat1', 'merf']
assert s.team_points('aardvarks') == 60
assert s.cat_points('cat1') == 30 assert s.cat_points('cat1') == 30
assert s.team_points_in_cat('cat1', 'aardvarks') == 10 assert s.team_points_in_cat('cat1', 'aardvarks') == 10
assert s.team_points_in_cat('merf', 'zebras') == 0 assert s.team_points_in_cat('merf', 'zebras') == 0

View File

@ -1,4 +1,4 @@
#! /bin/sh #! /bin/sh
/usr/lib/ctf/ctfd.py 2>&1 | logger -t ctfd exec /usr/lib/ctf/ctfd.py

View File

@ -1,42 +1,64 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import cgitb; cgitb.enable() import cgitb; cgitb.enable()
import os
import config import config
import teams
import points import points
flags_dir = config.get('global', 'flags_dir')
house_team = config.get('global', 'house_team')
def main(): def main():
s = points.Storage() s = points.Storage()
teams = s.teams()
categories = [(cat, s.cat_points(cat)) for cat in s.categories()] categories = [(cat, s.cat_points(cat)) for cat in s.categories()]
teamcolors = points.colors(teams)
print('Content-type: text/html') print('Content-type: text/html')
print() print()
print('''<?xml version="1.0" encoding="UTF-8" ?> print('''<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head> <head>
<title>CTF Scoreboard</title> <title>Scoreboard</title>
<link rel="stylesheet" href="%sctf.css" type="text/css" /> <link rel="stylesheet" href="%sctf.css" type="text/css" />
</head> </head>
<body> <body>
<h1>Scoreboard</h1> <h1>Scoreboard</h1>
''' % config.base_url) ''' % config.base_url)
print('<table>') print('<table class="scoreboard">')
print('<tr>') print('<tr>')
print('<th>Overall</th>')
for cat, score in categories: for cat, score in categories:
print('<th>%s (%d)</th>' % (cat, score)) print('<th>')
print(' %s (%d)' % (cat, score))
try:
fn = os.path.join(flags_dir, cat)
team = open(fn).read() or house_team
print(' <br/>')
print(' <!-- flag: %s --> flag: <span style="color: #%s">%s</span>'
% (cat, teams.color(team), team))
except IOError:
pass
print('</th>')
print('</tr>') print('</tr>')
print('<tr>') print('<tr>')
print('<td><ol>')
totals = []
for team in s.teams:
total = s.team_points(team)
totals.append((total, team))
for total, team in sorted(totals, reverse=True):
print('<li><span style="color: #%s;">%s (%0.3f)</span></li>'
% (teams.color(team), team, total))
print('</ol></td>')
for cat, total in categories: for cat, total in categories:
print('<td style="height: 400px;">') print('<td>')
scores = sorted([(s.team_points_in_cat(cat, team), team) for team in teams]) scores = sorted([(s.team_points_in_cat(cat, team), team) for team in s.teams])
for score, team in scores: for score, team in scores:
color = teamcolors[team] color = teams.color(team)
print('<div style="height: %f%%; overflow: hidden; background: #%s; color: black;">' % (float(score * 100)/total, color)) print('<div style="height: %f%%; overflow: hidden; background: #%s; color: black;">' % (float(score * 100)/total, color))
print('<!-- category: %s --> %s: %d' % (cat, team, score)) print('<!-- category: %s --> %s: %d' % (cat, team, score))
print('</div>') print('</div>')

View File

@ -2,12 +2,13 @@
import fcntl import fcntl
import time import time
import config
import os import os
from urllib.parse import quote, unquote from urllib.parse import quote, unquote
house = 'dirtbags' house = config.get('global', 'house_team')
passwdfn = config.get('global', 'passwd')
passwdfn = '/var/lib/ctf/passwd' team_colors = config.get('global', 'team_colors')
teams = {} teams = {}
built = 0 built = 0
@ -25,7 +26,9 @@ def build_teams():
for line in f: for line in f:
line = line.strip() line = line.strip()
team, passwd = [unquote(v) for v in line.strip().split('\t')] 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: except IOError:
pass pass
built = time.time() built = time.time()
@ -35,7 +38,7 @@ def validate(team):
def chkpasswd(team, passwd): def chkpasswd(team, passwd):
validate(team) validate(team)
if teams.get(team) == passwd: if teams.get(team, [None, None])[0] == passwd:
return True return True
else: else:
return False return False
@ -51,3 +54,15 @@ def add(team, passwd):
fcntl.lockf(f, fcntl.LOCK_EX) fcntl.lockf(f, fcntl.LOCK_EX)
f.seek(0, 2) f.seek(0, 2)
f.write('%s\t%s\n' % (quote(team), quote(passwd))) 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]