mirror of https://github.com/dirtbags/moth.git
Changes to work on qnap
This commit is contained in:
parent
ba19575d62
commit
46e7971151
|
@ -0,0 +1,7 @@
|
||||||
|
*~
|
||||||
|
*.pyc
|
||||||
|
*.dat
|
||||||
|
passwd
|
||||||
|
target/
|
||||||
|
puzzler/
|
||||||
|
ctf.tce
|
|
@ -0,0 +1,44 @@
|
||||||
|
DESTDIR = target
|
||||||
|
|
||||||
|
CTFDIR = $(DESTDIR)/usr/lib/ctf
|
||||||
|
WWWDIR = $(DESTDIR)/usr/lib/www
|
||||||
|
|
||||||
|
FAKE = fakeroot -s fake -i fake
|
||||||
|
INSTALL = $(FAKE) install
|
||||||
|
|
||||||
|
PYC = config.pyc points.pyc game.pyc teams.pyc
|
||||||
|
PYC += register.pyc scoreboard.pyc puzzler.pyc
|
||||||
|
PYC += flagd.pyc pointsd.pyc pointscli.pyc
|
||||||
|
PYC += roshambo.pyc histogram.pyc
|
||||||
|
|
||||||
|
all: ctf.tce
|
||||||
|
|
||||||
|
target: $(PYC)
|
||||||
|
$(INSTALL) -d --mode=0755 --owner=100 $(DESTDIR)/var/lib/ctf
|
||||||
|
$(INSTALL) -d $(DESTDIR)/var/lib/ctf/disabled
|
||||||
|
|
||||||
|
$(INSTALL) -d $(CTFDIR)
|
||||||
|
$(INSTALL) $(PYC) $(CTFDIR)
|
||||||
|
$(INSTALL) uberserv.py $(CTFDIR)
|
||||||
|
|
||||||
|
$(INSTALL) -d $(WWWDIR)
|
||||||
|
$(INSTALL) index.html ctf.css $(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.uberserv $(DESTDIR)/var/service/ctf/run
|
||||||
|
|
||||||
|
rm -rf $(WWWDIR)/puzzler
|
||||||
|
$(INSTALL) -d $(WWWDIR)/puzzler
|
||||||
|
./mkpuzzles.py --htmldir=$(WWWDIR)/puzzler --keyfile=$(CTFDIR)/puzzler.keys
|
||||||
|
|
||||||
|
ctf.tce: target
|
||||||
|
$(FAKE) sh -c 'cd target && tar -czf - --exclude=placeholder --exclude=*~ .' > $@
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf target
|
||||||
|
rm -f fake ctf.tce $(PYC)
|
||||||
|
|
||||||
|
%.pyc: %.py
|
||||||
|
python3 -c 'import $*'
|
|
@ -0,0 +1,57 @@
|
||||||
|
#! /usr/bin/env python3
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
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'
|
||||||
|
},
|
||||||
|
'puzzler':
|
||||||
|
{'dir': 'puzzles',
|
||||||
|
'ignore_dir': 'puzzler.ignore',
|
||||||
|
'cgi_url': 'puzzler.cgi',
|
||||||
|
'base_url': 'puzzler',
|
||||||
|
'keys_file': 'puzzler.keys',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
# An actual installation
|
||||||
|
config = {'global':
|
||||||
|
{'data_dir': '/var/lib/ctf',
|
||||||
|
'base_url': '/',
|
||||||
|
'css_url': '/ctf.css',
|
||||||
|
'disabled_dir': '/var/lib/ctf/disabled',
|
||||||
|
},
|
||||||
|
'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',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def get(section, key):
|
||||||
|
return config[section][key]
|
||||||
|
|
||||||
|
disabled_dir = get('global', 'disabled_dir')
|
||||||
|
data_dir = get('global', 'data_dir')
|
||||||
|
base_url = get('global', 'base_url')
|
||||||
|
css = get('global', 'css_url')
|
||||||
|
|
||||||
|
def disabled(cat):
|
||||||
|
path = os.path.join(disabled_dir, cat)
|
||||||
|
return os.path.exists(path)
|
||||||
|
|
||||||
|
def enabled(cat):
|
||||||
|
return not disabled(cat)
|
||||||
|
|
||||||
|
def datafile(filename):
|
||||||
|
return os.path.join(data_dir, filename)
|
||||||
|
|
||||||
|
def url(path):
|
||||||
|
return base_url + path
|
|
@ -1,15 +0,0 @@
|
||||||
#! /usr/bin/env python3
|
|
||||||
|
|
||||||
# 0 1 2 3 4
|
|
||||||
# 1234567890123456789012345678901234567890123
|
|
||||||
# ||| | | | | | | | | | | | |
|
|
||||||
msg = (' #### #### # ### ### ### # ### '
|
|
||||||
' # # # # ## # # # # # '
|
|
||||||
' # # #### # #### #### # # # '
|
|
||||||
' # # # # # # # # # # # # '
|
|
||||||
' #### #### ### ### ### ### # ### ')
|
|
||||||
|
|
||||||
msg = msg.replace('#', '0')
|
|
||||||
msg = msg.replace(' ', '1')
|
|
||||||
num = int(msg, 2)
|
|
||||||
print(num)
|
|
|
@ -1,30 +0,0 @@
|
||||||
#! /usr/bin/env python3
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import random
|
|
||||||
|
|
||||||
primes = [2, 3, 5, 7, 11, 13, 17, 19]
|
|
||||||
letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
|
||||||
|
|
||||||
data = sys.stdin.read().strip()
|
|
||||||
jumble = ''.join(data.split())
|
|
||||||
|
|
||||||
lj = len(jumble)
|
|
||||||
below = (0, 0)
|
|
||||||
above = (lj, 2)
|
|
||||||
for i in primes:
|
|
||||||
for j in primes:
|
|
||||||
m = i * j
|
|
||||||
if (m < lj) and (m > below[0] * below[1]):
|
|
||||||
below = (i, j)
|
|
||||||
elif (m >= lj) and (m < (above[0] * above[1])):
|
|
||||||
above = (i, j)
|
|
||||||
|
|
||||||
for i in range(lj, (above[0] * above[1])):
|
|
||||||
jumble += random.choice(letters)
|
|
||||||
|
|
||||||
out = []
|
|
||||||
for i in range(above[0]):
|
|
||||||
for j in range(above[1]):
|
|
||||||
out.append(jumble[j*above[0] + i])
|
|
||||||
print(''.join(out))
|
|
12
histogram.py
12
histogram.py
|
@ -4,13 +4,16 @@ import points
|
||||||
import time
|
import time
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
|
import config
|
||||||
|
|
||||||
|
pngout = config.datafile('histogram.png')
|
||||||
|
|
||||||
def main(s=None):
|
def main(s=None):
|
||||||
scores = {}
|
scores = {}
|
||||||
now = 0
|
now = 0
|
||||||
|
|
||||||
if not s:
|
if not s:
|
||||||
s = points.Storage('scores.dat')
|
s = points.Storage()
|
||||||
|
|
||||||
plotparts = []
|
plotparts = []
|
||||||
teams = s.teams()
|
teams = s.teams()
|
||||||
|
@ -57,11 +60,12 @@ set xtics nomirror
|
||||||
set ytics nomirror
|
set ytics nomirror
|
||||||
set nokey
|
set nokey
|
||||||
set terminal png transparent size 640,200 x000000 xffffff
|
set terminal png transparent size 640,200 x000000 xffffff
|
||||||
set output "histogram.png"
|
set output "%(pngout)s"
|
||||||
plot %(plot)s\n''' % {'plot': ','.join(plotparts)})
|
plot %(plot)s\n''' % {'plot': ','.join(plotparts),
|
||||||
|
'pngout': pngout})
|
||||||
instructions.flush()
|
instructions.flush()
|
||||||
|
|
||||||
gp = os.system('gnuplot %s' % instructions.name)
|
gp = os.system('gnuplot %s 2>/dev/null' % instructions.name)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
#! /usr/bin/env python3
|
||||||
|
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import optparse
|
||||||
|
import config
|
||||||
|
|
||||||
|
p = optparse.OptionParser()
|
||||||
|
p.add_option('-p', '--puzzles', dest='puzzles', default='puzzles',
|
||||||
|
help='Directory containing puzzles')
|
||||||
|
p.add_option('-w', '--htmldir', dest='htmldir', default='puzzler',
|
||||||
|
help='Directory to write HTML puzzle tree')
|
||||||
|
p.add_option('-k', '--keyfile', dest='keyfile', default='puzzler.keys',
|
||||||
|
help='Where to write keys')
|
||||||
|
|
||||||
|
opts, args = p.parse_args()
|
||||||
|
|
||||||
|
keys = []
|
||||||
|
|
||||||
|
for cat in os.listdir(opts.puzzles):
|
||||||
|
dirname = os.path.join(opts.puzzles, cat)
|
||||||
|
for points in os.listdir(dirname):
|
||||||
|
pointsdir = os.path.join(dirname, points)
|
||||||
|
outdir = os.path.join(opts.htmldir, cat, points)
|
||||||
|
os.makedirs(outdir)
|
||||||
|
|
||||||
|
readme = ''
|
||||||
|
files = []
|
||||||
|
for fn in os.listdir(pointsdir):
|
||||||
|
path = os.path.join(pointsdir, fn)
|
||||||
|
if fn == 'key':
|
||||||
|
key = open(path, encoding='utf-8').readline().strip()
|
||||||
|
keys.append((cat, points, key))
|
||||||
|
elif fn == 'index.html':
|
||||||
|
readme = open(path, encoding='utf-8').read()
|
||||||
|
else:
|
||||||
|
files.append((fn, path))
|
||||||
|
|
||||||
|
title = '%s for %s points' % (cat, points)
|
||||||
|
f = open(os.path.join(outdir, 'index.html'), 'w', encoding='utf-8')
|
||||||
|
f.write('''<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE html PUBLIC
|
||||||
|
"-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||||
|
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<head>
|
||||||
|
<title>%(title)s</title>
|
||||||
|
<link rel="stylesheet" href="%(css)s" type="text/css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>%(title)s</h1>
|
||||||
|
''' % {'title': title,
|
||||||
|
'css': config.css})
|
||||||
|
if readme:
|
||||||
|
f.write('<div class="readme">%s</div>\n' % readme)
|
||||||
|
if files:
|
||||||
|
f.write('<ul>\n')
|
||||||
|
for fn, path in files:
|
||||||
|
shutil.copy(path, outdir)
|
||||||
|
f.write('<li><a href="%s">%s</a></li>\n' % (fn, fn))
|
||||||
|
f.write('</ul>\n')
|
||||||
|
f.write('''
|
||||||
|
<form action="%(cgi)s" method="post">
|
||||||
|
<input type="hidden" name="c" value="%(cat)s" />
|
||||||
|
<input type="hidden" name="p" value="%(points)s" />
|
||||||
|
Team: <input name="t" /><br />
|
||||||
|
Password: <input type="password" name="w" /><br />
|
||||||
|
Key: <input name="k" /><br />
|
||||||
|
<input type="submit" />
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
''' % {'cgi': config.get('puzzler', 'cgi_url'),
|
||||||
|
'cat': cat,
|
||||||
|
'points': points})
|
||||||
|
|
||||||
|
f = open(opts.keyfile, 'w', encoding='utf-8')
|
||||||
|
for key in keys:
|
||||||
|
f.write('%s\t%s\t%s\n' % key)
|
||||||
|
|
|
@ -5,6 +5,8 @@ import hmac
|
||||||
import struct
|
import struct
|
||||||
import io
|
import io
|
||||||
import teams
|
import teams
|
||||||
|
import config
|
||||||
|
import os
|
||||||
|
|
||||||
##
|
##
|
||||||
## Authentication
|
## Authentication
|
||||||
|
@ -88,7 +90,9 @@ def incdict(dict, key, amt=1):
|
||||||
dict[key] = dict.get(key, 0) + amt
|
dict[key] = dict.get(key, 0) + amt
|
||||||
|
|
||||||
class Storage:
|
class Storage:
|
||||||
def __init__(self, fn='/var/lib/ctf/points.dat'):
|
def __init__(self, fn=None):
|
||||||
|
if not fn:
|
||||||
|
fn = config.datafile('scores.dat')
|
||||||
self.points_by_team = {}
|
self.points_by_team = {}
|
||||||
self.points_by_cat = {}
|
self.points_by_cat = {}
|
||||||
self.points_by_cat_team = {}
|
self.points_by_cat_team = {}
|
||||||
|
|
205
puzzler.cgi
205
puzzler.cgi
|
@ -1,206 +1,7 @@
|
||||||
#! /usr/bin/env python3
|
#! /usr/bin/env python3
|
||||||
|
|
||||||
import cgitb; cgitb.enable()
|
import cgitb; cgitb.enable()
|
||||||
import cgi
|
|
||||||
import os
|
|
||||||
import fcntl
|
|
||||||
import re
|
|
||||||
import sys
|
import sys
|
||||||
import pointscli
|
sys.path.insert(0, '/usr/lib/ctf')
|
||||||
import teams
|
import puzzler
|
||||||
import http.cookies
|
puzzler.main()
|
||||||
from urllib.parse import quote, unquote
|
|
||||||
|
|
||||||
##
|
|
||||||
## This allows you to edit the URL and work on puzzles that haven't been
|
|
||||||
## unlocked yet. For now I think that's an okay vulnerability. It's a
|
|
||||||
## hacking contest, after all.
|
|
||||||
##
|
|
||||||
|
|
||||||
cat_re = re.compile(r'^[a-z]+$')
|
|
||||||
points_re = re.compile(r'^[0-9]+$')
|
|
||||||
|
|
||||||
def dbg(*vals):
|
|
||||||
print('Content-type: text/plain\n\n')
|
|
||||||
print(*vals)
|
|
||||||
|
|
||||||
|
|
||||||
points_by_cat = {}
|
|
||||||
points_by_team = {}
|
|
||||||
try:
|
|
||||||
for line in open('/var/lib/ctf/puzzler.dat'):
|
|
||||||
cat, team, pts = [unquote(v) for v in line.strip().split('\t')]
|
|
||||||
pts = int(pts)
|
|
||||||
points_by_cat[cat] = max(points_by_cat.get(cat, 0), pts)
|
|
||||||
points_by_team.setdefault((team, cat), set()).add(pts)
|
|
||||||
except IOError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
c = http.cookies.SimpleCookie(os.environ.get('HTTP_COOKIE', ''))
|
|
||||||
try:
|
|
||||||
team = c['team'].value
|
|
||||||
passwd = c['passwd'].value
|
|
||||||
except KeyError:
|
|
||||||
team, passwd = None, None
|
|
||||||
|
|
||||||
f = cgi.FieldStorage()
|
|
||||||
cat = f.getfirst('c')
|
|
||||||
points = f.getfirst('p')
|
|
||||||
team = f.getfirst('t', team)
|
|
||||||
passwd = f.getfirst('w', passwd)
|
|
||||||
key = f.getfirst('k')
|
|
||||||
|
|
||||||
verboten = ['key', 'index.html']
|
|
||||||
|
|
||||||
def start_html(title):
|
|
||||||
print('Content-type: text/html')
|
|
||||||
if team or passwd:
|
|
||||||
c = http.cookies.SimpleCookie()
|
|
||||||
if team:
|
|
||||||
c['team'] = team
|
|
||||||
if passwd:
|
|
||||||
c['passwd'] = passwd
|
|
||||||
print(c)
|
|
||||||
print()
|
|
||||||
print('''<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE html PUBLIC
|
|
||||||
"-//W3C//DTD XHTML 1.0 Strict//EN"
|
|
||||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
|
||||||
<head>
|
|
||||||
<title>%s</title>
|
|
||||||
<link rel="stylesheet" href="ctf.css" type="text/css" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>%s</h1>
|
|
||||||
''' % (title, title))
|
|
||||||
|
|
||||||
def end_html():
|
|
||||||
print('</body></html>')
|
|
||||||
|
|
||||||
|
|
||||||
def safe_join(*args):
|
|
||||||
safe = []
|
|
||||||
for a in args:
|
|
||||||
if not a:
|
|
||||||
return None
|
|
||||||
else:
|
|
||||||
a = a.replace('..', '')
|
|
||||||
a = a.replace('/', '')
|
|
||||||
safe.append(a)
|
|
||||||
ret = '/'.join(safe)
|
|
||||||
if os.path.exists(ret):
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def dump_file(fn):
|
|
||||||
f = open(fn, 'rb')
|
|
||||||
while True:
|
|
||||||
d = f.read(4096)
|
|
||||||
if not d:
|
|
||||||
break
|
|
||||||
sys.stdout.buffer.write(d)
|
|
||||||
|
|
||||||
def show_cats():
|
|
||||||
start_html('Categories')
|
|
||||||
print('<ul>')
|
|
||||||
for p in sorted(os.listdir('puzzles')):
|
|
||||||
print('<li><a href="puzzler.cgi?c=%s">%s</a></li>' % (p, p))
|
|
||||||
print('</ul>')
|
|
||||||
end_html()
|
|
||||||
|
|
||||||
|
|
||||||
def show_puzzles(cat, cat_dir):
|
|
||||||
start_html('Open in %s' % cat)
|
|
||||||
opened = points_by_cat.get(cat, 0)
|
|
||||||
puzzles = sorted([int(v) for v in os.listdir(cat_dir)])
|
|
||||||
if puzzles:
|
|
||||||
print('<ul>')
|
|
||||||
for p in puzzles:
|
|
||||||
print('<li><a href="puzzler.cgi?c=%s&p=%d">%d</a></li>' % (cat, p, p))
|
|
||||||
if p > opened:
|
|
||||||
break
|
|
||||||
print('</ul>')
|
|
||||||
else:
|
|
||||||
print('<p>None (someone is slacking)</p>')
|
|
||||||
end_html()
|
|
||||||
|
|
||||||
def show_puzzle(cat, points, points_dir, team='', passwd=''):
|
|
||||||
# Show puzzle in cat for points
|
|
||||||
start_html('%s for %s points' % (cat, points))
|
|
||||||
fn = os.path.join(points_dir, 'index.html')
|
|
||||||
if os.path.exists(fn):
|
|
||||||
print('<div class="readme">')
|
|
||||||
dump_file(fn)
|
|
||||||
print('</div>')
|
|
||||||
print('<ul>')
|
|
||||||
for fn in sorted(os.listdir(points_dir)):
|
|
||||||
if fn.endswith('~') or fn.startswith('.') or fn in verboten:
|
|
||||||
continue
|
|
||||||
print('<li><a href="puzzler.cgi?c=%s&p=%s&f=%s">%s</a></li>' % (cat, points, fn, fn))
|
|
||||||
print('</ul>')
|
|
||||||
print('<form action="puzzler.cgi" method="post">')
|
|
||||||
print('<input type="hidden" name="c" value="%s" />' % cat)
|
|
||||||
print('<input type="hidden" name="p" value="%s" />' % points)
|
|
||||||
print('Team: <input name="t" value="%s" /><br />' % (team or ''))
|
|
||||||
print('Password: <input type="password" name="w" value="%s" /><br />' % (passwd or ''))
|
|
||||||
print('Key: <input name="k" /><br />')
|
|
||||||
print('<input type="submit" />')
|
|
||||||
print('</form>')
|
|
||||||
end_html()
|
|
||||||
|
|
||||||
def win(cat, team, points):
|
|
||||||
start_html('Winner!')
|
|
||||||
points = int(points)
|
|
||||||
f = open('puzzler.dat', 'a')
|
|
||||||
pointscli.submit(cat, team, points)
|
|
||||||
fcntl.lockf(f, fcntl.LOCK_EX)
|
|
||||||
f.write('%s\t%s\t%d\n' % (quote(cat), quote(team), points))
|
|
||||||
print('<p>%d points for %s.</p>' % (points, team))
|
|
||||||
print('<p>Back to <a href="puzzler.cgi?c=%s">%s</a>.</p>' % (cat, cat))
|
|
||||||
end_html()
|
|
||||||
|
|
||||||
def main():
|
|
||||||
cat_dir = safe_join('puzzles', cat)
|
|
||||||
points_dir = safe_join('puzzles', cat, points)
|
|
||||||
|
|
||||||
if not cat_dir:
|
|
||||||
# Show categories
|
|
||||||
show_cats()
|
|
||||||
elif not points_dir:
|
|
||||||
# Show available puzzles in category
|
|
||||||
show_puzzles(cat, cat_dir)
|
|
||||||
elif not (team and passwd and key):
|
|
||||||
fn = f.getfirst('f')
|
|
||||||
if fn in verboten:
|
|
||||||
fn = None
|
|
||||||
fn = safe_join('puzzles', cat, points, fn)
|
|
||||||
if fn:
|
|
||||||
# Provide a file from this directory
|
|
||||||
print('Content-type: application/octet-stream')
|
|
||||||
print()
|
|
||||||
dump_file(fn)
|
|
||||||
else:
|
|
||||||
show_puzzle(cat, points, points_dir, team, passwd)
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
thekey = open('%s/key' % points_dir, encoding='utf-8').read().strip()
|
|
||||||
except IOError:
|
|
||||||
# If there's no key, this can never be solved.
|
|
||||||
thekey = False
|
|
||||||
if not teams.chkpasswd(team, passwd):
|
|
||||||
start_html('Wrong password')
|
|
||||||
end_html()
|
|
||||||
elif key != thekey:
|
|
||||||
show_puzzle(cat, points, points_dir, team, passwd)
|
|
||||||
elif int(points) in points_by_team.get((team, cat), set()):
|
|
||||||
start_html('Greedy greedy')
|
|
||||||
end_html()
|
|
||||||
else:
|
|
||||||
win(cat, team, points)
|
|
||||||
|
|
||||||
main()
|
|
||||||
|
|
||||||
# Local Variables:
|
|
||||||
# mode: python
|
|
||||||
# End:
|
|
||||||
|
|
|
@ -0,0 +1,193 @@
|
||||||
|
#! /usr/bin/env python3
|
||||||
|
|
||||||
|
import cgi
|
||||||
|
import os
|
||||||
|
import fcntl
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import http.cookies
|
||||||
|
from urllib.parse import quote, unquote
|
||||||
|
import config
|
||||||
|
import pointscli
|
||||||
|
import teams
|
||||||
|
|
||||||
|
datafile = config.datafile('puzzler.dat')
|
||||||
|
keysfile = config.get('puzzler', 'keys_file')
|
||||||
|
puzzles_dir = config.get('puzzler', 'dir')
|
||||||
|
cgi_url = config.get('puzzler', 'cgi_url')
|
||||||
|
base_url = config.get('puzzler', 'base_url')
|
||||||
|
|
||||||
|
##
|
||||||
|
## This allows you to edit the URL and work on puzzles that haven't been
|
||||||
|
## unlocked yet. For now I think that's an okay vulnerability. It's a
|
||||||
|
## hacking contest, after all.
|
||||||
|
##
|
||||||
|
|
||||||
|
cat_re = re.compile(r'^[a-z]+$')
|
||||||
|
points_re = re.compile(r'^[0-9]+$')
|
||||||
|
|
||||||
|
def dbg(*vals):
|
||||||
|
print('<--: \nContent-type: text/html\n\n--><pre>')
|
||||||
|
print(*vals)
|
||||||
|
print('</pre>')
|
||||||
|
|
||||||
|
|
||||||
|
points_by_cat = {}
|
||||||
|
points_by_team = {}
|
||||||
|
try:
|
||||||
|
for line in open(datafile, encoding='utf-8'):
|
||||||
|
cat, team, pts = [unquote(v) for v in line.strip().split('\t')]
|
||||||
|
pts = int(pts)
|
||||||
|
points_by_cat[cat] = max(points_by_cat.get(cat, 0), pts)
|
||||||
|
points_by_team.setdefault((team, cat), set()).add(pts)
|
||||||
|
except IOError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
c = http.cookies.SimpleCookie(os.environ.get('HTTP_COOKIE', ''))
|
||||||
|
try:
|
||||||
|
team = c['team'].value
|
||||||
|
passwd = c['passwd'].value
|
||||||
|
except KeyError:
|
||||||
|
team, passwd = None, None
|
||||||
|
|
||||||
|
f = cgi.FieldStorage()
|
||||||
|
cat = f.getfirst('c')
|
||||||
|
points = f.getfirst('p')
|
||||||
|
team = f.getfirst('t', team)
|
||||||
|
passwd = f.getfirst('w', passwd)
|
||||||
|
key = f.getfirst('k')
|
||||||
|
|
||||||
|
def start_html(title):
|
||||||
|
if os.environ.get('GATEWAY_INTERFACE'):
|
||||||
|
print('Content-type: text/html')
|
||||||
|
if team or passwd:
|
||||||
|
c = http.cookies.SimpleCookie()
|
||||||
|
if team:
|
||||||
|
c['team'] = team
|
||||||
|
if passwd:
|
||||||
|
c['passwd'] = passwd
|
||||||
|
print(c)
|
||||||
|
print()
|
||||||
|
print('''<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE html PUBLIC
|
||||||
|
"-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||||
|
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<head>
|
||||||
|
<title>%s</title>
|
||||||
|
<link rel="stylesheet" href="%s" type="text/css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>%s</h1>
|
||||||
|
''' % (title, config.css, title))
|
||||||
|
|
||||||
|
def end_html():
|
||||||
|
print('</body></html>')
|
||||||
|
|
||||||
|
|
||||||
|
def safe_join(*args):
|
||||||
|
safe = list(args[:1])
|
||||||
|
for a in args[1:]:
|
||||||
|
if not a:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
a = a.replace('..', '')
|
||||||
|
a = a.replace('/', '')
|
||||||
|
safe.append(a)
|
||||||
|
ret = '/'.join(safe)
|
||||||
|
if os.path.exists(ret):
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def dump_file(fn):
|
||||||
|
f = open(fn, 'rb')
|
||||||
|
while True:
|
||||||
|
d = f.read(4096)
|
||||||
|
if not d:
|
||||||
|
break
|
||||||
|
sys.stdout.buffer.write(d)
|
||||||
|
|
||||||
|
def show_cats():
|
||||||
|
start_html('Categories')
|
||||||
|
print('<ul>')
|
||||||
|
for p in sorted(os.listdir(puzzles_dir)):
|
||||||
|
if config.disabled(p):
|
||||||
|
continue
|
||||||
|
print('<li><a href="%s?c=%s">%s</a></li>' % (cgi_url, p, p))
|
||||||
|
print('</ul>')
|
||||||
|
end_html()
|
||||||
|
|
||||||
|
|
||||||
|
def show_puzzles(cat, cat_dir):
|
||||||
|
start_html('Open in %s' % cat)
|
||||||
|
opened = points_by_cat.get(cat, 0)
|
||||||
|
puzzles = sorted([int(v) for v in os.listdir(cat_dir)])
|
||||||
|
if puzzles:
|
||||||
|
print('<ul>')
|
||||||
|
for p in puzzles:
|
||||||
|
print('<li><a href="%s/%s/%d">%d</a></li>' % (base_url, cat, p, p))
|
||||||
|
if p > opened:
|
||||||
|
break
|
||||||
|
print('</ul>')
|
||||||
|
else:
|
||||||
|
print('<p>None (someone is slacking)</p>')
|
||||||
|
end_html()
|
||||||
|
|
||||||
|
def win(cat, team, points):
|
||||||
|
start_html('Winner!')
|
||||||
|
points = int(points)
|
||||||
|
f = open(datafile, 'a', encoding='utf-8')
|
||||||
|
pointscli.submit(cat, team, points)
|
||||||
|
fcntl.lockf(f, fcntl.LOCK_EX)
|
||||||
|
f.write('%s\t%s\t%d\n' % (quote(cat), quote(team), points))
|
||||||
|
print('<p>%d points for %s.</p>' % (points, team))
|
||||||
|
print('<p>Back to <a href="%s?c=%s">%s</a>.</p>' % (cgi_url, cat, cat))
|
||||||
|
end_html()
|
||||||
|
|
||||||
|
def get_key(cat, points):
|
||||||
|
for line in open(keysfile, encoding='utf-8'):
|
||||||
|
thiscat, thispoints, ret = line.split('\t', 2)
|
||||||
|
if (cat, points) == (thiscat, thispoints):
|
||||||
|
return ret.strip()
|
||||||
|
return False
|
||||||
|
|
||||||
|
def main():
|
||||||
|
cat_dir = safe_join(puzzles_dir, cat)
|
||||||
|
points_dir = safe_join(puzzles_dir, cat, points)
|
||||||
|
|
||||||
|
if not cat_dir:
|
||||||
|
# Show categories
|
||||||
|
show_cats()
|
||||||
|
elif not points_dir:
|
||||||
|
# Show available puzzles in category
|
||||||
|
show_puzzles(cat, cat_dir)
|
||||||
|
else:
|
||||||
|
thekey = get_key(cat, points)
|
||||||
|
if not teams.chkpasswd(team, passwd):
|
||||||
|
start_html('Wrong password')
|
||||||
|
end_html()
|
||||||
|
elif key != thekey:
|
||||||
|
start_html('Wrong key')
|
||||||
|
end_html()
|
||||||
|
elif int(points) in points_by_team.get((team, cat), set()):
|
||||||
|
start_html('Greedy greedy')
|
||||||
|
end_html()
|
||||||
|
else:
|
||||||
|
win(cat, team, points)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import optparse
|
||||||
|
|
||||||
|
parser = optparse.OptionParser('%prog CATEGORY POINTS')
|
||||||
|
opts, args = parser.parse_args()
|
||||||
|
|
||||||
|
if len(args) == 2:
|
||||||
|
cat, points = args
|
||||||
|
show_puzzle(cat, points)
|
||||||
|
else:
|
||||||
|
parser.print_usage()
|
||||||
|
|
||||||
|
|
||||||
|
# Local Variables:
|
||||||
|
# mode: python
|
||||||
|
# End:
|
Before Width: | Height: | Size: 87 B After Width: | Height: | Size: 87 B |
64
register.cgi
64
register.cgi
|
@ -1,62 +1,6 @@
|
||||||
#! /usr/bin/env python3
|
#! /usr/bin/env python3
|
||||||
|
|
||||||
import cgitb; cgitb.enable()
|
import sys
|
||||||
import cgi
|
sys.path.insert(0, '/usr/lib/ctf')
|
||||||
import teams
|
import register
|
||||||
import fcntl
|
register.main()
|
||||||
import string
|
|
||||||
|
|
||||||
print('Content-type: text/html')
|
|
||||||
print()
|
|
||||||
|
|
||||||
f = cgi.FieldStorage()
|
|
||||||
|
|
||||||
team = f.getfirst('team', '')
|
|
||||||
pw = f.getfirst('pw')
|
|
||||||
confirm_pw = f.getfirst('confirm_pw')
|
|
||||||
|
|
||||||
html = string.Template('''<?xml version="1.0" encoding="UTF-8" ?>
|
|
||||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
|
||||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
|
||||||
<head>
|
|
||||||
<title>Team Registration</title>
|
|
||||||
<link rel="stylesheet" href="ctf.css" type="text/css" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Team Registration</h1>
|
|
||||||
|
|
||||||
<form method="post" action="register.cgi">
|
|
||||||
<fieldset>
|
|
||||||
<label>Desired Team Team:</label>
|
|
||||||
<input type="text" name="team" />
|
|
||||||
<span class="error">$team_error</span><br />
|
|
||||||
|
|
||||||
<label>Password:</label>
|
|
||||||
<input type="password" name="pw" /> <br />
|
|
||||||
|
|
||||||
<label>Confirm Password:</label>
|
|
||||||
<input type="password" name="confirm_pw" />
|
|
||||||
<span class="error">$pw_match_error</span><br />
|
|
||||||
|
|
||||||
<input type="submit" value="Register" />
|
|
||||||
</fieldset>
|
|
||||||
</form>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
''')
|
|
||||||
|
|
||||||
if not (team and pw and confirm_pw): #If we're starting from the beginning?
|
|
||||||
html = html.substitute(team_error='',
|
|
||||||
pw_match_error='')
|
|
||||||
elif teams.exists(team):
|
|
||||||
html = html.substitute(team_error='Team team already taken',
|
|
||||||
pw_match_error='')
|
|
||||||
elif pw != confirm_pw:
|
|
||||||
html = html.substitute(team_error='',
|
|
||||||
pw_match_error='Passwords do not match')
|
|
||||||
else:
|
|
||||||
teams.add(team, pw)
|
|
||||||
html = 'Team registered.'
|
|
||||||
|
|
||||||
print(html)
|
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
#! /usr/bin/env python3
|
||||||
|
|
||||||
|
import cgitb; cgitb.enable()
|
||||||
|
import cgi
|
||||||
|
import teams
|
||||||
|
import fcntl
|
||||||
|
import string
|
||||||
|
import config
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print('Content-type: text/html')
|
||||||
|
print()
|
||||||
|
|
||||||
|
f = cgi.FieldStorage()
|
||||||
|
|
||||||
|
team = f.getfirst('team', '')
|
||||||
|
pw = f.getfirst('pw')
|
||||||
|
confirm_pw = f.getfirst('confirm_pw')
|
||||||
|
|
||||||
|
html = string.Template('''<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||||
|
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||||
|
<head>
|
||||||
|
<title>Team Registration</title>
|
||||||
|
<link rel="stylesheet" href="%s" type="text/css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Team Registration</h1>
|
||||||
|
|
||||||
|
<form method="post" action="%s">
|
||||||
|
<fieldset>
|
||||||
|
<label>Desired Team Team:</label>
|
||||||
|
<input type="text" name="team" />
|
||||||
|
<span class="error">$team_error</span><br />
|
||||||
|
|
||||||
|
<label>Password:</label>
|
||||||
|
<input type="password" name="pw" />
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<label>Confirm Password:</label>
|
||||||
|
<input type="password" name="confirm_pw" />
|
||||||
|
<span class="error">$pw_match_error</span><br />
|
||||||
|
|
||||||
|
<input type="submit" value="Register" />
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
''' % (config.css, config.url('register.cgi')))
|
||||||
|
|
||||||
|
if not (team and pw and confirm_pw): # If we're starting from the beginning?
|
||||||
|
html = html.substitute(team_error='',
|
||||||
|
pw_match_error='')
|
||||||
|
elif teams.exists(team):
|
||||||
|
html = html.substitute(team_error='Team team already taken',
|
||||||
|
pw_match_error='')
|
||||||
|
elif pw != confirm_pw:
|
||||||
|
html = html.substitute(team_error='',
|
||||||
|
pw_match_error='Passwords do not match')
|
||||||
|
else:
|
||||||
|
teams.add(team, pw)
|
||||||
|
html = 'Team registered.'
|
||||||
|
|
||||||
|
print(html)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
|
@ -0,0 +1,3 @@
|
||||||
|
#! /bin/sh
|
||||||
|
|
||||||
|
/usr/lib/ctf/uberserv.py | logger -t ctf
|
|
@ -1,51 +1,6 @@
|
||||||
#!/usr/bin/env python3
|
#! /usr/bin/env python3
|
||||||
|
|
||||||
import cgitb; cgitb.enable()
|
import sys
|
||||||
import points
|
sys.path.insert(0, '/usr/lib/ctf')
|
||||||
|
import scoreboard
|
||||||
s = points.Storage('scores.dat')
|
scoreboard.main()
|
||||||
|
|
||||||
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('''<?xml version="1.0" encoding="UTF-8" ?>
|
|
||||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
|
||||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
|
||||||
<head>
|
|
||||||
<title>CTF Scoreboard</title>
|
|
||||||
<link rel="stylesheet" href="ctf.css" type="text/css" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<h1>Scoreboard</h1>
|
|
||||||
''')
|
|
||||||
print('<table>')
|
|
||||||
print('<tr>')
|
|
||||||
for cat, points in categories:
|
|
||||||
print('<th>%s (%d)</th>' % (cat, points))
|
|
||||||
print('</tr>')
|
|
||||||
|
|
||||||
print('<tr>')
|
|
||||||
for cat, total in categories:
|
|
||||||
print('<td style="height: 400px;">')
|
|
||||||
scores = sorted([(s.team_points_in_cat(cat, team), team) for team in teams])
|
|
||||||
for points, team in scores:
|
|
||||||
color = teamcolors[team]
|
|
||||||
print('<div style="height: %f%%; overflow: hidden; background: #%s; color: black;">' % (float(points * 100)/total, color))
|
|
||||||
print('<!-- category: %s --> %s: %d' % (cat, team, points))
|
|
||||||
print('</div>')
|
|
||||||
print('</td>')
|
|
||||||
print('</tr>')
|
|
||||||
print('''</table>
|
|
||||||
|
|
||||||
<img src="histogram.png" alt=""/>
|
|
||||||
</body>
|
|
||||||
</html>''')
|
|
||||||
|
|
||||||
# Local Variables:
|
|
||||||
# mode: python
|
|
||||||
# End:
|
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import cgitb; cgitb.enable()
|
||||||
|
import config
|
||||||
|
import points
|
||||||
|
|
||||||
|
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('''<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||||
|
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||||
|
<head>
|
||||||
|
<title>CTF Scoreboard</title>
|
||||||
|
<link rel="stylesheet" href="%sctf.css" type="text/css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Scoreboard</h1>
|
||||||
|
''' % config.base_url)
|
||||||
|
print('<table>')
|
||||||
|
print('<tr>')
|
||||||
|
for cat, score in categories:
|
||||||
|
print('<th>%s (%d)</th>' % (cat, score))
|
||||||
|
print('</tr>')
|
||||||
|
|
||||||
|
print('<tr>')
|
||||||
|
for cat, total in categories:
|
||||||
|
print('<td style="height: 400px;">')
|
||||||
|
scores = sorted([(s.team_points_in_cat(cat, team), team) for team in teams])
|
||||||
|
for score, team in scores:
|
||||||
|
color = teamcolors[team]
|
||||||
|
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('</div>')
|
||||||
|
print('</td>')
|
||||||
|
print('</tr>')
|
||||||
|
print('''</table>
|
||||||
|
|
||||||
|
<img src="histogram.png" alt=""/>
|
||||||
|
</body>
|
||||||
|
</html>''')
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
|
||||||
|
# Local Variables:
|
||||||
|
# mode: python
|
||||||
|
# End:
|
9
teams.py
9
teams.py
|
@ -9,13 +9,14 @@ house = 'dirtbags'
|
||||||
|
|
||||||
passwdfn = '/var/lib/ctf/passwd'
|
passwdfn = '/var/lib/ctf/passwd'
|
||||||
|
|
||||||
teams = None
|
teams = {}
|
||||||
built = 0
|
built = 0
|
||||||
def build_teams():
|
def build_teams():
|
||||||
global teams, built
|
global teams, built
|
||||||
|
|
||||||
modt = os.path.getmtime(passwdfn)
|
if not os.path.exists(passwdfn):
|
||||||
if modt <= built:
|
return
|
||||||
|
if os.path.getmtime(passwdfn) <= built:
|
||||||
return
|
return
|
||||||
|
|
||||||
teams = {}
|
teams = {}
|
||||||
|
@ -46,7 +47,7 @@ def exists(team):
|
||||||
return team in teams
|
return team in teams
|
||||||
|
|
||||||
def add(team, passwd):
|
def add(team, passwd):
|
||||||
f = open('passwd', 'a')
|
f = open(passwdfn, 'a')
|
||||||
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)))
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
import asyncore
|
import asyncore
|
||||||
import pointsd
|
import pointsd
|
||||||
import roshambo
|
|
||||||
import game
|
import game
|
||||||
import flagd
|
import flagd
|
||||||
import histogram
|
import histogram
|
||||||
|
@ -10,7 +9,11 @@ import histogram
|
||||||
def main():
|
def main():
|
||||||
pointsrv = pointsd.start()
|
pointsrv = pointsd.start()
|
||||||
flagsrv = flagd.start()
|
flagsrv = flagd.start()
|
||||||
|
|
||||||
|
if config.enabled('roshambo'):
|
||||||
|
import roshambo
|
||||||
roshambosrv = roshambo.start()
|
roshambosrv = roshambo.start()
|
||||||
|
|
||||||
s = pointsrv.store
|
s = pointsrv.store
|
||||||
slen = 0
|
slen = 0
|
||||||
while True:
|
while True:
|
Loading…
Reference in New Issue