Skip to content

Commit 4950bf3

Browse files
committed
Initial commit
1 parent b7be4be commit 4950bf3

27 files changed

+896
-0
lines changed

config.json

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"secret_key": "PLEASE_CHANGE_ME",
3+
4+
"host": "0.0.0.0",
5+
"port": 8888,
6+
7+
"db": "sqlite:///ctf.db",
8+
9+
"language_file": "lang.json",
10+
"language": "english",
11+
12+
"debug": false
13+
}

lang.json

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
{
2+
"english": {
3+
"frame": {
4+
"title": "tinyCTF",
5+
"toggle": "Toggle navigation",
6+
"login": "Sign in",
7+
"register": "Register",
8+
"logged_in": "Logged in as",
9+
"logout": "log out"
10+
},
11+
"main": {
12+
"title": "Hello World!",
13+
"text": "Welcome text goes here"
14+
},
15+
"about": {
16+
"title": "About",
17+
"news": "News",
18+
"news_text": "News go here",
19+
"rules": "Rules",
20+
"rules_text": "Rules go here"
21+
},
22+
"tasks": {
23+
"title": "Tasks"
24+
},
25+
"task": {
26+
"description": "Description",
27+
"attachment": "Attachment",
28+
"submit": "Submit",
29+
"success": "Correct flag!",
30+
"failure": "Incorrect flag",
31+
"solution_format": "solved by %d",
32+
"no_description": "(none)",
33+
"placeholder": "flag{insert_flag_here}"
34+
},
35+
"scoreboard": {
36+
"title": "Scoreboard",
37+
"player": "Player",
38+
"score": "Score"
39+
},
40+
"error": {
41+
"title": "Error",
42+
"unknown": "Unknown error",
43+
"login_required": "You need to be logged in to see this page",
44+
"invalid_credentials": "Invalid username or password",
45+
"already_registered": "This user is already registered",
46+
"empty_user": "Empty username is not allowed",
47+
"task_not_found": "TBD"
48+
}
49+
}
50+
}

server.py

+308
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
#!/usr/bin/env python
2+
3+
"""server.py -- the main flask server module"""
4+
5+
import dataset
6+
import json
7+
import random
8+
import time
9+
10+
from base64 import b64decode
11+
from functools import wraps
12+
13+
from flask import Flask
14+
from flask import jsonify
15+
from flask import make_response
16+
from flask import redirect
17+
from flask import render_template
18+
from flask import request
19+
from flask import session
20+
from flask import url_for
21+
22+
app = Flask(__name__, static_folder='static', static_url_path='')
23+
24+
db = None
25+
lang = None
26+
config = None
27+
28+
def login_required(f):
29+
"""Ensures that an user is logged in"""
30+
31+
@wraps(f)
32+
def decorated_function(*args, **kwargs):
33+
if 'user_id' not in session:
34+
return redirect(url_for('error', msg='login_required'))
35+
return f(*args, **kwargs)
36+
return decorated_function
37+
38+
def get_user():
39+
"""Looks up the current user in the database"""
40+
41+
login = 'user_id' in session
42+
if login:
43+
return (True, db['users'].find_one(id=session['user_id']))
44+
45+
return (False, None)
46+
47+
def get_task(category, score):
48+
"""Finds a task with a given category and score"""
49+
50+
task = db.query('''select t.* from tasks t, categories c, cat_task ct
51+
where t.id = ct.task_id and c.id = ct.cat_id
52+
and t.score=:score and lower(c.short_name)=:cat''',
53+
score=score, cat=category)
54+
return list(task)[0]
55+
56+
def get_flags():
57+
"""Returns the flags of the current user"""
58+
59+
flags = db.query('''select f.task_id from flags f
60+
where f.user_id = :user_id''',
61+
user_id=session['user_id'])
62+
return [f['task_id'] for f in list(flags)]
63+
64+
@app.route('/error/<msg>')
65+
def error(msg):
66+
"""Displays an error message"""
67+
68+
if msg in lang:
69+
message = lang['error'][msg]
70+
else:
71+
message = lang['error']['unknown']
72+
73+
login, user = get_user()
74+
75+
render = render_template('frame.html', lang=lang, page='error.html',
76+
message=message, login=login, user=user)
77+
return make_response(render)
78+
79+
def session_login(username):
80+
"""Initializes the session with the current user's id"""
81+
user = db['users'].find_one(username=username)
82+
session['user_id'] = user['id']
83+
84+
@app.route('/login', methods = ['POST'])
85+
def login():
86+
"""Attempts to log the user in"""
87+
88+
from werkzeug.security import check_password_hash
89+
90+
username = request.form['user']
91+
password = request.form['password']
92+
93+
user = db['users'].find_one(username=username)
94+
if user is None:
95+
return redirect('/error/invalid_credentials')
96+
97+
if check_password_hash(user['password'], password):
98+
session_login(username)
99+
return redirect('/tasks')
100+
101+
return redirect('/error/invalid_credentials')
102+
103+
@app.route('/register')
104+
def register():
105+
"""Displays the register form"""
106+
107+
# Render template
108+
render = render_template('frame.html', lang=lang,
109+
page='register.html', login=False)
110+
return make_response(render)
111+
112+
@app.route('/register/submit', methods = ['POST'])
113+
def register_submit():
114+
"""Attempts to register a new user"""
115+
116+
from werkzeug.security import generate_password_hash
117+
118+
username = request.form['user']
119+
password = request.form['password']
120+
121+
if not username:
122+
return redirect('/error/empty_user')
123+
124+
user_found = db['users'].find_one(username=username)
125+
if user_found:
126+
return redirect('/error/already_registered')
127+
128+
new_user = dict(hidden=0, username=username,
129+
password=generate_password_hash(password))
130+
db['users'].insert(new_user)
131+
132+
# Set up the user id for this session
133+
session_login(username)
134+
135+
return redirect('/tasks')
136+
137+
@app.route('/tasks')
138+
@login_required
139+
def tasks():
140+
"""Displays all the tasks in a grid"""
141+
142+
login, user = get_user()
143+
flags = get_flags()
144+
145+
categories = db['categories']
146+
147+
tasks = db.query('''select c.id as cat_id, t.id as id, c.short_name,
148+
t.score, t.row from categories c, tasks t, cat_task c_t
149+
where c.id = c_t.cat_id and t.id = c_t.task_id''')
150+
tasks = list(tasks)
151+
152+
grid = []
153+
# Find the max row number
154+
max_row = max(t['row'] for t in tasks)
155+
156+
for row in range(max_row + 1):
157+
158+
row_tasks = []
159+
for cat in categories:
160+
161+
# Find the task with the correct row
162+
for task in tasks:
163+
if task['row'] == row and task['cat_id'] == cat['id']:
164+
break
165+
else:
166+
task = None
167+
168+
row_tasks.append(task)
169+
170+
grid.append(row_tasks)
171+
172+
# Render template
173+
render = render_template('frame.html', lang=lang, page='tasks.html',
174+
login=login, user=user, categories=categories, grid=grid,
175+
flags=flags)
176+
return make_response(render)
177+
178+
@app.route('/tasks/<category>/<score>')
179+
@login_required
180+
def task(category, score):
181+
"""Displays a task with a given category and score"""
182+
183+
login, user = get_user()
184+
185+
task = get_task(category, score)
186+
if not task:
187+
return redirect('/error/task_not_found')
188+
189+
flags = get_flags()
190+
task_done = task['id'] in flags
191+
192+
solutions = db['flags'].find(task_id=task['id'])
193+
solutions = len(list(solutions))
194+
195+
# Render template
196+
render = render_template('frame.html', lang=lang, page='task.html',
197+
task_done=task_done, login=login, solutions=solutions,
198+
user=user, category=category, task=task, score=score)
199+
return make_response(render)
200+
201+
@app.route('/submit/<category>/<score>/<flag>')
202+
@login_required
203+
def submit(category, score, flag):
204+
"""Handles the submission of flags"""
205+
206+
print "ok"
207+
208+
login, user = get_user()
209+
210+
task = get_task(category, score)
211+
flags = get_flags()
212+
task_done = task['id'] in flags
213+
214+
result = {'success': False}
215+
if not task_done and task['flag'] == b64decode(flag):
216+
217+
timestamp = int(time.time() * 1000)
218+
219+
# Insert flag
220+
new_flag = dict(task_id=task['id'], user_id=session['user_id'],
221+
score=score, timestamp=timestamp)
222+
db['flags'].insert(new_flag)
223+
224+
result['success'] = True
225+
226+
return jsonify(result)
227+
228+
@app.route('/scoreboard')
229+
@login_required
230+
def scoreboard():
231+
"""Displays the scoreboard"""
232+
233+
login, user = get_user()
234+
scores = db.query('''select u.username, ifnull(sum(f.score), 0) as score,
235+
max(timestamp) as last_submit from users u left join flags f
236+
on u.id = f.user_id where u.hidden = 0 group by u.username
237+
order by score desc, last_submit asc''')
238+
239+
scores = list(scores)
240+
241+
# Render template
242+
render = render_template('frame.html', lang=lang, page='scoreboard.html',
243+
login=login, user=user, scores=scores)
244+
return make_response(render)
245+
246+
@app.route('/about')
247+
@login_required
248+
def about():
249+
"""Displays the about menu"""
250+
251+
login, user = get_user()
252+
253+
# Render template
254+
render = render_template('frame.html', lang=lang, page='about.html',
255+
login=login, user=user)
256+
return make_response(render)
257+
258+
@app.route('/logout')
259+
@login_required
260+
def logout():
261+
"""Logs the current user out"""
262+
263+
del session['user_id']
264+
return redirect('/')
265+
266+
@app.route('/')
267+
def index():
268+
"""Displays the main page"""
269+
270+
login, user = get_user()
271+
272+
# Render template
273+
render = render_template('frame.html', lang=lang,
274+
page='main.html', login=login, user=user)
275+
return make_response(render)
276+
277+
if __name__ == '__main__':
278+
"""Initializes the database and sets up the language"""
279+
280+
# Load config
281+
config_str = open('config.json', 'rb').read()
282+
config = json.loads(config_str)
283+
284+
app.secret_key = config['secret_key']
285+
286+
# Load language
287+
lang_str = open(config['language_file'], 'rb').read()
288+
lang = json.loads(lang_str)
289+
290+
# Only a single language is supported for now
291+
lang = lang[config['language']]
292+
293+
# Connect to database
294+
db = dataset.connect(config['db'])
295+
296+
# Setup the flags table at first execution
297+
if 'flags' not in db.tables:
298+
db.query('''create table flags (
299+
task_id INTEGER,
300+
user_id INTEGER,
301+
score INTEGER,
302+
timestamp BIGINT,
303+
PRIMARY KEY (task_id, user_id))''')
304+
305+
# Start web server
306+
app.run(host=config['host'], port=config['port'],
307+
debug=config['debug'], threaded=True)
308+

static/css/bootstrap.min.css

+9
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)