├── .gitignore ├── LICENSES.txt ├── README.md ├── collabCTF ├── __init__.py ├── context_processors.py ├── local_settings.template.py ├── settings.py ├── urls.py ├── views.py └── wsgi.py ├── competition ├── __init__.py ├── admin.py ├── ajax.py ├── forms.py ├── migrations │ ├── 0001_initial.py │ └── __init__.py ├── models.py ├── static │ ├── clocks │ │ ├── blue.png │ │ ├── green.png │ │ ├── orange.png │ │ └── pink.png │ ├── css │ │ ├── foundation.min.css │ │ ├── normalize.css │ │ └── style.css │ ├── dashboard.css │ ├── favicon.ico │ ├── fonts │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.svg │ │ ├── glyphicons-halflings-regular.ttf │ │ └── glyphicons-halflings-regular.woff │ ├── holder.js │ ├── images │ │ ├── doge2.png │ │ └── fire doge.png │ └── js │ │ ├── ctf_overview.js │ │ ├── ctftools.js │ │ ├── lib │ │ ├── fastclick.js │ │ ├── foundation.min.js │ │ ├── jquery.cookie.js │ │ ├── modernizr.js │ │ └── placeholder.js │ │ ├── metr.js │ │ └── site.js ├── templates │ ├── 404.html │ ├── 40x-50x.html │ ├── 500.html │ ├── base.html │ ├── breadcrumbs.html │ ├── ctf │ │ ├── add.html │ │ ├── challenge │ │ │ ├── add.html │ │ │ ├── deleted.html │ │ │ ├── files │ │ │ │ └── add.html │ │ │ ├── overview.html │ │ │ └── update.html │ │ ├── overview.html │ │ ├── removed.html │ │ └── update.html │ ├── index.html │ ├── login.html │ ├── profile.html │ ├── register.html │ ├── registration_locked.html │ ├── reports.html │ ├── settings.html │ ├── sidebar.html │ └── tools.html ├── tests.py └── views.py ├── manage.py ├── requirements.txt └── tools ├── __init__.py ├── ajax.py ├── crypto.py ├── forms.py ├── misc.py └── views.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | 21 | # Installer logs 22 | pip-log.txt 23 | 24 | # Unit test / coverage reports 25 | .coverage 26 | .tox 27 | nosetests.xml 28 | 29 | # Translations 30 | *.mo 31 | 32 | # Mr Developer 33 | .mr.developer.cfg 34 | .project 35 | .pydevproject 36 | .idea 37 | 38 | # project-specific 39 | /collabCTF/local_settings.py 40 | /db.sqlite3 -------------------------------------------------------------------------------- /LICENSES.txt: -------------------------------------------------------------------------------- 1 | // collabCTF.tools.misc.UTC from pytz under the MIT license 2 | 3 | Copyright (c) 2003-2005 Stuart Bishop 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a 6 | copy of this software and associated documentation files (the "Software"), 7 | to deal in the Software without restriction, including without limitation 8 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | and/or sell copies of the Software, and to permit persons to whom the 10 | Software is furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 18 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | collabCTF 2 | ========= 3 | 4 | collabCTF is a collaborative CTF framework written in [Django](https://www.djangoproject.com/), originally created as a final project for the COP 4331 course at the [University of Central Florida](http://www.ucf.edu/) by a few members of [Hack@UCF](https://hackucf.org/blog/). 5 | 6 | 7 | Requirements 8 | ------------ 9 | collabCTF has only been deployed with [Python](https://www.python.org/) versions **2.7.x** and **3.3.x** on Linux. 10 | 11 | Required packages: 12 | 13 | * [South](http://south.readthedocs.org/en/latest/) 14 | * [django-crispy-forms](https://github.com/maraujop/django-crispy-forms/) 15 | * [crispy-forms-foundation](https://github.com/sveetch/crispy-forms-foundation) 16 | 17 | -------------------------------------------------------------------------------- /collabCTF/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackUCF/collabCTF/463f4e5a303b5eb6ae6b5d4aab916e02a3bb2ae0/collabCTF/__init__.py -------------------------------------------------------------------------------- /collabCTF/context_processors.py: -------------------------------------------------------------------------------- 1 | from competition.models import Competition 2 | 3 | 4 | def ctf_sidebar(request): 5 | context = { 6 | 'sidebar': { 7 | 'ctfs': Competition.objects.only('name', 'slug') 8 | } 9 | } 10 | 11 | return context -------------------------------------------------------------------------------- /collabCTF/local_settings.template.py: -------------------------------------------------------------------------------- 1 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 2 | import os 3 | BASE_DIR = os.path.dirname(os.path.dirname(__file__)) 4 | 5 | TIME_ZONE = 'America/New_York' 6 | 7 | # Database 8 | # https://docs.djangoproject.com/en/1.6/ref/settings/#databases 9 | DATABASES = { 10 | 'default': { 11 | 'ENGINE': 'django.db.backends.sqlite3', 12 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 13 | } 14 | } 15 | 16 | # pretty important things 17 | ALLOWED_HOSTS = ['127.0.0.1', 'localhost'] 18 | REGISTRATION_LOCK = False 19 | 20 | # SECURITY WARNING: keep the secret key used in production secret! 21 | SECRET_KEY = 'ohyou' 22 | 23 | # SECURITY WARNING: don't run with debug turned on in production! 24 | DEBUG = True 25 | TEMPLATE_DEBUG = DEBUG -------------------------------------------------------------------------------- /collabCTF/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for collabCTF project. 3 | 4 | For more information on this file, see 5 | https://docs.djangoproject.com/en/1.6/topics/settings/ 6 | 7 | For the full list of settings and their values, see 8 | https://docs.djangoproject.com/en/1.6/ref/settings/ 9 | """ 10 | 11 | # Quick-start development settings - unsuitable for production 12 | # See https://docs.djangoproject.com/en/1.6/howto/deployment/checklist/ 13 | 14 | ALLOWED_HOSTS = ['localhost'] 15 | 16 | 17 | # Application definition 18 | 19 | INSTALLED_APPS = ( 20 | 'django.contrib.admin', 21 | 'django.contrib.auth', 22 | 'django.contrib.contenttypes', 23 | 'django.contrib.sessions', 24 | 'django.contrib.messages', 25 | 'django.contrib.staticfiles', 26 | 'crispy_forms', 27 | 'crispy_forms_foundation', 28 | 'competition', 29 | 'tools' 30 | ) 31 | 32 | MIDDLEWARE_CLASSES = ( 33 | 'django.contrib.sessions.middleware.SessionMiddleware', 34 | 'django.middleware.common.CommonMiddleware', 35 | 'django.middleware.csrf.CsrfViewMiddleware', 36 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 37 | 'django.contrib.messages.middleware.MessageMiddleware', 38 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 39 | ) 40 | 41 | TEMPLATE_LOADERS = ( 42 | 'django.template.loaders.filesystem.Loader', 43 | 'django.template.loaders.app_directories.Loader' 44 | ) 45 | 46 | TEMPLATE_CONTEXT_PROCESSORS = ( 47 | "django.contrib.auth.context_processors.auth", 48 | "django.core.context_processors.debug", 49 | "django.core.context_processors.i18n", 50 | "django.core.context_processors.media", 51 | "django.core.context_processors.static", 52 | "django.core.context_processors.tz", 53 | "django.core.context_processors.request", 54 | "django.contrib.messages.context_processors.messages", 55 | "collabCTF.context_processors.ctf_sidebar" 56 | ) 57 | 58 | ROOT_URLCONF = 'collabCTF.urls' 59 | 60 | WSGI_APPLICATION = 'collabCTF.wsgi.application' 61 | 62 | # Internationalization 63 | # https://docs.djangoproject.com/en/1.6/topics/i18n/ 64 | 65 | LANGUAGE_CODE = 'en-us' 66 | 67 | USE_I18N = True 68 | 69 | USE_L10N = True 70 | 71 | USE_TZ = True 72 | 73 | # Static files (CSS, JavaScript, Images) 74 | # https://docs.djangoproject.com/en/1.6/howto/static-files/ 75 | 76 | STATIC_URL = '/static/' 77 | 78 | # Theme 79 | CRISPY_TEMPLATE_PACK = 'foundation-5' 80 | 81 | LOGIN_URL = '/login' 82 | MEDIA_URL = '/media/' 83 | 84 | try: 85 | from collabCTF.local_settings import * 86 | except ImportError: 87 | from django.core.exceptions import ImproperlyConfigured 88 | 89 | raise ImproperlyConfigured('local_settings.py not found') 90 | -------------------------------------------------------------------------------- /collabCTF/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf.urls import patterns, include, url, static 2 | from django.conf import settings 3 | from django.contrib import admin 4 | 5 | admin.autodiscover() 6 | 7 | urlpatterns = patterns('', 8 | # Examples: 9 | # url(r'^$', 'collabCTF.views.home', name='home'), 10 | # url(r'^blog/', include('blog.urls')), 11 | 12 | url(r'^$', 'collabCTF.views.index', name='index'), 13 | url(r'^admin/', include(admin.site.urls)), 14 | url(r'^settings$', 'collabCTF.views.user_settings', name='settings'), 15 | url(r'^profile$', 'collabCTF.views.profile', name='profile'), 16 | url(r'^login$', 'collabCTF.views.log_in', name='login'), 17 | url(r'^logout$', 'django.contrib.auth.views.logout', {'next_page': "/login"}, name='logout'), 18 | url(r'^register$', 'collabCTF.views.register', name='register'), 19 | url(r'^reports$', 'collabCTF.views.reports', name='reports'), 20 | url(r'^ctf-tools$', 'tools.views.ctf_tools', name='ctf_tools'), 21 | url(r'^ctf/add$', 'competition.views.add_ctf', name='add_ctf'), 22 | url(r'^ctf/(?P[a-z\d_\-]+)/$', 'competition.views.view_ctf', name='view_ctf'), 23 | url(r'^ctf/(?P[a-z\d_\-]+)/update$', 'competition.views.update_ctf', name='update_ctf'), 24 | url(r'^ctf/(?P[a-z\d_\-]+)/delete$', 'competition.views.delete_ctf', name='delete_ctf'), 25 | url(r'^ctf/(?P[a-z\d_\-]+)/add$', 'competition.views.add_challenge', name='add_challenge'), 26 | url(r'^ctf/(?P[a-z\d_\-]+)/(?P[a-z\d_\-]+)/$', 'competition.views.view_challenge', 27 | name='view_challenge'), 28 | url(r'^ctf/(?P[a-z\d_\-]+)/(?P[a-z\d_\-]+)/update$', 'competition.views.update_challenge', 29 | name='update_challenge'), 30 | url(r'^ctf/(?P[a-z\d_\-]+)/(?P[a-z\d_\-]+)/delete$', 'competition.views.delete_challenge', 31 | name='delete_challenge'), 32 | url(r'ctf/(?P[a-z\d_\-]+)/(?P[a-z\d_\-]+)/add', 'competition.views.add_file', 33 | name='add_file'), 34 | 35 | # ajax 36 | url(r'^ctf/(?P[a-z\d_\-]+)/.chart$', 'competition.ajax.chart_data', name='ctf_chart'), 37 | url(r'^tools/.hash$', 'tools.ajax.hash_val', name='tools_hash'), 38 | url(r'^tools/.rot$', 'tools.ajax.rot_val', name='tools_rot'), 39 | url(r'^tools/.base_conversions$', 'tools.ajax.base_conversion_val', name='tools_base_conversion'), 40 | url(r'^tools/.xor$', 'tools.ajax.xor_val', name='tools_xor'), 41 | url(r'^tools/.url-quote$', 'tools.ajax.quote_url', name='tools_quote'), 42 | url(r'^tools/.url-unquote$', 'tools.ajax.unquote_url', name='tools_unquote'), 43 | url(r'^.challenge-visit$', 'competition.ajax.track_challenge_visit', name='track_challenge_visit'), 44 | ) 45 | 46 | if settings.DEBUG: 47 | if 'MEDIA_ROOT' in dir(settings): 48 | media_root = settings.MEDIA_ROOT 49 | else: 50 | media_root = 'files' 51 | urlpatterns += static.static(settings.MEDIA_URL, document_root=media_root) 52 | -------------------------------------------------------------------------------- /collabCTF/views.py: -------------------------------------------------------------------------------- 1 | import json 2 | import sys 3 | 4 | from django.contrib.auth import authenticate, login 5 | from django.contrib.auth.decorators import login_required 6 | from django.core.urlresolvers import reverse 7 | from django.http import HttpResponse, HttpResponseBadRequest 8 | from django.shortcuts import render_to_response, redirect 9 | from django.template import RequestContext 10 | from django.views.decorators.http import require_safe, require_POST, require_http_methods 11 | from django.conf import settings 12 | 13 | from competition.forms import RegistrationForm, LoginForm, \ 14 | PasswordChangeForm, EmailChangeForm 15 | from competition.models import Competition, Challenge 16 | 17 | 18 | @login_required 19 | def index(request): 20 | recently_viewed = Challenge.objects.order_by('-last_viewed') 21 | recently_solved = Challenge.objects.filter(progress=Challenge.SOLVED) 22 | data = { 23 | 'recently_solved': recently_solved[:5], 24 | 'recently_viewed': recently_viewed[:5] 25 | } 26 | return render_to_response('index.html', data, RequestContext(request)) 27 | 28 | 29 | @login_required 30 | def profile(request): 31 | return render_to_response('profile.html', context_instance=RequestContext(request)) 32 | 33 | 34 | @login_required 35 | def user_settings(request): 36 | if request.method == 'GET': 37 | data = { 38 | 'password_form': PasswordChangeForm(request.user), 39 | 'email_form': EmailChangeForm() 40 | } 41 | 42 | return render_to_response('settings.html', data, context_instance=RequestContext(request)) 43 | else: 44 | password_changed = False 45 | password_form = PasswordChangeForm(request.user, data=request.POST) 46 | if password_form.is_valid(): 47 | cd = password_form.cleaned_data 48 | password_form.save() 49 | password_changed = True 50 | password_form = PasswordChangeForm(request.user) 51 | 52 | data = { 53 | 'password_form': password_form, 54 | 'password_changed': password_changed 55 | } 56 | return render_to_response('settings.html', data, context_instance=RequestContext(request)) 57 | 58 | 59 | @login_required 60 | def ctfoverview(request): 61 | return render_to_response('ctf/overview.html') 62 | 63 | 64 | @login_required 65 | def ctfchallenge(request): 66 | return render_to_response('ctf/challenge/overview.html') 67 | 68 | 69 | @login_required 70 | @require_safe 71 | def reports(request): 72 | data = { 73 | 'ctfs': Competition.objects.order_by('-start_time') 74 | } 75 | return render_to_response('reports.html', data, RequestContext(request)) 76 | 77 | 78 | def register(request): 79 | if request.user.is_authenticated(): 80 | return redirect('index') 81 | 82 | elif settings.REGISTRATION_LOCK: 83 | resp = render_to_response('registration_locked.html', {}, RequestContext(request)) 84 | resp.status_code = 403 85 | return resp 86 | 87 | if request.method == 'GET': 88 | form = RegistrationForm() 89 | data = { 90 | 'register_form': form 91 | } 92 | return render_to_response('register.html', data, RequestContext(request)) 93 | 94 | elif request.method == 'POST': 95 | form = RegistrationForm(request.POST) 96 | data = { 97 | 'register_form': form 98 | } 99 | 100 | if form.is_valid(): 101 | user = form.save(commit=False) 102 | user.email = form.cleaned_data['email'] 103 | user.first_name = form.cleaned_data['first_name'] 104 | user.last_name = form.cleaned_data['last_name'] 105 | user.save() 106 | data['user'] = user 107 | return redirect(reverse('login')) 108 | 109 | return render_to_response('register.html', data, RequestContext(request)) 110 | 111 | 112 | @require_http_methods(['GET', 'POST']) 113 | def log_in(request): 114 | if request.method == 'GET': 115 | data = { 116 | 'login_form': LoginForm() 117 | } 118 | 119 | return render_to_response('login.html', data, RequestContext(request)) 120 | 121 | elif request.method == 'POST': 122 | form = LoginForm(data=request.POST) 123 | if form.is_valid(): 124 | cd = form.cleaned_data 125 | user = authenticate(username=cd['username'], password=cd['password']) 126 | if user is not None: 127 | if user.is_active: 128 | login(request, user) 129 | return redirect(reverse('index')) 130 | 131 | data = { 132 | 'login_form': form 133 | } 134 | return render_to_response('login.html', data, RequestContext(request)) 135 | 136 | -------------------------------------------------------------------------------- /collabCTF/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for collabCTF project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.6/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "collabCTF.settings") 12 | 13 | from django.core.wsgi import get_wsgi_application 14 | application = get_wsgi_application() 15 | -------------------------------------------------------------------------------- /competition/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackUCF/collabCTF/463f4e5a303b5eb6ae6b5d4aab916e02a3bb2ae0/competition/__init__.py -------------------------------------------------------------------------------- /competition/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from competition.models import Competition, Challenge, ChallengeFile 3 | 4 | # Register your models here. 5 | 6 | 7 | class CompetitionAdmin(admin.ModelAdmin): 8 | prepopulated_fields = {'slug': ('name',)} 9 | 10 | class Meta: 11 | model = Competition 12 | 13 | 14 | class ChallengeAdmin(admin.ModelAdmin): 15 | prepopulated_fields = {'slug': ('name',)} 16 | 17 | class Meta: 18 | model = Challenge 19 | 20 | admin.site.register(Competition, CompetitionAdmin) 21 | admin.site.register(Challenge, ChallengeAdmin) 22 | admin.site.register(ChallengeFile) -------------------------------------------------------------------------------- /competition/ajax.py: -------------------------------------------------------------------------------- 1 | import json 2 | import datetime as dt 3 | 4 | from django.contrib.auth.decorators import login_required 5 | from django.core.urlresolvers import resolve 6 | from django.http import HttpResponse 7 | from django.shortcuts import get_object_or_404 8 | from django.views.decorators.csrf import csrf_exempt 9 | from django.views.decorators.http import require_GET, require_POST 10 | from django.db.models import Sum 11 | 12 | from competition.models import Competition, Challenge 13 | from tools.misc import JSONResponse 14 | 15 | 16 | try: 17 | # not required since it's included, but... 18 | from pytz import UTC 19 | except ImportError: 20 | from tools.misc import UTC 21 | 22 | @login_required 23 | @require_GET 24 | def chart_data(request, ctf_slug): 25 | ctf = get_object_or_404(Competition.objects.prefetch_related('challenges'), slug=ctf_slug) 26 | challenges = ctf.challenges 27 | 28 | # compute aggregate data 29 | assert isinstance(ctf, Competition) 30 | solved_challenges = challenges.filter(progress=Challenge.SOLVED) 31 | in_progress_challenges = challenges.filter(progress=Challenge.IN_PROGRESS) 32 | challenges_data = { 33 | 'total': challenges.count(), 34 | 'in_progress': in_progress_challenges.count(), 35 | 'solved': solved_challenges.count() 36 | } 37 | 38 | # Py2+3 unix time 39 | if ctf.start_time is not None: 40 | start_time = (ctf.start_time - dt.datetime(1970, 1, 1, tzinfo=UTC)).total_seconds() 41 | else: 42 | start_time = None 43 | if ctf.end_time is not None: 44 | end_time = (ctf.end_time - dt.datetime(1970, 1, 1, tzinfo=UTC)).total_seconds() 45 | else: 46 | end_time = None 47 | users = { 48 | 'online': 4, 49 | 'total': 22 50 | } 51 | 52 | pv_sum = Sum('point_value') 53 | points = { 54 | 'earned': solved_challenges.aggregate(pv_sum)['point_value__sum'] or 0.001, 55 | 'in_progress': in_progress_challenges.aggregate(pv_sum)['point_value__sum'] or 0.001, 56 | 'total': challenges.aggregate(pv_sum)['point_value__sum'] or 1 57 | } 58 | 59 | data = { 60 | 'challenges': challenges_data, 61 | 'start_time': start_time, 62 | 'end_time': end_time, 63 | 'users': users, 64 | 'points': points 65 | } 66 | 67 | return JSONResponse(json.dumps(data)) 68 | 69 | 70 | @login_required 71 | @csrf_exempt 72 | @require_POST 73 | def track_challenge_visit(request): 74 | resolved = resolve(request.POST['url']) 75 | if resolved.view_name.endswith('challenge'): 76 | try: 77 | kwargs = resolved.kwargs 78 | challenge = Challenge.objects.get(competition__slug=kwargs['ctf_slug'], slug=kwargs['chall_slug']) 79 | if challenge.progress != Challenge.SOLVED: 80 | challenge.last_viewed = dt.datetime.now(UTC) 81 | challenge.save() 82 | except Challenge.DoesNotExist: 83 | pass 84 | return HttpResponse() -------------------------------------------------------------------------------- /competition/forms.py: -------------------------------------------------------------------------------- 1 | from crispy_forms.helper import FormHelper 2 | from crispy_forms_foundation.layout import Submit, Reset, Layout, Fieldset, ButtonHolder, HTML 3 | from django import forms 4 | from django.contrib.auth.forms import UserCreationForm, AuthenticationForm, PasswordChangeForm as PWChangeForm, \ 5 | SetPasswordForm 6 | from django.core.urlresolvers import reverse 7 | 8 | from competition.models import Challenge, Competition, ChallengeFile 9 | 10 | 11 | _form_control = {'class': 'form-control'} 12 | 13 | 14 | class CompetitionModelForm(forms.ModelForm): 15 | def __init__(self, *args, **kwargs): 16 | super(CompetitionModelForm, self).__init__(*args, **kwargs) 17 | self.add_helper = FormHelper() 18 | self.add_helper.form_id = 'add-ctf' 19 | self.add_helper.form_method = 'post' 20 | self.add_helper.form_action = '' 21 | 22 | self.update_helper = FormHelper() 23 | self.update_helper.form_id = 'update-ctf' 24 | self.update_helper.form_method = 'post' 25 | self.update_helper.form_action = '' 26 | 27 | button_holder = ButtonHolder( 28 | Submit('submit', 'Submit'), 29 | Reset('reset', 'Reset'), 30 | css_class='text-right' 31 | ) 32 | 33 | self.add_helper.layout = Layout( 34 | Fieldset( 35 | 'Add a competition', 36 | 'name', 37 | 'url', 38 | 'start_time', 39 | 'end_time' 40 | ), 41 | button_holder 42 | ) 43 | 44 | self.update_helper.layout = Layout( 45 | Fieldset( 46 | 'Update competition', 47 | 'name', 48 | 'url', 49 | 'start_time', 50 | 'end_time' 51 | ), 52 | button_holder 53 | ) 54 | 55 | class Meta: 56 | model = Competition 57 | fields = ('name', 'url', 'start_time', 'end_time') 58 | 59 | 60 | class ChallengeModelForm(forms.ModelForm): 61 | def __init__(self, *args, **kwargs): 62 | super(ChallengeModelForm, self).__init__(*args, **kwargs) 63 | 64 | self.add_helper = FormHelper() 65 | self.add_helper.form_id = 'add-challenge' 66 | self.add_helper.form_method = 'post' 67 | self.add_helper.form_action = '' 68 | 69 | self.update_helper = FormHelper() 70 | self.update_helper.form_id = 'update-challenge' 71 | self.update_helper.form_method = 'post' 72 | self.update_helper.form_action = '' 73 | 74 | holder = ButtonHolder( 75 | Submit('submit', 'Submit'), 76 | Reset('reset', 'Reset'), 77 | css_class='text-right' 78 | ) 79 | 80 | self.add_helper.layout = Layout( 81 | Fieldset( 82 | 'Add a challenge', 83 | 'name', 84 | 'point_value', 85 | 'progress', 86 | 'num_progress' 87 | ), 88 | holder 89 | ) 90 | 91 | self.update_helper.layout = Layout( 92 | Fieldset( 93 | 'Update a challenge', 94 | 'name', 95 | 'point_value', 96 | 'progress', 97 | 'num_progress' 98 | ), 99 | holder 100 | ) 101 | 102 | class Meta: 103 | model = Challenge 104 | fields = ('name', 'point_value', 'progress', 'num_progress') 105 | widgets = { 106 | 'num_progress': forms.NumberInput(attrs={'type': 'range', 107 | 'min': 0, 108 | 'max': 100, 109 | 'step': 1}) 110 | } 111 | 112 | 113 | class ChallengeFileModelForm(forms.ModelForm): 114 | def __init__(self, *args, **kwargs): 115 | super(ChallengeFileModelForm, self).__init__(*args, **kwargs) 116 | 117 | self.add_helper = FormHelper() 118 | self.add_helper.form_id = 'add-file' 119 | self.add_helper.form_method = 'post' 120 | self.add_helper.form_action = '' 121 | 122 | self.update_helper = FormHelper() 123 | self.update_helper.form_id = 'update-file' 124 | self.update_helper.form_method = 'post' 125 | self.update_helper.form_action = '' 126 | 127 | holder = ButtonHolder( 128 | Submit('submit', 'Submit'), 129 | Reset('reset', 'Reset'), 130 | css_class='text-right' 131 | ) 132 | 133 | self.add_helper.layout = Layout( 134 | Fieldset( 135 | 'Add a file', 136 | HTML('

Original filenames are preserved whenever possible.

'), 137 | 'file', 138 | ), 139 | holder 140 | ) 141 | 142 | self.update_helper.layout = Layout( 143 | Fieldset( 144 | 'Update a file', 145 | 'file', 146 | ), 147 | holder 148 | ) 149 | 150 | class Meta: 151 | model = ChallengeFile 152 | fields = ('file',) 153 | 154 | 155 | class RegistrationForm(UserCreationForm): 156 | email = forms.EmailField() 157 | first_name = forms.CharField(required=False) 158 | last_name = forms.CharField(required=False) 159 | 160 | def __init__(self, *args, **kwargs): 161 | super(RegistrationForm, self).__init__(*args, **kwargs) 162 | self.helper = FormHelper() 163 | self.helper.form_id = 'registration-form' 164 | self.helper.form_method = 'post' 165 | self.helper.form_action = reverse("register") 166 | self.helper.layout = Layout( 167 | Fieldset( 168 | '', 169 | 'username', 170 | 'first_name', 171 | 'last_name', 172 | 'email', 173 | 'password1', 174 | 'password2' 175 | ), 176 | ButtonHolder( 177 | Submit('submit', 'Submit'), 178 | Reset('reset', 'Reset'), 179 | css_class='text-right' 180 | ) 181 | ) 182 | 183 | 184 | class LoginForm(AuthenticationForm): 185 | def __init__(self, *args, **kwargs): 186 | super(LoginForm, self).__init__(*args, **kwargs) 187 | self.helper = FormHelper() 188 | self.helper.form_id = 'login-form' 189 | self.helper.attrs = {'data_abide': ''} 190 | self.helper.form_method = 'post' 191 | self.helper.form_action = '' 192 | self.helper.layout = Layout( 193 | Fieldset( 194 | '', 195 | 'username', 196 | 'password' 197 | ), 198 | ButtonHolder( 199 | Reset('reset', 'Reset', css_class='secondary'), 200 | Submit('submit', 'Submit'), 201 | css_class='buttons text-right' 202 | ) 203 | ) 204 | 205 | 206 | class PasswordChangeForm(PWChangeForm): 207 | def __init__(self, *args, **kwargs): 208 | super(PasswordChangeForm, self).__init__(*args, **kwargs) 209 | 210 | self.helper = FormHelper() 211 | self.helper.form_id = 'pw-change-form' 212 | self.helper.form_method = 'post' 213 | self.helper.form_action = '' 214 | self.helper.layout = Layout( 215 | Fieldset( 216 | 'Change password', 217 | 'old_password', 218 | 'new_password1', 219 | 'new_password2' 220 | ), 221 | ButtonHolder( 222 | Submit('submit', 'Submit'), 223 | Reset('reset', 'Reset'), 224 | css_class='text-right' 225 | ) 226 | ) 227 | 228 | 229 | #not currently working and actually changing things 230 | class EmailChangeForm(forms.Form): 231 | email = forms.EmailField(label="New Email Address") 232 | password = forms.CharField(widget=forms.PasswordInput(), label="Password") 233 | 234 | def __init__(self, *args, **kwargs): 235 | super(EmailChangeForm, self).__init__(*args, **kwargs) 236 | 237 | self.helper = FormHelper() 238 | self.helper.form_id = 'email-change-form' 239 | self.helper.form_method = 'post' 240 | self.helper.form_action = '' 241 | self.helper.layout = Layout( 242 | Fieldset( 243 | 'Change email', 244 | HTML( 245 | '''

{{ user.email }}

'''), 246 | 'email', 247 | 'password' 248 | ), 249 | ButtonHolder( 250 | Submit('submit', 'Submit'), 251 | Reset('reset', 'Reset'), 252 | css_class='text-right' 253 | ) 254 | ) 255 | 256 | #The below was taken from password change form 257 | error_messages = dict(SetPasswordForm.error_messages, **{ 258 | 'password_incorrect': ("Your old password was entered incorrectly. " 259 | "Please enter it again.") 260 | }) 261 | 262 | def clean_password(self): 263 | """ 264 | Validates that the old_password field is correct. 265 | """ 266 | old_password = self.cleaned_data["old_password"] 267 | if not self.user.check_password(old_password): 268 | raise forms.ValidationError( 269 | self.error_messages['password_incorrect'], 270 | code='password_incorrect', 271 | ) 272 | return old_password 273 | 274 | def save(self, commit=True): 275 | self.user.set_password(self.cleaned_data['new_password1']) 276 | if commit: 277 | self.user.save() 278 | return self.user 279 | -------------------------------------------------------------------------------- /competition/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from south.utils import datetime_utils as datetime 3 | from south.db import db 4 | from south.v2 import SchemaMigration 5 | from django.db import models 6 | 7 | 8 | class Migration(SchemaMigration): 9 | 10 | def forwards(self, orm): 11 | # Adding model 'Competition' 12 | db.create_table('competition_competition', ( 13 | ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 14 | ('name', self.gf('django.db.models.fields.CharField')(unique=True, max_length=255)), 15 | ('slug', self.gf('django.db.models.fields.SlugField')(unique=True, max_length=50)), 16 | ('url', self.gf('django.db.models.fields.URLField')(blank=True, max_length=200)), 17 | ('start_time', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)), 18 | ('end_time', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)), 19 | )) 20 | db.send_create_signal('competition', ['Competition']) 21 | 22 | # Adding model 'Challenge' 23 | db.create_table('competition_challenge', ( 24 | ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 25 | ('last_viewed', self.gf('django.db.models.fields.DateTimeField')()), 26 | ('name', self.gf('django.db.models.fields.CharField')(max_length=255)), 27 | ('slug', self.gf('django.db.models.fields.SlugField')(max_length=50)), 28 | ('progress', self.gf('django.db.models.fields.PositiveSmallIntegerField')()), 29 | ('num_progress', self.gf('django.db.models.fields.FloatField')(default=0)), 30 | ('point_value', self.gf('django.db.models.fields.FloatField')(default=0)), 31 | ('competition', self.gf('django.db.models.fields.related.ForeignKey')(related_name='challenges', to=orm['competition.Competition'])), 32 | )) 33 | db.send_create_signal('competition', ['Challenge']) 34 | 35 | # Adding unique constraint on 'Challenge', fields ['name', 'competition'] 36 | db.create_unique('competition_challenge', ['name', 'competition_id']) 37 | 38 | # Adding model 'ChallengeFile' 39 | db.create_table('competition_challengefile', ( 40 | ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 41 | ('ctime', self.gf('django.db.models.fields.DateTimeField')()), 42 | ('file', self.gf('django.db.models.fields.files.FileField')(max_length=100)), 43 | ('mtime', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, blank=True)), 44 | ('challenge', self.gf('django.db.models.fields.related.ForeignKey')(related_name='files', to=orm['competition.Challenge'])), 45 | )) 46 | db.send_create_signal('competition', ['ChallengeFile']) 47 | 48 | # Adding model 'Tag' 49 | db.create_table('competition_tag', ( 50 | ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), 51 | ('tag', self.gf('django.db.models.fields.SlugField')(max_length=50)), 52 | ('is_category', self.gf('django.db.models.fields.BooleanField')(default=False)), 53 | ('content_type', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['contenttypes.ContentType'])), 54 | ('object_id', self.gf('django.db.models.fields.PositiveIntegerField')()), 55 | )) 56 | db.send_create_signal('competition', ['Tag']) 57 | 58 | 59 | def backwards(self, orm): 60 | # Removing unique constraint on 'Challenge', fields ['name', 'competition'] 61 | db.delete_unique('competition_challenge', ['name', 'competition_id']) 62 | 63 | # Deleting model 'Competition' 64 | db.delete_table('competition_competition') 65 | 66 | # Deleting model 'Challenge' 67 | db.delete_table('competition_challenge') 68 | 69 | # Deleting model 'ChallengeFile' 70 | db.delete_table('competition_challengefile') 71 | 72 | # Deleting model 'Tag' 73 | db.delete_table('competition_tag') 74 | 75 | 76 | models = { 77 | 'competition.challenge': { 78 | 'Meta': {'object_name': 'Challenge', 'ordering': "('progress',)", 'unique_together': "(('name', 'competition'),)"}, 79 | 'competition': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'challenges'", 'to': "orm['competition.Competition']"}), 80 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 81 | 'last_viewed': ('django.db.models.fields.DateTimeField', [], {}), 82 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}), 83 | 'num_progress': ('django.db.models.fields.FloatField', [], {'default': '0'}), 84 | 'point_value': ('django.db.models.fields.FloatField', [], {'default': '0'}), 85 | 'progress': ('django.db.models.fields.PositiveSmallIntegerField', [], {}), 86 | 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}) 87 | }, 88 | 'competition.challengefile': { 89 | 'Meta': {'object_name': 'ChallengeFile'}, 90 | 'challenge': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'files'", 'to': "orm['competition.Challenge']"}), 91 | 'ctime': ('django.db.models.fields.DateTimeField', [], {}), 92 | 'file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}), 93 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 94 | 'mtime': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}) 95 | }, 96 | 'competition.competition': { 97 | 'Meta': {'object_name': 'Competition'}, 98 | 'end_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), 99 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 100 | 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255'}), 101 | 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50'}), 102 | 'start_time': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), 103 | 'url': ('django.db.models.fields.URLField', [], {'blank': 'True', 'max_length': '200'}) 104 | }, 105 | 'competition.tag': { 106 | 'Meta': {'object_name': 'Tag'}, 107 | 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), 108 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 109 | 'is_category': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 110 | 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {}), 111 | 'tag': ('django.db.models.fields.SlugField', [], {'max_length': '50'}) 112 | }, 113 | 'contenttypes.contenttype': { 114 | 'Meta': {'object_name': 'ContentType', 'db_table': "'django_content_type'", 'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)"}, 115 | 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 116 | 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 117 | 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 118 | 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) 119 | } 120 | } 121 | 122 | complete_apps = ['competition'] -------------------------------------------------------------------------------- /competition/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackUCF/collabCTF/463f4e5a303b5eb6ae6b5d4aab916e02a3bb2ae0/competition/migrations/__init__.py -------------------------------------------------------------------------------- /competition/models.py: -------------------------------------------------------------------------------- 1 | import os 2 | from django.contrib.contenttypes import generic 3 | from django.contrib.contenttypes.models import ContentType 4 | from django.core.urlresolvers import reverse 5 | from django.db import models 6 | 7 | 8 | class Competition(models.Model): 9 | name = models.CharField('Name', max_length=255, unique=True) 10 | slug = models.SlugField(unique=True) 11 | url = models.URLField('Competition URL', blank=True) 12 | start_time = models.DateTimeField(blank=True, null=True) 13 | end_time = models.DateTimeField(blank=True, null=True) 14 | 15 | def __unicode__(self): 16 | return self.name 17 | 18 | __str__ = __unicode__ 19 | 20 | def get_absolute_url(self): 21 | return reverse('view_ctf', kwargs={'ctf_slug': self.slug}) 22 | 23 | 24 | class Challenge(models.Model): 25 | NOT_STARTED = 0 26 | IN_PROGRESS = 1 27 | SOLVED = 2 28 | 29 | PROGRESS_CHOICES = ( 30 | (NOT_STARTED, 'Not Started'), 31 | (IN_PROGRESS, 'In Progress'), 32 | (SOLVED, 'Solved') 33 | ) 34 | 35 | name = models.CharField('Name', max_length=255) 36 | slug = models.SlugField() 37 | progress = models.PositiveSmallIntegerField(choices=PROGRESS_CHOICES) 38 | num_progress = models.FloatField('Progress %', default=0) 39 | point_value = models.FloatField(default=0) 40 | 41 | competition = models.ForeignKey(Competition, related_name='challenges') 42 | last_viewed = models.DateTimeField(auto_created=True) 43 | 44 | def __unicode__(self): 45 | return self.name 46 | 47 | __str__ = __unicode__ 48 | 49 | def get_absolute_url(self): 50 | return reverse('view_challenge', kwargs={'ctf_slug': self.competition.slug, 'chall_slug': self.slug}) 51 | 52 | def last_viewed_display(self): 53 | if self.last_viewed == 0: 54 | return 'Never' 55 | else: 56 | return self.last_viewed 57 | 58 | class Meta: 59 | unique_together = ('name', 'competition') 60 | ordering = ('progress',) 61 | 62 | 63 | class ChallengeFile(models.Model): 64 | file = models.FileField(upload_to='files/') 65 | ctime = models.DateTimeField(auto_created=True) 66 | mtime = models.DateTimeField(auto_now=True) 67 | challenge = models.ForeignKey(Challenge, related_name='files') 68 | 69 | def __unicode__(self): 70 | return self.file.name 71 | 72 | __str__ = __unicode__ 73 | 74 | def filename(self): 75 | return os.path.basename(self.file.name) 76 | 77 | 78 | class Tag(models.Model): 79 | tag = models.SlugField() 80 | is_category = models.BooleanField(default=False) 81 | content_type = models.ForeignKey(ContentType) 82 | object_id = models.PositiveIntegerField() 83 | content_object = generic.GenericForeignKey('content_type', 'object_id') 84 | 85 | def __unicode__(self): 86 | return self.tag 87 | 88 | __str__ = __unicode__ -------------------------------------------------------------------------------- /competition/static/clocks/blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackUCF/collabCTF/463f4e5a303b5eb6ae6b5d4aab916e02a3bb2ae0/competition/static/clocks/blue.png -------------------------------------------------------------------------------- /competition/static/clocks/green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackUCF/collabCTF/463f4e5a303b5eb6ae6b5d4aab916e02a3bb2ae0/competition/static/clocks/green.png -------------------------------------------------------------------------------- /competition/static/clocks/orange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackUCF/collabCTF/463f4e5a303b5eb6ae6b5d4aab916e02a3bb2ae0/competition/static/clocks/orange.png -------------------------------------------------------------------------------- /competition/static/clocks/pink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackUCF/collabCTF/463f4e5a303b5eb6ae6b5d4aab916e02a3bb2ae0/competition/static/clocks/pink.png -------------------------------------------------------------------------------- /competition/static/css/normalize.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v3.0.0 | MIT License | git.io/normalize */ 2 | 3 | /** 4 | * 1. Set default font family to sans-serif. 5 | * 2. Prevent iOS text size adjust after orientation change, without disabling 6 | * user zoom. 7 | */ 8 | 9 | html { 10 | font-family: sans-serif; /* 1 */ 11 | -ms-text-size-adjust: 100%; /* 2 */ 12 | -webkit-text-size-adjust: 100%; /* 2 */ 13 | } 14 | 15 | /** 16 | * Remove default margin. 17 | */ 18 | 19 | body { 20 | margin: 0; 21 | } 22 | 23 | /* HTML5 display definitions 24 | ========================================================================== */ 25 | 26 | /** 27 | * Correct `block` display not defined in IE 8/9. 28 | */ 29 | 30 | article, 31 | aside, 32 | details, 33 | figcaption, 34 | figure, 35 | footer, 36 | header, 37 | hgroup, 38 | main, 39 | nav, 40 | section, 41 | summary { 42 | display: block; 43 | } 44 | 45 | /** 46 | * 1. Correct `inline-block` display not defined in IE 8/9. 47 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. 48 | */ 49 | 50 | audio, 51 | canvas, 52 | progress, 53 | video { 54 | display: inline-block; /* 1 */ 55 | vertical-align: baseline; /* 2 */ 56 | } 57 | 58 | /** 59 | * Prevent modern browsers from displaying `audio` without controls. 60 | * Remove excess height in iOS 5 devices. 61 | */ 62 | 63 | audio:not([controls]) { 64 | display: none; 65 | height: 0; 66 | } 67 | 68 | /** 69 | * Address `[hidden]` styling not present in IE 8/9. 70 | * Hide the `template` element in IE, Safari, and Firefox < 22. 71 | */ 72 | 73 | [hidden], 74 | template { 75 | display: none; 76 | } 77 | 78 | /* Links 79 | ========================================================================== */ 80 | 81 | /** 82 | * Remove the gray background color from active links in IE 10. 83 | */ 84 | 85 | a { 86 | background: transparent; 87 | } 88 | 89 | /** 90 | * Improve readability when focused and also mouse hovered in all browsers. 91 | */ 92 | 93 | a:active, 94 | a:hover { 95 | outline: 0; 96 | } 97 | 98 | /* Text-level semantics 99 | ========================================================================== */ 100 | 101 | /** 102 | * Address styling not present in IE 8/9, Safari 5, and Chrome. 103 | */ 104 | 105 | abbr[title] { 106 | border-bottom: 1px dotted; 107 | } 108 | 109 | /** 110 | * Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome. 111 | */ 112 | 113 | b, 114 | strong { 115 | font-weight: bold; 116 | } 117 | 118 | /** 119 | * Address styling not present in Safari 5 and Chrome. 120 | */ 121 | 122 | dfn { 123 | font-style: italic; 124 | } 125 | 126 | /** 127 | * Address variable `h1` font-size and margin within `section` and `article` 128 | * contexts in Firefox 4+, Safari 5, and Chrome. 129 | */ 130 | 131 | h1 { 132 | font-size: 2em; 133 | margin: 0.67em 0; 134 | } 135 | 136 | /** 137 | * Address styling not present in IE 8/9. 138 | */ 139 | 140 | mark { 141 | background: #ff0; 142 | color: #000; 143 | } 144 | 145 | /** 146 | * Address inconsistent and variable font size in all browsers. 147 | */ 148 | 149 | small { 150 | font-size: 80%; 151 | } 152 | 153 | /** 154 | * Prevent `sub` and `sup` affecting `line-height` in all browsers. 155 | */ 156 | 157 | sub, 158 | sup { 159 | font-size: 75%; 160 | line-height: 0; 161 | position: relative; 162 | vertical-align: baseline; 163 | } 164 | 165 | sup { 166 | top: -0.5em; 167 | } 168 | 169 | sub { 170 | bottom: -0.25em; 171 | } 172 | 173 | /* Embedded content 174 | ========================================================================== */ 175 | 176 | /** 177 | * Remove border when inside `a` element in IE 8/9. 178 | */ 179 | 180 | img { 181 | border: 0; 182 | } 183 | 184 | /** 185 | * Correct overflow displayed oddly in IE 9. 186 | */ 187 | 188 | svg:not(:root) { 189 | overflow: hidden; 190 | } 191 | 192 | /* Grouping content 193 | ========================================================================== */ 194 | 195 | /** 196 | * Address margin not present in IE 8/9 and Safari 5. 197 | */ 198 | 199 | figure { 200 | margin: 1em 40px; 201 | } 202 | 203 | /** 204 | * Address differences between Firefox and other browsers. 205 | */ 206 | 207 | hr { 208 | -moz-box-sizing: content-box; 209 | box-sizing: content-box; 210 | height: 0; 211 | } 212 | 213 | /** 214 | * Contain overflow in all browsers. 215 | */ 216 | 217 | pre { 218 | overflow: auto; 219 | } 220 | 221 | /** 222 | * Address odd `em`-unit font size rendering in all browsers. 223 | */ 224 | 225 | code, 226 | kbd, 227 | pre, 228 | samp { 229 | font-family: monospace, monospace; 230 | font-size: 1em; 231 | } 232 | 233 | /* Forms 234 | ========================================================================== */ 235 | 236 | /** 237 | * Known limitation: by default, Chrome and Safari on OS X allow very limited 238 | * styling of `select`, unless a `border` property is set. 239 | */ 240 | 241 | /** 242 | * 1. Correct color not being inherited. 243 | * Known issue: affects color of disabled elements. 244 | * 2. Correct font properties not being inherited. 245 | * 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome. 246 | */ 247 | 248 | button, 249 | input, 250 | optgroup, 251 | select, 252 | textarea { 253 | color: inherit; /* 1 */ 254 | font: inherit; /* 2 */ 255 | margin: 0; /* 3 */ 256 | } 257 | 258 | /** 259 | * Address `overflow` set to `hidden` in IE 8/9/10. 260 | */ 261 | 262 | button { 263 | overflow: visible; 264 | } 265 | 266 | /** 267 | * Address inconsistent `text-transform` inheritance for `button` and `select`. 268 | * All other form control elements do not inherit `text-transform` values. 269 | * Correct `button` style inheritance in Firefox, IE 8+, and Opera 270 | * Correct `select` style inheritance in Firefox. 271 | */ 272 | 273 | button, 274 | select { 275 | text-transform: none; 276 | } 277 | 278 | /** 279 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` 280 | * and `video` controls. 281 | * 2. Correct inability to style clickable `input` types in iOS. 282 | * 3. Improve usability and consistency of cursor style between image-type 283 | * `input` and others. 284 | */ 285 | 286 | button, 287 | html input[type="button"], /* 1 */ 288 | input[type="reset"], 289 | input[type="submit"] { 290 | -webkit-appearance: button; /* 2 */ 291 | cursor: pointer; /* 3 */ 292 | } 293 | 294 | /** 295 | * Re-set default cursor for disabled elements. 296 | */ 297 | 298 | button[disabled], 299 | html input[disabled] { 300 | cursor: default; 301 | } 302 | 303 | /** 304 | * Remove inner padding and border in Firefox 4+. 305 | */ 306 | 307 | button::-moz-focus-inner, 308 | input::-moz-focus-inner { 309 | border: 0; 310 | padding: 0; 311 | } 312 | 313 | /** 314 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in 315 | * the UA stylesheet. 316 | */ 317 | 318 | input { 319 | line-height: normal; 320 | } 321 | 322 | /** 323 | * It's recommended that you don't attempt to style these elements. 324 | * Firefox's implementation doesn't respect box-sizing, padding, or width. 325 | * 326 | * 1. Address box sizing set to `content-box` in IE 8/9/10. 327 | * 2. Remove excess padding in IE 8/9/10. 328 | */ 329 | 330 | input[type="checkbox"], 331 | input[type="radio"] { 332 | box-sizing: border-box; /* 1 */ 333 | padding: 0; /* 2 */ 334 | } 335 | 336 | /** 337 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain 338 | * `font-size` values of the `input`, it causes the cursor style of the 339 | * decrement button to change from `default` to `text`. 340 | */ 341 | 342 | input[type="number"]::-webkit-inner-spin-button, 343 | input[type="number"]::-webkit-outer-spin-button { 344 | height: auto; 345 | } 346 | 347 | /** 348 | * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome. 349 | * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome 350 | * (include `-moz` to future-proof). 351 | */ 352 | 353 | input[type="search"] { 354 | -webkit-appearance: textfield; /* 1 */ 355 | -moz-box-sizing: content-box; 356 | -webkit-box-sizing: content-box; /* 2 */ 357 | box-sizing: content-box; 358 | } 359 | 360 | /** 361 | * Remove inner padding and search cancel button in Safari and Chrome on OS X. 362 | * Safari (but not Chrome) clips the cancel button when the search input has 363 | * padding (and `textfield` appearance). 364 | */ 365 | 366 | input[type="search"]::-webkit-search-cancel-button, 367 | input[type="search"]::-webkit-search-decoration { 368 | -webkit-appearance: none; 369 | } 370 | 371 | /** 372 | * Define consistent border, margin, and padding. 373 | */ 374 | 375 | fieldset { 376 | border: 1px solid #c0c0c0; 377 | margin: 0 2px; 378 | padding: 0.35em 0.625em 0.75em; 379 | } 380 | 381 | /** 382 | * 1. Correct `color` not being inherited in IE 8/9. 383 | * 2. Remove padding so people aren't caught out if they zero out fieldsets. 384 | */ 385 | 386 | legend { 387 | border: 0; /* 1 */ 388 | padding: 0; /* 2 */ 389 | } 390 | 391 | /** 392 | * Remove default vertical scrollbar in IE 8/9. 393 | */ 394 | 395 | textarea { 396 | overflow: auto; 397 | } 398 | 399 | /** 400 | * Don't inherit the `font-weight` (applied by a rule above). 401 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. 402 | */ 403 | 404 | optgroup { 405 | font-weight: bold; 406 | } 407 | 408 | /* Tables 409 | ========================================================================== */ 410 | 411 | /** 412 | * Remove most spacing between table cells. 413 | */ 414 | 415 | table { 416 | border-collapse: collapse; 417 | border-spacing: 0; 418 | } 419 | 420 | td, 421 | th { 422 | padding: 0; 423 | } 424 | -------------------------------------------------------------------------------- /competition/static/css/style.css: -------------------------------------------------------------------------------- 1 | .errorlist > li { 2 | color: red; 3 | } 4 | 5 | table#challenges { 6 | width: 100%; 7 | } 8 | 9 | table#challenges > tbody > tr { 10 | cursor: pointer; 11 | } 12 | 13 | table#challenges > tbody > tr:before { 14 | display: table-cell; 15 | content: ''; 16 | width: 1px; 17 | height: 100%; 18 | margin: 1px 0; 19 | overflow: hidden; 20 | box-sizing: border-box; 21 | } 22 | 23 | table#challenges > tbody > tr.not-started:before { 24 | background: red; 25 | } 26 | 27 | table#challenges > tbody > tr.in-progress:before { 28 | background: #fcf8e3; 29 | overflow: hidden; 30 | } 31 | 32 | table#challenges > tbody > tr.solved:before { 33 | background: #dff0d8; 34 | overflow: hidden; 35 | } 36 | 37 | table#challenges > tbody > tr.solved { 38 | opacity: 0.6; 39 | transition: opacity .1s ease-in-out; 40 | -webkit-transition: opacity .1s ease-in-out; 41 | -moz-transition: opacity .1s ease-in-out; 42 | -ms-transition: opacity .1s ease-in-out; 43 | -o-transition: opacity .1s ease-in-out; 44 | } 45 | 46 | table#challenges > tbody > tr.solved:hover { 47 | opacity: 1; 48 | } 49 | 50 | #challenge-header.solved { 51 | padding-left: 2px; 52 | background: #dff0d8; 53 | } 54 | 55 | #uploaded-files tr > td:last-child, 56 | #uploaded-files tr > th:last-child { 57 | text-align: right; 58 | } 59 | 60 | /* covering the "Upload new file" td */ 61 | #uploaded-files tr > td:first-child { 62 | text-align: inherit; 63 | } 64 | 65 | .sidebar { 66 | overflow-y: auto; 67 | } 68 | 69 | .recent .challenge { 70 | cursor: pointer; 71 | } 72 | 73 | form .button-holder.text-right > input.button { 74 | margin-left: 8px; 75 | } 76 | 77 | #clocks > li { 78 | overflow: hidden; 79 | } 80 | 81 | input[type=range] { 82 | width: 100%; 83 | } 84 | 85 | .add-challenge-row a { 86 | display: block; 87 | } 88 | 89 | #tools-sticky dt { 90 | margin-bottom: 0; 91 | } -------------------------------------------------------------------------------- /competition/static/dashboard.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Base structure 3 | */ 4 | 5 | /* Move down content because we have a fixed navbar that is 50px tall */ 6 | body { 7 | padding-top: 50px; 8 | } 9 | 10 | 11 | /* 12 | * Global add-ons 13 | */ 14 | 15 | .sub-header { 16 | padding-bottom: 10px; 17 | border-bottom: 1px solid #eee; 18 | } 19 | 20 | 21 | /* 22 | * Sidebar 23 | */ 24 | 25 | /* Hide for mobile, show later */ 26 | .sidebar { 27 | display: none; 28 | } 29 | @media (min-width: 768px) { 30 | .sidebar { 31 | position: fixed; 32 | top: 0; 33 | left: 0; 34 | bottom: 0; 35 | z-index: 1000; 36 | display: block; 37 | padding: 70px 20px 20px; 38 | background-color: #f5f5f5; 39 | border-right: 1px solid #eee; 40 | } 41 | } 42 | 43 | /* Sidebar navigation */ 44 | .nav-sidebar { 45 | margin-left: -20px; 46 | margin-right: -21px; /* 20px padding + 1px border */ 47 | margin-bottom: 20px; 48 | } 49 | .nav-sidebar > li > a { 50 | padding-left: 20px; 51 | padding-right: 20px; 52 | } 53 | .nav-sidebar > .active > a { 54 | color: #fff; 55 | background-color: #428bca; 56 | } 57 | 58 | 59 | /* 60 | * Main content 61 | */ 62 | 63 | .main { 64 | padding: 20px; 65 | } 66 | @media (min-width: 768px) { 67 | .main { 68 | padding-left: 40px; 69 | pading-right: 40px; 70 | } 71 | } 72 | .main .page-header { 73 | margin-top: 0; 74 | } 75 | 76 | 77 | /* 78 | * Placeholder dashboard ideas 79 | */ 80 | 81 | .placeholders { 82 | margin-bottom: 30px; 83 | text-align: center; 84 | } 85 | .placeholders h4 { 86 | margin-bottom: 0; 87 | } 88 | .placeholder { 89 | margin-bottom: 20px; 90 | } 91 | .placeholder img { 92 | border-radius: 50%; 93 | margin: auto; 94 | } 95 | -------------------------------------------------------------------------------- /competition/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackUCF/collabCTF/463f4e5a303b5eb6ae6b5d4aab916e02a3bb2ae0/competition/static/favicon.ico -------------------------------------------------------------------------------- /competition/static/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackUCF/collabCTF/463f4e5a303b5eb6ae6b5d4aab916e02a3bb2ae0/competition/static/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /competition/static/fonts/glyphicons-halflings-regular.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | -------------------------------------------------------------------------------- /competition/static/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackUCF/collabCTF/463f4e5a303b5eb6ae6b5d4aab916e02a3bb2ae0/competition/static/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /competition/static/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackUCF/collabCTF/463f4e5a303b5eb6ae6b5d4aab916e02a3bb2ae0/competition/static/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /competition/static/holder.js: -------------------------------------------------------------------------------- 1 | /*! 2 | 3 | Holder - 2.3 - client side image placeholders 4 | (c) 2012-2014 Ivan Malopinsky / http://imsky.co 5 | 6 | Provided under the MIT License. 7 | Commercial use requires attribution. 8 | 9 | */ 10 | var Holder = Holder || {}; 11 | (function (app, win) { 12 | 13 | var system_config = { 14 | use_svg: false, 15 | use_canvas: false, 16 | use_fallback: false 17 | }; 18 | var instance_config = {}; 19 | var preempted = false; 20 | canvas = document.createElement('canvas'); 21 | var dpr = 1, bsr = 1; 22 | var resizable_images = []; 23 | 24 | if (!canvas.getContext) { 25 | system_config.use_fallback = true; 26 | } else { 27 | if (canvas.toDataURL("image/png") 28 | .indexOf("data:image/png") < 0) { 29 | //Android doesn't support data URI 30 | system_config.use_fallback = true; 31 | } else { 32 | var ctx = canvas.getContext("2d"); 33 | } 34 | } 35 | 36 | if(!!document.createElementNS && !!document.createElementNS('http://www.w3.org/2000/svg', 'svg').createSVGRect){ 37 | system_config.use_svg = true; 38 | system_config.use_canvas = false; 39 | } 40 | 41 | if(!system_config.use_fallback){ 42 | dpr = window.devicePixelRatio || 1, 43 | bsr = ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1; 44 | } 45 | 46 | var ratio = dpr / bsr; 47 | 48 | var settings = { 49 | domain: "holder.js", 50 | images: "img", 51 | bgnodes: ".holderjs", 52 | themes: { 53 | "gray": { 54 | background: "#eee", 55 | foreground: "#aaa", 56 | size: 12 57 | }, 58 | "social": { 59 | background: "#3a5a97", 60 | foreground: "#fff", 61 | size: 12 62 | }, 63 | "industrial": { 64 | background: "#434A52", 65 | foreground: "#C2F200", 66 | size: 12 67 | }, 68 | "sky": { 69 | background: "#0D8FDB", 70 | foreground: "#fff", 71 | size: 12 72 | }, 73 | "vine": { 74 | background: "#39DBAC", 75 | foreground: "#1E292C", 76 | size: 12 77 | }, 78 | "lava": { 79 | background: "#F8591A", 80 | foreground: "#1C2846", 81 | size: 12 82 | } 83 | }, 84 | stylesheet: "" 85 | }; 86 | app.flags = { 87 | dimensions: { 88 | regex: /^(\d+)x(\d+)$/, 89 | output: function (val) { 90 | var exec = this.regex.exec(val); 91 | return { 92 | width: +exec[1], 93 | height: +exec[2] 94 | } 95 | } 96 | }, 97 | fluid: { 98 | regex: /^([0-9%]+)x([0-9%]+)$/, 99 | output: function (val) { 100 | var exec = this.regex.exec(val); 101 | return { 102 | width: exec[1], 103 | height: exec[2] 104 | } 105 | } 106 | }, 107 | colors: { 108 | regex: /#([0-9a-f]{3,})\:#([0-9a-f]{3,})/i, 109 | output: function (val) { 110 | var exec = this.regex.exec(val); 111 | return { 112 | size: settings.themes.gray.size, 113 | foreground: "#" + exec[2], 114 | background: "#" + exec[1] 115 | } 116 | } 117 | }, 118 | text: { 119 | regex: /text\:(.*)/, 120 | output: function (val) { 121 | return this.regex.exec(val)[1]; 122 | } 123 | }, 124 | font: { 125 | regex: /font\:(.*)/, 126 | output: function (val) { 127 | return this.regex.exec(val)[1]; 128 | } 129 | }, 130 | auto: { 131 | regex: /^auto$/ 132 | }, 133 | textmode: { 134 | regex: /textmode\:(.*)/, 135 | output: function(val){ 136 | return this.regex.exec(val)[1]; 137 | } 138 | } 139 | } 140 | 141 | function text_size(width, height, template) { 142 | height = parseInt(height, 10); 143 | width = parseInt(width, 10); 144 | var bigSide = Math.max(height, width) 145 | var smallSide = Math.min(height, width) 146 | var scale = 1 / 12; 147 | var newHeight = Math.min(smallSide * 0.75, 0.75 * bigSide * scale); 148 | return { 149 | height: Math.round(Math.max(template.size, newHeight)) 150 | } 151 | } 152 | 153 | var svg_el = (function(){ 154 | var serializer = new XMLSerializer(); 155 | var svg_ns = "http://www.w3.org/2000/svg" 156 | var svg = document.createElementNS(svg_ns, "svg"); 157 | svg.setAttribute("xmlns", "http://www.w3.org/2000/svg") 158 | svg.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink"); 159 | var bg_el = document.createElementNS(svg_ns, "rect") 160 | var text_el = document.createElementNS(svg_ns, "text") 161 | var textnode_el = document.createTextNode(null) 162 | text_el.setAttribute("text-anchor", "middle") 163 | text_el.appendChild(textnode_el) 164 | svg.appendChild(bg_el) 165 | svg.appendChild(text_el) 166 | 167 | return function(props){ 168 | svg.setAttribute("width",props.width); 169 | svg.setAttribute("height", props.height); 170 | bg_el.setAttribute("width", props.width); 171 | bg_el.setAttribute("height", props.height); 172 | bg_el.setAttribute("fill", props.template.background); 173 | text_el.setAttribute("x", props.width/2) 174 | text_el.setAttribute("y", props.height/2) 175 | textnode_el.nodeValue=props.text 176 | text_el.setAttribute("style", css_properties({ 177 | "fill": props.template.foreground, 178 | "font-weight": "bold", 179 | "font-size": props.text_height+"px", 180 | "font-family":props.font, 181 | "dominant-baseline":"central" 182 | })) 183 | return serializer.serializeToString(svg) 184 | } 185 | })() 186 | 187 | function css_properties(props){ 188 | var ret = []; 189 | for(p in props){ 190 | if(props.hasOwnProperty(p)){ 191 | ret.push(p+":"+props[p]) 192 | } 193 | } 194 | return ret.join(";") 195 | } 196 | 197 | function draw_canvas(args) { 198 | var ctx = args.ctx, 199 | dimensions = args.dimensions, 200 | template = args.template, 201 | ratio = args.ratio, 202 | holder = args.holder, 203 | literal = holder.textmode == "literal", 204 | exact = holder.textmode == "exact"; 205 | 206 | var ts = text_size(dimensions.width, dimensions.height, template); 207 | var text_height = ts.height; 208 | var width = dimensions.width * ratio, 209 | height = dimensions.height * ratio; 210 | var font = template.font ? template.font : "Arial,Helvetica,sans-serif"; 211 | canvas.width = width; 212 | canvas.height = height; 213 | ctx.textAlign = "center"; 214 | ctx.textBaseline = "middle"; 215 | ctx.fillStyle = template.background; 216 | ctx.fillRect(0, 0, width, height); 217 | ctx.fillStyle = template.foreground; 218 | ctx.font = "bold " + text_height + "px " + font; 219 | var text = template.text ? template.text : (Math.floor(dimensions.width) + "x" + Math.floor(dimensions.height)); 220 | if (literal) { 221 | var dimensions = holder.dimensions; 222 | text = dimensions.width + "x" + dimensions.height; 223 | } 224 | else if(exact && holder.exact_dimensions){ 225 | var dimensions = holder.exact_dimensions; 226 | text = (Math.floor(dimensions.width) + "x" + Math.floor(dimensions.height)); 227 | } 228 | var text_width = ctx.measureText(text).width; 229 | if (text_width / width >= 0.75) { 230 | text_height = Math.floor(text_height * 0.75 * (width / text_width)); 231 | } 232 | //Resetting font size if necessary 233 | ctx.font = "bold " + (text_height * ratio) + "px " + font; 234 | ctx.fillText(text, (width / 2), (height / 2), width); 235 | return canvas.toDataURL("image/png"); 236 | } 237 | 238 | function draw_svg(args){ 239 | var dimensions = args.dimensions, 240 | template = args.template, 241 | holder = args.holder, 242 | literal = holder.textmode == "literal", 243 | exact = holder.textmode == "exact"; 244 | 245 | var ts = text_size(dimensions.width, dimensions.height, template); 246 | var text_height = ts.height; 247 | var width = dimensions.width, 248 | height = dimensions.height; 249 | 250 | var font = template.font ? template.font : "Arial,Helvetica,sans-serif"; 251 | var text = template.text ? template.text : (Math.floor(dimensions.width) + "x" + Math.floor(dimensions.height)); 252 | 253 | if (literal) { 254 | var dimensions = holder.dimensions; 255 | text = dimensions.width + "x" + dimensions.height; 256 | } 257 | else if(exact && holder.exact_dimensions){ 258 | var dimensions = holder.exact_dimensions; 259 | text = (Math.floor(dimensions.width) + "x" + Math.floor(dimensions.height)); 260 | } 261 | var string = svg_el({ 262 | text: text, 263 | width:width, 264 | height:height, 265 | text_height:text_height, 266 | font:font, 267 | template:template 268 | }) 269 | return "data:image/svg+xml;base64,"+btoa(string); 270 | } 271 | 272 | function draw(args) { 273 | if(instance_config.use_canvas && !instance_config.use_svg){ 274 | return draw_canvas(args); 275 | } 276 | else{ 277 | return draw_svg(args); 278 | } 279 | } 280 | 281 | function render(mode, el, holder, src) { 282 | var dimensions = holder.dimensions, 283 | theme = holder.theme, 284 | text = holder.text ? decodeURIComponent(holder.text) : holder.text; 285 | var dimensions_caption = dimensions.width + "x" + dimensions.height; 286 | theme = (text ? extend(theme, { 287 | text: text 288 | }) : theme); 289 | theme = (holder.font ? extend(theme, { 290 | font: holder.font 291 | }) : theme); 292 | el.setAttribute("data-src", src); 293 | holder.theme = theme; 294 | el.holder_data = holder; 295 | 296 | if (mode == "image") { 297 | el.setAttribute("alt", text ? text : theme.text ? theme.text + " [" + dimensions_caption + "]" : dimensions_caption); 298 | if (instance_config.use_fallback || !holder.auto) { 299 | el.style.width = dimensions.width + "px"; 300 | el.style.height = dimensions.height + "px"; 301 | } 302 | if (instance_config.use_fallback) { 303 | el.style.backgroundColor = theme.background; 304 | } else { 305 | el.setAttribute("src", draw({ctx: ctx, dimensions: dimensions, template: theme, ratio:ratio, holder: holder})); 306 | 307 | if(holder.textmode && holder.textmode == "exact"){ 308 | resizable_images.push(el); 309 | resizable_update(el); 310 | } 311 | 312 | } 313 | } else if (mode == "background") { 314 | if (!instance_config.use_fallback) { 315 | el.style.backgroundImage = "url(" + draw({ctx:ctx, dimensions: dimensions, template: theme, ratio: ratio, holder: holder}) + ")"; 316 | el.style.backgroundSize = dimensions.width + "px " + dimensions.height + "px"; 317 | } 318 | } else if (mode == "fluid") { 319 | el.setAttribute("alt", text ? text : theme.text ? theme.text + " [" + dimensions_caption + "]" : dimensions_caption); 320 | if (dimensions.height.slice(-1) == "%") { 321 | el.style.height = dimensions.height 322 | } else if(holder.auto == null || !holder.auto){ 323 | el.style.height = dimensions.height + "px" 324 | } 325 | if (dimensions.width.slice(-1) == "%") { 326 | el.style.width = dimensions.width 327 | } else if(holder.auto == null || !holder.auto){ 328 | el.style.width = dimensions.width + "px" 329 | } 330 | if (el.style.display == "inline" || el.style.display === "" || el.style.display == "none") { 331 | el.style.display = "block"; 332 | } 333 | 334 | set_initial_dimensions(el) 335 | 336 | if (instance_config.use_fallback) { 337 | el.style.backgroundColor = theme.background; 338 | } else { 339 | resizable_images.push(el); 340 | resizable_update(el); 341 | } 342 | } 343 | } 344 | 345 | function dimension_check(el, callback) { 346 | var dimensions = { 347 | height: el.clientHeight, 348 | width: el.clientWidth 349 | }; 350 | if (!dimensions.height && !dimensions.width) { 351 | el.setAttribute("data-holder-invisible", true) 352 | callback.call(this, el) 353 | } 354 | else{ 355 | el.removeAttribute("data-holder-invisible") 356 | return dimensions; 357 | } 358 | } 359 | 360 | function set_initial_dimensions(el){ 361 | if(el.holder_data){ 362 | var dimensions = dimension_check(el, app.invisible_error_fn( set_initial_dimensions)) 363 | if(dimensions){ 364 | var holder = el.holder_data; 365 | holder.initial_dimensions = dimensions; 366 | holder.fluid_data = { 367 | fluid_height: holder.dimensions.height.slice(-1) == "%", 368 | fluid_width: holder.dimensions.width.slice(-1) == "%", 369 | mode: null 370 | } 371 | if(holder.fluid_data.fluid_width && !holder.fluid_data.fluid_height){ 372 | holder.fluid_data.mode = "width" 373 | holder.fluid_data.ratio = holder.initial_dimensions.width / parseFloat(holder.dimensions.height) 374 | } 375 | else if(!holder.fluid_data.fluid_width && holder.fluid_data.fluid_height){ 376 | holder.fluid_data.mode = "height"; 377 | holder.fluid_data.ratio = parseFloat(holder.dimensions.width) / holder.initial_dimensions.height 378 | } 379 | } 380 | } 381 | } 382 | 383 | function resizable_update(element) { 384 | var images; 385 | if (element.nodeType == null) { 386 | images = resizable_images; 387 | } else { 388 | images = [element] 389 | } 390 | for (var i in images) { 391 | if (!images.hasOwnProperty(i)) { 392 | continue; 393 | } 394 | var el = images[i] 395 | if (el.holder_data) { 396 | var holder = el.holder_data; 397 | var dimensions = dimension_check(el, app.invisible_error_fn( resizable_update)) 398 | if(dimensions){ 399 | if(holder.fluid){ 400 | if(holder.auto){ 401 | switch(holder.fluid_data.mode){ 402 | case "width": 403 | dimensions.height = dimensions.width / holder.fluid_data.ratio; 404 | break; 405 | case "height": 406 | dimensions.width = dimensions.height * holder.fluid_data.ratio; 407 | break; 408 | } 409 | } 410 | el.setAttribute("src", draw({ 411 | ctx: ctx, 412 | dimensions: dimensions, 413 | template: holder.theme, 414 | ratio: ratio, 415 | holder: holder 416 | })) 417 | } 418 | if(holder.textmode && holder.textmode == "exact"){ 419 | holder.exact_dimensions = dimensions; 420 | el.setAttribute("src", draw({ 421 | ctx: ctx, 422 | dimensions: holder.dimensions, 423 | template: holder.theme, 424 | ratio: ratio, 425 | holder: holder 426 | })) 427 | } 428 | } 429 | } 430 | } 431 | } 432 | 433 | function parse_flags(flags, options) { 434 | var ret = { 435 | theme: extend(settings.themes.gray, {}) 436 | }; 437 | var render = false; 438 | for (var fl = flags.length, j = 0; j < fl; j++) { 439 | var flag = flags[j]; 440 | if (app.flags.dimensions.match(flag)) { 441 | render = true; 442 | ret.dimensions = app.flags.dimensions.output(flag); 443 | } else if (app.flags.fluid.match(flag)) { 444 | render = true; 445 | ret.dimensions = app.flags.fluid.output(flag); 446 | ret.fluid = true; 447 | } else if (app.flags.textmode.match(flag)) { 448 | ret.textmode = app.flags.textmode.output(flag) 449 | } else if (app.flags.colors.match(flag)) { 450 | ret.theme = app.flags.colors.output(flag); 451 | } else if (options.themes[flag]) { 452 | //If a theme is specified, it will override custom colors 453 | if(options.themes.hasOwnProperty(flag)){ 454 | ret.theme = extend(options.themes[flag], {}); 455 | } 456 | } else if (app.flags.font.match(flag)) { 457 | ret.font = app.flags.font.output(flag); 458 | } else if (app.flags.auto.match(flag)) { 459 | ret.auto = true; 460 | } else if (app.flags.text.match(flag)) { 461 | ret.text = app.flags.text.output(flag); 462 | } 463 | } 464 | return render ? ret : false; 465 | } 466 | 467 | for (var flag in app.flags) { 468 | if (!app.flags.hasOwnProperty(flag)) continue; 469 | app.flags[flag].match = function (val) { 470 | return val.match(this.regex) 471 | } 472 | } 473 | 474 | app.invisible_error_fn = function(fn){ 475 | return function(el){ 476 | if(el.hasAttribute("data-holder-invisible")){ 477 | throw new Error("Holder: invisible placeholder") 478 | } 479 | } 480 | } 481 | 482 | app.add_theme = function (name, theme) { 483 | name != null && theme != null && (settings.themes[name] = theme); 484 | return app; 485 | }; 486 | 487 | app.add_image = function (src, el) { 488 | var node = selector(el); 489 | if (node.length) { 490 | for (var i = 0, l = node.length; i < l; i++) { 491 | var img = document.createElement("img") 492 | img.setAttribute("data-src", src); 493 | node[i].appendChild(img); 494 | } 495 | } 496 | return app; 497 | }; 498 | 499 | app.run = function (o) { 500 | instance_config = extend({}, system_config) 501 | preempted = true; 502 | 503 | var options = extend(settings, o), 504 | images = [], 505 | imageNodes = [], 506 | bgnodes = []; 507 | 508 | if(options.use_canvas != null && options.use_canvas){ 509 | instance_config.use_canvas = true; 510 | instance_config.use_svg = false; 511 | } 512 | 513 | if (typeof (options.images) == "string") { 514 | imageNodes = selector(options.images); 515 | } else if (window.NodeList && options.images instanceof window.NodeList) { 516 | imageNodes = options.images; 517 | } else if (window.Node && options.images instanceof window.Node) { 518 | imageNodes = [options.images]; 519 | } else if(window.HTMLCollection && options.images instanceof window.HTMLCollection){ 520 | imageNodes = options.images 521 | } 522 | 523 | if (typeof (options.bgnodes) == "string") { 524 | bgnodes = selector(options.bgnodes); 525 | } else if (window.NodeList && options.elements instanceof window.NodeList) { 526 | bgnodes = options.bgnodes; 527 | } else if (window.Node && options.bgnodes instanceof window.Node) { 528 | bgnodes = [options.bgnodes]; 529 | } 530 | for (i = 0, l = imageNodes.length; i < l; i++) images.push(imageNodes[i]); 531 | var holdercss = document.getElementById("holderjs-style"); 532 | if (!holdercss) { 533 | holdercss = document.createElement("style"); 534 | holdercss.setAttribute("id", "holderjs-style"); 535 | holdercss.type = "text/css"; 536 | document.getElementsByTagName("head")[0].appendChild(holdercss); 537 | } 538 | if (!options.nocss) { 539 | if (holdercss.styleSheet) { 540 | holdercss.styleSheet.cssText += options.stylesheet; 541 | } else { 542 | holdercss.appendChild(document.createTextNode(options.stylesheet)); 543 | } 544 | } 545 | var cssregex = new RegExp(options.domain + "\/(.*?)\"?\\)"); 546 | for (var l = bgnodes.length, i = 0; i < l; i++) { 547 | var src = window.getComputedStyle(bgnodes[i], null) 548 | .getPropertyValue("background-image"); 549 | var flags = src.match(cssregex); 550 | var bgsrc = bgnodes[i].getAttribute("data-background-src"); 551 | if (flags) { 552 | var holder = parse_flags(flags[1].split("/"), options); 553 | if (holder) { 554 | render("background", bgnodes[i], holder, src); 555 | } 556 | } else if (bgsrc != null) { 557 | var holder = parse_flags(bgsrc.substr(bgsrc.lastIndexOf(options.domain) + options.domain.length + 1) 558 | .split("/"), options); 559 | if (holder) { 560 | render("background", bgnodes[i], holder, src); 561 | } 562 | } 563 | } 564 | for (l = images.length, i = 0; i < l; i++) { 565 | var attr_data_src, attr_src; 566 | attr_src = attr_data_src = src = null; 567 | try { 568 | attr_src = images[i].getAttribute("src"); 569 | attr_datasrc = images[i].getAttribute("data-src"); 570 | } catch (e) {} 571 | if (attr_datasrc == null && !! attr_src && attr_src.indexOf(options.domain) >= 0) { 572 | src = attr_src; 573 | } else if ( !! attr_datasrc && attr_datasrc.indexOf(options.domain) >= 0) { 574 | src = attr_datasrc; 575 | } 576 | if (src) { 577 | var holder = parse_flags(src.substr(src.lastIndexOf(options.domain) + options.domain.length + 1).split("/"), options); 578 | if (holder) { 579 | if (holder.fluid) { 580 | render("fluid", images[i], holder, src) 581 | } else { 582 | render("image", images[i], holder, src); 583 | } 584 | } 585 | } 586 | } 587 | return app; 588 | }; 589 | 590 | contentLoaded(win, function () { 591 | if (window.addEventListener) { 592 | window.addEventListener("resize", resizable_update, false); 593 | window.addEventListener("orientationchange", resizable_update, false); 594 | } else { 595 | window.attachEvent("onresize", resizable_update) 596 | } 597 | preempted || app.run({}); 598 | }); 599 | if (typeof define === "function" && define.amd) { 600 | define([], function () { 601 | return app; 602 | }); 603 | } 604 | 605 | //github.com/davidchambers/Base64.js 606 | (function(){function t(t){this.message=t}var e="undefined"!=typeof exports?exports:this,r="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";t.prototype=Error(),t.prototype.name="InvalidCharacterError",e.btoa||(e.btoa=function(e){for(var o,n,a=0,i=r,c="";e.charAt(0|a)||(i="=",a%1);c+=i.charAt(63&o>>8-8*(a%1))){if(n=e.charCodeAt(a+=.75),n>255)throw new t("'btoa' failed");o=o<<8|n}return c}),e.atob||(e.atob=function(e){if(e=e.replace(/=+$/,""),1==e.length%4)throw new t("'atob' failed");for(var o,n,a=0,i=0,c="";n=e.charAt(i++);~n&&(o=a%4?64*o+n:n,a++%4)?c+=String.fromCharCode(255&o>>(6&-2*a)):0)n=r.indexOf(n);return c})})(); 607 | 608 | //getElementsByClassName polyfill 609 | document.getElementsByClassName||(document.getElementsByClassName=function(e){var t=document,n,r,i,s=[];if(t.querySelectorAll)return t.querySelectorAll("."+e);if(t.evaluate){r=".//*[contains(concat(' ', @class, ' '), ' "+e+" ')]",n=t.evaluate(r,t,null,0,null);while(i=n.iterateNext())s.push(i)}else{n=t.getElementsByTagName("*"),r=new RegExp("(^|\\s)"+e+"(\\s|$)");for(i=0;i 1) { 46 | clearInterval(timeInterval); 47 | console.log("Stopping time update - CTF has ended"); 48 | } 49 | }, 1000); 50 | } 51 | else { 52 | // remove time container 53 | $('#time-container').parent().remove(); 54 | $('#clocks').removeClass('medium-block-grid-3').removeClass('small-block-grid-2') 55 | .addClass('medium-block-grid-2').addClass('small-block-grid-1'); 56 | } 57 | 58 | var usersMetr = new Metr($('#users-container'), { 59 | background: true, 60 | colors: ['#0000D0', '#0000A0'], 61 | progress: [users['online'] / users['total'] * 100, 100] 62 | }); 63 | var pointsMetr = new Metr($('#points-container'), { 64 | background: true, 65 | colors: ['#00D000', '#00A000'], 66 | progress: [points['earned'] / points['total'] * 100, points['in_progress'] / points['total'] * 100] 67 | }); 68 | 69 | 70 | 71 | }); 72 | 73 | $.post('/.challenge-visit', {url: location.pathname}); 74 | }); -------------------------------------------------------------------------------- /competition/static/js/ctftools.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | $('section[role=main]').find('form').submit(function(event) { 3 | event.preventDefault(); 4 | var data = $(this).serialize(); 5 | var resultId = this.id.replace('-form', '-result'); 6 | $.post(this.action, data, function(jdata){ 7 | $('#' + resultId).text(jdata['result']); 8 | }); 9 | }); 10 | }); -------------------------------------------------------------------------------- /competition/static/js/lib/fastclick.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @preserve FastClick: polyfill to remove click delays on browsers with touch UIs. 3 | * 4 | * @version 1.0.0 5 | * @codingstandard ftlabs-jsv2 6 | * @copyright The Financial Times Limited [All Rights Reserved] 7 | * @license MIT License (see LICENSE.txt) 8 | */ 9 | function FastClick(a){"use strict";function b(a,b){return function(){return a.apply(b,arguments)}}var c;this.trackingClick=!1,this.trackingClickStart=0,this.targetElement=null,this.touchStartX=0,this.touchStartY=0,this.lastTouchIdentifier=0,this.touchBoundary=10,this.layer=a,FastClick.notNeeded(a)||(deviceIsAndroid&&(a.addEventListener("mouseover",b(this.onMouse,this),!0),a.addEventListener("mousedown",b(this.onMouse,this),!0),a.addEventListener("mouseup",b(this.onMouse,this),!0)),a.addEventListener("click",b(this.onClick,this),!0),a.addEventListener("touchstart",b(this.onTouchStart,this),!1),a.addEventListener("touchmove",b(this.onTouchMove,this),!1),a.addEventListener("touchend",b(this.onTouchEnd,this),!1),a.addEventListener("touchcancel",b(this.onTouchCancel,this),!1),Event.prototype.stopImmediatePropagation||(a.removeEventListener=function(b,c,d){var e=Node.prototype.removeEventListener;"click"===b?e.call(a,b,c.hijacked||c,d):e.call(a,b,c,d)},a.addEventListener=function(b,c,d){var e=Node.prototype.addEventListener;"click"===b?e.call(a,b,c.hijacked||(c.hijacked=function(a){a.propagationStopped||c(a)}),d):e.call(a,b,c,d)}),"function"==typeof a.onclick&&(c=a.onclick,a.addEventListener("click",function(a){c(a)},!1),a.onclick=null))}var deviceIsAndroid=navigator.userAgent.indexOf("Android")>0,deviceIsIOS=/iP(ad|hone|od)/.test(navigator.userAgent),deviceIsIOS4=deviceIsIOS&&/OS 4_\d(_\d)?/.test(navigator.userAgent),deviceIsIOSWithBadTarget=deviceIsIOS&&/OS ([6-9]|\d{2})_\d/.test(navigator.userAgent);FastClick.prototype.needsClick=function(a){"use strict";switch(a.nodeName.toLowerCase()){case"button":case"select":case"textarea":if(a.disabled)return!0;break;case"input":if(deviceIsIOS&&"file"===a.type||a.disabled)return!0;break;case"label":case"video":return!0}return/\bneedsclick\b/.test(a.className)},FastClick.prototype.needsFocus=function(a){"use strict";switch(a.nodeName.toLowerCase()){case"textarea":return!0;case"select":return!deviceIsAndroid;case"input":switch(a.type){case"button":case"checkbox":case"file":case"image":case"radio":case"submit":return!1}return!a.disabled&&!a.readOnly;default:return/\bneedsfocus\b/.test(a.className)}},FastClick.prototype.sendClick=function(a,b){"use strict";var c,d;document.activeElement&&document.activeElement!==a&&document.activeElement.blur(),d=b.changedTouches[0],c=document.createEvent("MouseEvents"),c.initMouseEvent(this.determineEventType(a),!0,!0,window,1,d.screenX,d.screenY,d.clientX,d.clientY,!1,!1,!1,!1,0,null),c.forwardedTouchEvent=!0,a.dispatchEvent(c)},FastClick.prototype.determineEventType=function(a){"use strict";return deviceIsAndroid&&"select"===a.tagName.toLowerCase()?"mousedown":"click"},FastClick.prototype.focus=function(a){"use strict";var b;deviceIsIOS&&a.setSelectionRange&&0!==a.type.indexOf("date")&&"time"!==a.type?(b=a.value.length,a.setSelectionRange(b,b)):a.focus()},FastClick.prototype.updateScrollParent=function(a){"use strict";var b,c;if(b=a.fastClickScrollParent,!b||!b.contains(a)){c=a;do{if(c.scrollHeight>c.offsetHeight){b=c,a.fastClickScrollParent=c;break}c=c.parentElement}while(c)}b&&(b.fastClickLastScrollTop=b.scrollTop)},FastClick.prototype.getTargetElementFromEventTarget=function(a){"use strict";return a.nodeType===Node.TEXT_NODE?a.parentNode:a},FastClick.prototype.onTouchStart=function(a){"use strict";var b,c,d;if(a.targetTouches.length>1)return!0;if(b=this.getTargetElementFromEventTarget(a.target),c=a.targetTouches[0],deviceIsIOS){if(d=window.getSelection(),d.rangeCount&&!d.isCollapsed)return!0;if(!deviceIsIOS4){if(c.identifier===this.lastTouchIdentifier)return a.preventDefault(),!1;this.lastTouchIdentifier=c.identifier,this.updateScrollParent(b)}}return this.trackingClick=!0,this.trackingClickStart=a.timeStamp,this.targetElement=b,this.touchStartX=c.pageX,this.touchStartY=c.pageY,a.timeStamp-this.lastClickTime<200&&a.preventDefault(),!0},FastClick.prototype.touchHasMoved=function(a){"use strict";var b=a.changedTouches[0],c=this.touchBoundary;return Math.abs(b.pageX-this.touchStartX)>c||Math.abs(b.pageY-this.touchStartY)>c?!0:!1},FastClick.prototype.onTouchMove=function(a){"use strict";return this.trackingClick?((this.targetElement!==this.getTargetElementFromEventTarget(a.target)||this.touchHasMoved(a))&&(this.trackingClick=!1,this.targetElement=null),!0):!0},FastClick.prototype.findControl=function(a){"use strict";return void 0!==a.control?a.control:a.htmlFor?document.getElementById(a.htmlFor):a.querySelector("button, input:not([type=hidden]), keygen, meter, output, progress, select, textarea")},FastClick.prototype.onTouchEnd=function(a){"use strict";var b,c,d,e,f,g=this.targetElement;if(!this.trackingClick)return!0;if(a.timeStamp-this.lastClickTime<200)return this.cancelNextClick=!0,!0;if(this.cancelNextClick=!1,this.lastClickTime=a.timeStamp,c=this.trackingClickStart,this.trackingClick=!1,this.trackingClickStart=0,deviceIsIOSWithBadTarget&&(f=a.changedTouches[0],g=document.elementFromPoint(f.pageX-window.pageXOffset,f.pageY-window.pageYOffset)||g,g.fastClickScrollParent=this.targetElement.fastClickScrollParent),d=g.tagName.toLowerCase(),"label"===d){if(b=this.findControl(g)){if(this.focus(g),deviceIsAndroid)return!1;g=b}}else if(this.needsFocus(g))return a.timeStamp-c>100||deviceIsIOS&&window.top!==window&&"input"===d?(this.targetElement=null,!1):(this.focus(g),this.sendClick(g,a),deviceIsIOS4&&"select"===d||(this.targetElement=null,a.preventDefault()),!1);return deviceIsIOS&&!deviceIsIOS4&&(e=g.fastClickScrollParent,e&&e.fastClickLastScrollTop!==e.scrollTop)?!0:(this.needsClick(g)||(a.preventDefault(),this.sendClick(g,a)),!1)},FastClick.prototype.onTouchCancel=function(){"use strict";this.trackingClick=!1,this.targetElement=null},FastClick.prototype.onMouse=function(a){"use strict";return this.targetElement?a.forwardedTouchEvent?!0:a.cancelable&&(!this.needsClick(this.targetElement)||this.cancelNextClick)?(a.stopImmediatePropagation?a.stopImmediatePropagation():a.propagationStopped=!0,a.stopPropagation(),a.preventDefault(),!1):!0:!0},FastClick.prototype.onClick=function(a){"use strict";var b;return this.trackingClick?(this.targetElement=null,this.trackingClick=!1,!0):"submit"===a.target.type&&0===a.detail?!0:(b=this.onMouse(a),b||(this.targetElement=null),b)},FastClick.prototype.destroy=function(){"use strict";var a=this.layer;deviceIsAndroid&&(a.removeEventListener("mouseover",this.onMouse,!0),a.removeEventListener("mousedown",this.onMouse,!0),a.removeEventListener("mouseup",this.onMouse,!0)),a.removeEventListener("click",this.onClick,!0),a.removeEventListener("touchstart",this.onTouchStart,!1),a.removeEventListener("touchmove",this.onTouchMove,!1),a.removeEventListener("touchend",this.onTouchEnd,!1),a.removeEventListener("touchcancel",this.onTouchCancel,!1)},FastClick.notNeeded=function(a){"use strict";var b,c;if("undefined"==typeof window.ontouchstart)return!0;if(c=+(/Chrome\/([0-9]+)/.exec(navigator.userAgent)||[,0])[1]){if(!deviceIsAndroid)return!0;if(b=document.querySelector("meta[name=viewport]")){if(-1!==b.content.indexOf("user-scalable=no"))return!0;if(c>31&&window.innerWidth<=window.screen.width)return!0}}return"none"===a.style.msTouchAction?!0:!1},FastClick.attach=function(a){"use strict";return new FastClick(a)},"undefined"!=typeof define&&define.amd?define(function(){"use strict";return FastClick}):"undefined"!=typeof module&&module.exports?(module.exports=FastClick.attach,module.exports.FastClick=FastClick):window.FastClick=FastClick; 10 | -------------------------------------------------------------------------------- /competition/static/js/lib/jquery.cookie.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * jQuery Cookie Plugin v1.4.0 3 | * https://github.com/carhartl/jquery-cookie 4 | * 5 | * Copyright 2013 Klaus Hartl 6 | * Released under the MIT license 7 | */ 8 | !function(a){"function"==typeof define&&define.amd?define(["jquery"],a):a(jQuery)}(function(a){function b(a){return h.raw?a:encodeURIComponent(a)}function c(a){return h.raw?a:decodeURIComponent(a)}function d(a){return b(h.json?JSON.stringify(a):String(a))}function e(a){0===a.indexOf('"')&&(a=a.slice(1,-1).replace(/\\"/g,'"').replace(/\\\\/g,"\\"));try{a=decodeURIComponent(a.replace(g," "))}catch(b){return}try{return h.json?JSON.parse(a):a}catch(b){}}function f(b,c){var d=h.raw?b:e(b);return a.isFunction(c)?c(d):d}var g=/\+/g,h=a.cookie=function(e,g,i){if(void 0!==g&&!a.isFunction(g)){if(i=a.extend({},h.defaults,i),"number"==typeof i.expires){var j=i.expires,k=i.expires=new Date;k.setDate(k.getDate()+j)}return document.cookie=[b(e),"=",d(g),i.expires?"; expires="+i.expires.toUTCString():"",i.path?"; path="+i.path:"",i.domain?"; domain="+i.domain:"",i.secure?"; secure":""].join("")}for(var l=e?void 0:{},m=document.cookie?document.cookie.split("; "):[],n=0,o=m.length;o>n;n++){var p=m[n].split("="),q=c(p.shift()),r=p.join("=");if(e&&e===q){l=f(r,g);break}e||void 0===(r=f(r))||(l[q]=r)}return l};h.defaults={},a.removeCookie=function(b,c){return void 0!==a.cookie(b)?(a.cookie(b,"",a.extend({},c,{expires:-1})),!0):!1}}); 9 | -------------------------------------------------------------------------------- /competition/static/js/lib/modernizr.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Modernizr v2.7.2 3 | * www.modernizr.com 4 | * 5 | * Copyright (c) Faruk Ates, Paul Irish, Alex Sexton 6 | * Available under the BSD and MIT licenses: www.modernizr.com/license/ 7 | */ 8 | window.Modernizr=function(a,b,c){function d(a){t.cssText=a}function e(a,b){return d(x.join(a+";")+(b||""))}function f(a,b){return typeof a===b}function g(a,b){return!!~(""+a).indexOf(b)}function h(a,b){for(var d in a){var e=a[d];if(!g(e,"-")&&t[e]!==c)return"pfx"==b?e:!0}return!1}function i(a,b,d){for(var e in a){var g=b[a[e]];if(g!==c)return d===!1?a[e]:f(g,"function")?g.bind(d||b):g}return!1}function j(a,b,c){var d=a.charAt(0).toUpperCase()+a.slice(1),e=(a+" "+z.join(d+" ")+d).split(" ");return f(b,"string")||f(b,"undefined")?h(e,b):(e=(a+" "+A.join(d+" ")+d).split(" "),i(e,b,c))}function k(){o.input=function(c){for(var d=0,e=c.length;e>d;d++)E[c[d]]=!!(c[d]in u);return E.list&&(E.list=!(!b.createElement("datalist")||!a.HTMLDataListElement)),E}("autocomplete autofocus list placeholder max min multiple pattern required step".split(" ")),o.inputtypes=function(a){for(var d,e,f,g=0,h=a.length;h>g;g++)u.setAttribute("type",e=a[g]),d="text"!==u.type,d&&(u.value=v,u.style.cssText="position:absolute;visibility:hidden;",/^range$/.test(e)&&u.style.WebkitAppearance!==c?(q.appendChild(u),f=b.defaultView,d=f.getComputedStyle&&"textfield"!==f.getComputedStyle(u,null).WebkitAppearance&&0!==u.offsetHeight,q.removeChild(u)):/^(search|tel)$/.test(e)||(d=/^(url|email)$/.test(e)?u.checkValidity&&u.checkValidity()===!1:u.value!=v)),D[a[g]]=!!d;return D}("search tel url email datetime date month week time datetime-local number range color".split(" "))}var l,m,n="2.7.2",o={},p=!0,q=b.documentElement,r="modernizr",s=b.createElement(r),t=s.style,u=b.createElement("input"),v=":)",w={}.toString,x=" -webkit- -moz- -o- -ms- ".split(" "),y="Webkit Moz O ms",z=y.split(" "),A=y.toLowerCase().split(" "),B={svg:"http://www.w3.org/2000/svg"},C={},D={},E={},F=[],G=F.slice,H=function(a,c,d,e){var f,g,h,i,j=b.createElement("div"),k=b.body,l=k||b.createElement("body");if(parseInt(d,10))for(;d--;)h=b.createElement("div"),h.id=e?e[d]:r+(d+1),j.appendChild(h);return f=["­",'"].join(""),j.id=r,(k?j:l).innerHTML+=f,l.appendChild(j),k||(l.style.background="",l.style.overflow="hidden",i=q.style.overflow,q.style.overflow="hidden",q.appendChild(l)),g=c(j,a),k?j.parentNode.removeChild(j):(l.parentNode.removeChild(l),q.style.overflow=i),!!g},I=function(b){var c=a.matchMedia||a.msMatchMedia;if(c)return c(b).matches;var d;return H("@media "+b+" { #"+r+" { position: absolute; } }",function(b){d="absolute"==(a.getComputedStyle?getComputedStyle(b,null):b.currentStyle).position}),d},J=function(){function a(a,e){e=e||b.createElement(d[a]||"div"),a="on"+a;var g=a in e;return g||(e.setAttribute||(e=b.createElement("div")),e.setAttribute&&e.removeAttribute&&(e.setAttribute(a,""),g=f(e[a],"function"),f(e[a],"undefined")||(e[a]=c),e.removeAttribute(a))),e=null,g}var d={select:"input",change:"input",submit:"form",reset:"form",error:"img",load:"img",abort:"img"};return a}(),K={}.hasOwnProperty;m=f(K,"undefined")||f(K.call,"undefined")?function(a,b){return b in a&&f(a.constructor.prototype[b],"undefined")}:function(a,b){return K.call(a,b)},Function.prototype.bind||(Function.prototype.bind=function(a){var b=this;if("function"!=typeof b)throw new TypeError;var c=G.call(arguments,1),d=function(){if(this instanceof d){var e=function(){};e.prototype=b.prototype;var f=new e,g=b.apply(f,c.concat(G.call(arguments)));return Object(g)===g?g:f}return b.apply(a,c.concat(G.call(arguments)))};return d}),C.flexbox=function(){return j("flexWrap")},C.flexboxlegacy=function(){return j("boxDirection")},C.canvas=function(){var a=b.createElement("canvas");return!(!a.getContext||!a.getContext("2d"))},C.canvastext=function(){return!(!o.canvas||!f(b.createElement("canvas").getContext("2d").fillText,"function"))},C.webgl=function(){return!!a.WebGLRenderingContext},C.touch=function(){var c;return"ontouchstart"in a||a.DocumentTouch&&b instanceof DocumentTouch?c=!0:H(["@media (",x.join("touch-enabled),("),r,")","{#modernizr{top:9px;position:absolute}}"].join(""),function(a){c=9===a.offsetTop}),c},C.geolocation=function(){return"geolocation"in navigator},C.postmessage=function(){return!!a.postMessage},C.websqldatabase=function(){return!!a.openDatabase},C.indexedDB=function(){return!!j("indexedDB",a)},C.hashchange=function(){return J("hashchange",a)&&(b.documentMode===c||b.documentMode>7)},C.history=function(){return!(!a.history||!history.pushState)},C.draganddrop=function(){var a=b.createElement("div");return"draggable"in a||"ondragstart"in a&&"ondrop"in a},C.websockets=function(){return"WebSocket"in a||"MozWebSocket"in a},C.rgba=function(){return d("background-color:rgba(150,255,150,.5)"),g(t.backgroundColor,"rgba")},C.hsla=function(){return d("background-color:hsla(120,40%,100%,.5)"),g(t.backgroundColor,"rgba")||g(t.backgroundColor,"hsla")},C.multiplebgs=function(){return d("background:url(https://),url(https://),red url(https://)"),/(url\s*\(.*?){3}/.test(t.background)},C.backgroundsize=function(){return j("backgroundSize")},C.borderimage=function(){return j("borderImage")},C.borderradius=function(){return j("borderRadius")},C.boxshadow=function(){return j("boxShadow")},C.textshadow=function(){return""===b.createElement("div").style.textShadow},C.opacity=function(){return e("opacity:.55"),/^0.55$/.test(t.opacity)},C.cssanimations=function(){return j("animationName")},C.csscolumns=function(){return j("columnCount")},C.cssgradients=function(){var a="background-image:",b="gradient(linear,left top,right bottom,from(#9f9),to(white));",c="linear-gradient(left top,#9f9, white);";return d((a+"-webkit- ".split(" ").join(b+a)+x.join(c+a)).slice(0,-a.length)),g(t.backgroundImage,"gradient")},C.cssreflections=function(){return j("boxReflect")},C.csstransforms=function(){return!!j("transform")},C.csstransforms3d=function(){var a=!!j("perspective");return a&&"webkitPerspective"in q.style&&H("@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}",function(b){a=9===b.offsetLeft&&3===b.offsetHeight}),a},C.csstransitions=function(){return j("transition")},C.fontface=function(){var a;return H('@font-face {font-family:"font";src:url("https://")}',function(c,d){var e=b.getElementById("smodernizr"),f=e.sheet||e.styleSheet,g=f?f.cssRules&&f.cssRules[0]?f.cssRules[0].cssText:f.cssText||"":"";a=/src/i.test(g)&&0===g.indexOf(d.split(" ")[0])}),a},C.generatedcontent=function(){var a;return H(["#",r,"{font:0/0 a}#",r,':after{content:"',v,'";visibility:hidden;font:3px/1 a}'].join(""),function(b){a=b.offsetHeight>=3}),a},C.video=function(){var a=b.createElement("video"),c=!1;try{(c=!!a.canPlayType)&&(c=new Boolean(c),c.ogg=a.canPlayType('video/ogg; codecs="theora"').replace(/^no$/,""),c.h264=a.canPlayType('video/mp4; codecs="avc1.42E01E"').replace(/^no$/,""),c.webm=a.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/,""))}catch(d){}return c},C.audio=function(){var a=b.createElement("audio"),c=!1;try{(c=!!a.canPlayType)&&(c=new Boolean(c),c.ogg=a.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),c.mp3=a.canPlayType("audio/mpeg;").replace(/^no$/,""),c.wav=a.canPlayType('audio/wav; codecs="1"').replace(/^no$/,""),c.m4a=(a.canPlayType("audio/x-m4a;")||a.canPlayType("audio/aac;")).replace(/^no$/,""))}catch(d){}return c},C.localstorage=function(){try{return localStorage.setItem(r,r),localStorage.removeItem(r),!0}catch(a){return!1}},C.sessionstorage=function(){try{return sessionStorage.setItem(r,r),sessionStorage.removeItem(r),!0}catch(a){return!1}},C.webworkers=function(){return!!a.Worker},C.applicationcache=function(){return!!a.applicationCache},C.svg=function(){return!!b.createElementNS&&!!b.createElementNS(B.svg,"svg").createSVGRect},C.inlinesvg=function(){var a=b.createElement("div");return a.innerHTML="",(a.firstChild&&a.firstChild.namespaceURI)==B.svg},C.smil=function(){return!!b.createElementNS&&/SVGAnimate/.test(w.call(b.createElementNS(B.svg,"animate")))},C.svgclippaths=function(){return!!b.createElementNS&&/SVGClipPath/.test(w.call(b.createElementNS(B.svg,"clipPath")))};for(var L in C)m(C,L)&&(l=L.toLowerCase(),o[l]=C[L](),F.push((o[l]?"":"no-")+l));return o.input||k(),o.addTest=function(a,b){if("object"==typeof a)for(var d in a)m(a,d)&&o.addTest(d,a[d]);else{if(a=a.toLowerCase(),o[a]!==c)return o;b="function"==typeof b?b():b,"undefined"!=typeof p&&p&&(q.className+=" "+(b?"":"no-")+a),o[a]=b}return o},d(""),s=u=null,function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=s.elements;return"string"==typeof a?a.split(" "):a}function e(a){var b=r[a[p]];return b||(b={},q++,a[p]=q,r[q]=b),b}function f(a,c,d){if(c||(c=b),k)return c.createElement(a);d||(d=e(c));var f;return f=d.cache[a]?d.cache[a].cloneNode():o.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!f.canHaveChildren||n.test(a)||f.tagUrn?f:d.frag.appendChild(f)}function g(a,c){if(a||(a=b),k)return a.createDocumentFragment();c=c||e(a);for(var f=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)f.createElement(h[g]);return f}function h(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return s.shivMethods?f(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(s,b.frag)}function i(a){a||(a=b);var d=e(a);return!s.shivCSS||j||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),k||h(a,d),a}var j,k,l="3.7.0",m=a.html5||{},n=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,o=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,p="_html5shiv",q=0,r={};!function(){try{var a=b.createElement("a");a.innerHTML="",j="hidden"in a,k=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){j=!0,k=!0}}();var s={elements:m.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output progress section summary template time video",version:l,shivCSS:m.shivCSS!==!1,supportsUnknownElements:k,shivMethods:m.shivMethods!==!1,type:"default",shivDocument:i,createElement:f,createDocumentFragment:g};a.html5=s,i(b)}(this,b),o._version=n,o._prefixes=x,o._domPrefixes=A,o._cssomPrefixes=z,o.mq=I,o.hasEvent=J,o.testProp=function(a){return h([a])},o.testAllProps=j,o.testStyles=H,o.prefixed=function(a,b,c){return b?j(a,b,c):j(a,"pfx")},q.className=q.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(p?" js "+F.join(" "):""),o}(this,this.document); 9 | -------------------------------------------------------------------------------- /competition/static/js/lib/placeholder.js: -------------------------------------------------------------------------------- 1 | /*! http://mths.be/placeholder v2.0.7 by @mathias */ 2 | !function(a,b,c){function d(a){var b={},d=/^jQuery\d+$/;return c.each(a.attributes,function(a,c){c.specified&&!d.test(c.name)&&(b[c.name]=c.value)}),b}function e(a,d){var e=this,f=c(e);if(e.value==f.attr("placeholder")&&f.hasClass("placeholder"))if(f.data("placeholder-password")){if(f=f.hide().next().show().attr("id",f.removeAttr("id").data("placeholder-id")),a===!0)return f[0].value=d;f.focus()}else e.value="",f.removeClass("placeholder"),e==b.activeElement&&e.select()}function f(){var a,b=this,f=c(b),g=this.id;if(""==b.value){if("password"==b.type){if(!f.data("placeholder-textinput")){try{a=f.clone().attr({type:"text"})}catch(h){a=c("").attr(c.extend(d(this),{type:"text"}))}a.removeAttr("name").data({"placeholder-password":!0,"placeholder-id":g}).bind("focus.placeholder",e),f.data({"placeholder-textinput":a,"placeholder-id":g}).before(a)}f=f.removeAttr("id").hide().prev().attr("id",g).show()}f.addClass("placeholder"),f[0].value=f.attr("placeholder")}else f.removeClass("placeholder")}var g,h,i="placeholder"in b.createElement("input"),j="placeholder"in b.createElement("textarea"),k=c.fn,l=c.valHooks;i&&j?(h=k.placeholder=function(){return this},h.input=h.textarea=!0):(h=k.placeholder=function(){var a=this;return a.filter((i?"textarea":":input")+"[placeholder]").not(".placeholder").bind({"focus.placeholder":e,"blur.placeholder":f}).data("placeholder-enabled",!0).trigger("blur.placeholder"),a},h.input=i,h.textarea=j,g={get:function(a){var b=c(a);return b.data("placeholder-enabled")&&b.hasClass("placeholder")?"":a.value},set:function(a,d){var g=c(a);return g.data("placeholder-enabled")?(""==d?(a.value=d,a!=b.activeElement&&f.call(a)):g.hasClass("placeholder")?e.call(a,!0,d)||(a.value=d):a.value=d,g):a.value=d}},i||(l.input=g),j||(l.textarea=g),c(function(){c(b).delegate("form","submit.placeholder",function(){var a=c(".placeholder",this).each(e);setTimeout(function(){a.each(f)},10)})}),c(a).bind("beforeunload.placeholder",function(){c(".placeholder").each(function(){this.value=""})}))}(this,document,jQuery); 3 | -------------------------------------------------------------------------------- /competition/static/js/metr.js: -------------------------------------------------------------------------------- 1 | /* Metr by Jordan Werthman. 2014. 2 | 3 | This program is free software: you can redistribute it and/or modify 4 | it under the terms of the GNU General Public License as published by 5 | the Free Software Foundation, either version 3 of the License, or 6 | (at your option) any later version. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | GNU General Public License for more details. 12 | 13 | You should have received a copy of the GNU General Public License 14 | along with this program. If not, see . 15 | */ 16 | 17 | /* 18 | Metr is a circular counter. 19 | 20 | opts: colors - array two colors to use 21 | backgound - boolean display bg 22 | progress - array two values from 0-100 23 | representing progress 24 | width - int width of Metr 25 | height - int height of Metr 26 | */ 27 | 28 | function Metr(parent, opts) { 29 | opts = opts || {}; 30 | 31 | this._fgColors = opts.colors || ['#00D000', '#00A000']; 32 | this._background = opts.background; 33 | this._progress = opts.progress || [100, 100]; 34 | this._width = opts.width || 200; 35 | this._height = opts.height || 200; 36 | 37 | this._context = this.init(parent); 38 | this.draw(); 39 | } 40 | 41 | Metr.prototype.init = function(parent) { 42 | var canvas = document.createElement('canvas'); 43 | canvas.width = this._width; 44 | canvas.height = this._height; 45 | 46 | parent.append(canvas); 47 | return canvas.getContext('2d'); 48 | }; 49 | 50 | Metr.prototype.draw = function() { 51 | var ctx = this._context; 52 | var cX = this._width / 2; 53 | var cY = this._height / 2; 54 | var sW = this._width * 0.15, 55 | cR = this._width * 0.2; 56 | 57 | var pr = this._progress; 58 | var fg = this._fgColors; 59 | 60 | ctx.clearRect(0, 0, this._width, this._height); 61 | 62 | for (var i=fg.length - 1; i>=0; --i) { 63 | var radius = cR + (i+1) * sW; 64 | if (this._background) { 65 | ctx.fillStyle = "#000000"; 66 | ctx.beginPath(); 67 | ctx.arc(cX, cY, radius - 0.5, 0, 2*Math.PI); 68 | ctx.closePath(); 69 | ctx.fill(); 70 | } 71 | 72 | ctx.fillStyle = fg[i]; 73 | ctx.beginPath(); 74 | ctx.moveTo(cX, cY); 75 | ctx.arc(cX, cY, radius, 3/2 * Math.PI, 3/2 * Math.PI + 2 * Math.PI * pr[i] / 100); 76 | ctx.closePath(); 77 | ctx.fill(); 78 | } 79 | 80 | ctx.fillStyle = "#000000"; 81 | ctx.beginPath(); 82 | ctx.arc(cX, cY, cR, 0, 2*Math.PI); 83 | ctx.closePath(); 84 | ctx.fill(); 85 | }; 86 | 87 | Metr.prototype.update = function(progress) { 88 | this._progress = progress; 89 | this.draw(); 90 | }; 91 | -------------------------------------------------------------------------------- /competition/static/js/site.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var $deleteModal = $('.delete-modal'); 3 | $deleteModal.find('input[type=reset]').on('click', function (event) { 4 | event.preventDefault(); 5 | $deleteModal.foundation('reveal', 'close'); 6 | }); 7 | })(); -------------------------------------------------------------------------------- /competition/templates/404.html: -------------------------------------------------------------------------------- 1 | {% extends '40x-50x.html' %} 2 | {% load static %} 3 | {% block error_num %}404{% endblock %} 4 | {% block content %} 5 |
6 |

404 Page Not Found

7 | 8 | 9 |

Doge you want to go back?

10 |
11 | {% endblock %} 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /competition/templates/40x-50x.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | {% block error_num %}{% endblock %} Error - CollabCTF 13 | 14 | 20 | 21 | 22 | {% block content %}{% endblock %} 23 | 24 | -------------------------------------------------------------------------------- /competition/templates/500.html: -------------------------------------------------------------------------------- 1 | {% extends '40x-50x.html' %} 2 | {% load static %} 3 | {% block error_num %}500{% endblock %} 4 | {% block content %} 5 |
6 |

500 Internal Server Error

7 | 8 | 9 |

Doge you want me to put out the fires?

10 |
11 | {% endblock %} -------------------------------------------------------------------------------- /competition/templates/base.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | CollabCTF {% block subtitle %}{% endblock %} 13 | 14 | 15 | 16 | 17 | 18 | 19 | 45 |
46 |
47 | {% if user.is_authenticated %} 48 | {% include 'sidebar.html' %} 49 | {% endif %} 50 | {% block content %}{% endblock %} 51 |
52 |
53 | {% block scripts %} 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | {% endblock %} 62 | {% block extra_scripts %}{% endblock %} 63 | 64 | -------------------------------------------------------------------------------- /competition/templates/breadcrumbs.html: -------------------------------------------------------------------------------- 1 | {# what a mess. #} 2 | -------------------------------------------------------------------------------- /competition/templates/ctf/add.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load static %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block subtitle %}- Overview{% endblock %} 6 | {% block addctfoverview_class %}active{% endblock %} 7 | 8 | {% block content %} 9 |
10 |

Add Competition

11 | 12 |
13 |
14 | {% crispy form form.add_helper %} 15 |
16 |
17 |
18 | {% endblock %} -------------------------------------------------------------------------------- /competition/templates/ctf/challenge/add.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load crispy_forms_tags %} 3 | 4 | {% block subtitle %}- Add Challenge{% endblock %} 5 | {% block content %} 6 |
7 |

{{ ctf.name }} Add Challenge

8 | 9 |
10 |
11 | {% crispy form form.add_helper %} 12 |
13 |
14 |
15 | {% endblock %} -------------------------------------------------------------------------------- /competition/templates/ctf/challenge/deleted.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block subtitle %}- {{ challenge.name }}{% endblock %} 3 | {% block content %} 4 |
5 |

Challenge Deleted

6 | 7 |
8 |
9 |

{{ challenge.name }} has been deleted. You wil be redirected to the CTF overview in 5 seconds.

10 | 11 |
12 |
13 |
14 | {% endblock %} -------------------------------------------------------------------------------- /competition/templates/ctf/challenge/files/add.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load crispy_forms_tags %} 3 | 4 | {% block subtitle %}- Add Challenge{% endblock %} 5 | {% block content %} 6 |
7 |

{{ challenge.name }} Upload file

8 | 9 |
10 |
11 | {% crispy form form.add_helper %} 12 |
13 |
14 |
15 | {% endblock %} -------------------------------------------------------------------------------- /competition/templates/ctf/challenge/overview.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load static %} 3 | 4 | {% block subtitle %}- {{ challenge.name }}{% endblock %} 5 | {% block ctfchallenge_class %}active{% endblock %} 6 | {% block content %} 7 | 8 |
9 | {% with challenge.get_progress_display as status %} 10 |

{{ challenge.name }} 11 | {{ status }} 12 | 13 | 14 | × 15 | 16 |

17 | {% endwith %} 18 | {% include 'breadcrumbs.html' with state='overview' %} 19 | 20 |

Files

21 | {% if files %} 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | {% for upfile in files %} 33 | 34 | 35 | 36 | 37 | 38 | 39 | {% endfor %} 40 | 41 | 42 | 43 | 44 |
FilenameUpload TimeUploaded ByLink
{{ upfile.filename }}{{ upfile.ctime }}Sample UserDownload
Upload New File
45 | {% else %} 46 |

No files uploaded. Click here to add a file.

47 | {% endif %} 48 |

Collaborative Document

49 |

Your online collaborative document for this challenge is available here. 50 |

51 |
52 |
53 |

Challenge Deletion Confirmation

54 |

Are you sure that you want to delete {{ ctf.name }}?

55 |
56 | {% csrf_token %} 57 | 58 | 59 | × 60 |
61 |
62 | {% endblock %} 63 | 64 | {% block extra_scripts %} 65 | 66 | {% endblock %} -------------------------------------------------------------------------------- /competition/templates/ctf/challenge/update.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load crispy_forms_tags %} 3 | 4 | {% block subtitle %}- {{ challenge.name }}{% endblock %} 5 | {% block content %} 6 |
7 |

{{ challenge.name }} Update

8 | {% include 'breadcrumbs.html' with state='update' %} 9 | 10 |
11 |
12 | {% crispy form form.update_helper %} 13 |
14 |
15 |
16 | {% endblock %} 17 | {% block extra_scripts %} 18 | 19 | {% endblock %} -------------------------------------------------------------------------------- /competition/templates/ctf/overview.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load static %} 3 | 4 | {% block subtitle %}- Overview{% endblock %} 5 | {% block ctfoverview_class %}active{% endblock %} 6 | {% block content %} 7 |
8 |

{{ ctf.name }} 9 | 10 | × 11 |

12 | {% include 'breadcrumbs.html' with state='overview' %} 13 | 14 |
    15 |
  • 16 |
    17 |

    Challenges Solved

    18 | Percent for {{ ctf.name }} 19 |
  • 20 |
  • 21 |
    22 |

    Time Remaining

    23 | Until the end of {{ ctf.name }} 24 |
  • 25 |
  • 26 |
    27 |

    Points Earned

    28 | Percent by {{ ctf.name }} 29 |
  • 30 |
31 | 32 |
33 | {% if ctf.url %} 34 |
URL
35 |
{{ ctf.url }}
36 | {% endif %} 37 | {% if ctf.start_time %} 38 |
Start time
39 |
{{ ctf.start_time }}
40 | {% endif %} 41 | {% if ctf.end_time %} 42 |
End time
43 |
{{ ctf.end_time }}
44 | {% endif %} 45 |
46 | 47 |

Challenges

48 | 49 | {% if challenges %} 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | {% for chall in challenges %} 61 | 62 | 63 | 64 | 65 | 66 | {% endfor %} 67 | 68 | 71 | 72 | 73 |
Challenge IDStatusLast viewed
{{ chall.name }}{{ chall.get_progress_display }}{{ chall.last_viewed }}
69 | Add a challenge 70 |
74 | {% else %} 75 |
76 |
77 |

No challenges found. Click here to add a challenge.

78 |
79 |
80 | {% endif %} 81 |
82 | 83 |
84 |

CTF Deletion Confirmation

85 | 86 |

Are you sure that you want to delete {{ ctf.name }}?

87 | 88 |
89 | {% csrf_token %} 90 | 91 | 92 | × 93 |
94 |
95 | {% endblock %} 96 | {% block extra_scripts %} 97 | 98 | {% endblock %} -------------------------------------------------------------------------------- /competition/templates/ctf/removed.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block subtitle %}- {{ ctf.name }}{% endblock %} 3 | {% block content %} 4 |
5 |

CTF Deleted

6 | 7 |
8 |
9 |

{{ ctf.name }} has been deleted. You wil be redirected to the index in 5 seconds.

10 | 11 |
12 |
13 |
14 | {% endblock %} -------------------------------------------------------------------------------- /competition/templates/ctf/update.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load static %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block subtitle %}- {{ ctf.name }}{% endblock %} 6 | {% block ctfoverview_class %}active{% endblock %} 7 | 8 | {% block content %} 9 |
10 |

{{ ctf.name }}

11 | {% include 'breadcrumbs.html' with state='update' %} 12 | 13 |
14 |
15 | {% if saved %} 16 |
17 | {{ ctf.name }} has been updated accordingly. 18 | × 19 |
20 | {% endif %} 21 | {% crispy form form.update_helper %} 22 |
23 |
24 |
25 | {% endblock %} -------------------------------------------------------------------------------- /competition/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load static %} 3 | 4 | {% block subtitle %}{% endblock %} 5 | {% block overview_class %}active{% endblock %} 6 | {% block dashboard_class %}active{% endblock %} 7 | {% block content %} 8 |
9 |

Dashboard

10 | 11 |

Welcome to CollabCTF!

12 | {% if recently_viewed %} 13 |
14 |

Recently Viewed Challenges

15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | {% for challenge in recently_viewed %} 27 | 28 | 29 | 30 | 31 | 32 | {% endfor %} 33 | 34 |
ChallengeStatusLast Viewed
{{ challenge.name }}{{ challenge.get_progress_display }}{{ challenge.last_viewed }}
35 |
36 |
37 | 38 |
39 |

Recently Solved Challenges

40 | {% if recently_solved %} 41 |
42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | {% for challenge in recently_solved %} 52 | 53 | 54 | 55 | 56 | 57 | {% endfor %} 58 |
ChallengeStatusLast Viewed
{{ challenge.name }}{{ challenge.get_progress_display }}{{ challenge.last_viewed }}
59 |
60 | {% else %} 61 |

No challenges solved!

62 | {% endif %} 63 |
64 | {% else %} 65 |

No challenges have been added. Please add a CTF and/or a challenge!

66 | {% endif %} 67 |
68 | 69 | {% endblock %} 70 | {% block extra_scripts %} 71 | 77 | {% endblock %} -------------------------------------------------------------------------------- /competition/templates/login.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load static %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block subtitle %}- Challenge{% endblock %} 6 | {% block login_class %}active{% endblock %} 7 | {% block content %} 8 |
9 |

Login

10 | {% crispy login_form %} 11 |
12 | {% endblock %} -------------------------------------------------------------------------------- /competition/templates/profile.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load static %} 3 | 4 | {% block subtitle %}- Reports{% endblock %} 5 | {% block profile_class %}active{% endblock %} 6 | {% block content %} 7 | 8 |
9 |

Your Profile

10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
Your UsernameYour NameYour EmailDate Joined
{{ user.username }}{{ user.first_name }} {{ user.last_name }}{{ user.email }}{{ user.date_joined }}
30 |
31 |
32 | {% endblock %} -------------------------------------------------------------------------------- /competition/templates/register.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load crispy_forms_tags %} 3 | 4 | {% block subtitle %}- Registration{% endblock %} 5 | {% block content %} 6 |
7 |

Register

8 | {% crispy register_form %} 9 |
10 | {% endblock %} -------------------------------------------------------------------------------- /competition/templates/registration_locked.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block subtitle %}- Registration{% endblock %} 3 | {% block content %} 4 |
5 |

Registration Locked

6 |

Web-based registration has been locked by the sysops. Please contact them for help or further information.

7 |
8 | {% endblock %} -------------------------------------------------------------------------------- /competition/templates/reports.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load static %} 3 | 4 | {% block subtitle %}- Reports{% endblock %} 5 | {% block reports_class %}active{% endblock %} 6 | {% block content %} 7 |
8 |

CTF Summary Report

9 | 10 |
11 | {% for ctf in ctfs %} 12 |
13 |

{{ ctf.name }}

14 | 15 |
    16 |
  • 17 |
    18 |

    Challenges Solved

    19 | Percent for CTF 20 |
  • 21 |
  • 22 |
    23 |

    Points Earned

    24 | Percent by CTF 25 |
  • 26 |
27 |
28 | {% endfor %} 29 |
30 | 31 |
32 | {% endblock %} 33 | {% block extra_scripts %} 34 | 62 | {% endblock %} -------------------------------------------------------------------------------- /competition/templates/settings.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load static %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block subtitle %}- Settings{% endblock %} 6 | {% block settings_class %}active{% endblock %} 7 | {% block content %} 8 | 9 |
10 |

Settings

11 | {% crispy password_form %} 12 | 13 |
14 | {% endblock %} -------------------------------------------------------------------------------- /competition/templates/sidebar.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | -------------------------------------------------------------------------------- /competition/templates/tools.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load static %} 3 | {% load crispy_forms_tags %} 4 | 5 | {% block subtitle %}- Tools{% endblock %} 6 | {% block ctftools_class %}active{% endblock %} 7 | {% block content %} 8 |
9 |

CTF Tools

10 | 11 |
12 | 18 | 23 |
24 | 25 |
    26 |
  • {% crispy hash_form %}
  • 27 |
  • {% crispy rot_form %}
  • 28 |
  • {% crispy xor_form %}
  • 29 |
  • {% crispy base_conversion_form %}
  • 30 |
  • {% crispy quote_form %}
  • 31 |
  • {% crispy unquote_form %}
  • 32 |
33 |
34 | {% endblock %} 35 | {% block extra_scripts %} 36 | 37 | {% endblock %} -------------------------------------------------------------------------------- /competition/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | 5 | -------------------------------------------------------------------------------- /competition/views.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | import hashlib 3 | from django.conf import settings 4 | from django.contrib.auth.decorators import login_required 5 | 6 | from django.shortcuts import render_to_response, get_object_or_404, redirect 7 | from django.template import RequestContext 8 | from django.utils.text import slugify 9 | from django.views.decorators.http import require_safe, require_http_methods, require_POST 10 | 11 | from competition.forms import CompetitionModelForm, ChallengeModelForm, ChallengeFileModelForm 12 | from competition.models import Competition, Challenge, ChallengeFile 13 | 14 | 15 | @login_required 16 | @require_http_methods(['GET', 'POST']) 17 | def add_ctf(request): 18 | if request.method == 'GET': 19 | form = CompetitionModelForm() 20 | data = { 21 | 'form': form 22 | } 23 | return render_to_response('ctf/add.html', data, RequestContext(request)) 24 | 25 | elif request.method == 'POST': 26 | form = CompetitionModelForm(request.POST) 27 | data = { 28 | 'form': form 29 | } 30 | 31 | if form.is_valid(): 32 | competition = form.save(commit=False) 33 | competition.slug = slugify(competition.name) 34 | competition.save() 35 | data['competition'] = competition 36 | return redirect(competition.get_absolute_url()) 37 | 38 | # url tbd 39 | return render_to_response('ctf/add.html', data, RequestContext(request)) 40 | 41 | 42 | @login_required 43 | @require_http_methods(['GET', 'POST']) 44 | def update_ctf(request, ctf_slug): 45 | ctf = get_object_or_404(Competition.objects, slug=ctf_slug) 46 | if request.method == 'GET': 47 | data = { 48 | 'ctf': ctf, 49 | 'form': CompetitionModelForm(instance=ctf) 50 | } 51 | return render_to_response('ctf/update.html', data, RequestContext(request)) 52 | 53 | elif request.method == 'POST': 54 | form = CompetitionModelForm(request.POST, instance=ctf) 55 | saved = False 56 | if form.is_valid(): 57 | form.save() 58 | saved = True 59 | 60 | data = { 61 | 'ctf': ctf, 62 | 'form': form, 63 | 'saved': saved 64 | } 65 | return render_to_response('ctf/update.html', data, RequestContext(request)) 66 | 67 | 68 | @login_required 69 | @require_safe 70 | def view_ctf(request, ctf_slug): 71 | ctf = get_object_or_404(Competition.objects.prefetch_related('challenges'), slug=ctf_slug) 72 | challenges = ctf.challenges.all() 73 | data = { 74 | 'ctf': ctf, 75 | 'challenges': challenges 76 | } 77 | 78 | return render_to_response('ctf/overview.html', data, RequestContext(request)) 79 | 80 | 81 | @login_required 82 | @require_POST 83 | def delete_ctf(request, ctf_slug): 84 | ctf = get_object_or_404(Competition.objects, slug=ctf_slug) 85 | data = { 86 | 'ctf': ctf 87 | } 88 | response = render_to_response('ctf/removed.html', data) 89 | ctf.delete() 90 | return response 91 | 92 | 93 | @login_required 94 | @require_http_methods(['GET', 'POST']) 95 | def add_challenge(request, ctf_slug): 96 | ctf = get_object_or_404(Competition.objects, slug=ctf_slug) 97 | if request.method == 'GET': 98 | form = ChallengeModelForm(initial={'competition': ctf}) 99 | data = { 100 | 'form': form, 101 | 'ctf': ctf 102 | } 103 | return render_to_response('ctf/challenge/add.html', data, RequestContext(request)) 104 | 105 | elif request.method == 'POST': 106 | form = ChallengeModelForm(request.POST) 107 | data = { 108 | 'form': form, 109 | 'ctf': ctf 110 | } 111 | 112 | if form.is_valid(): 113 | challenge = form.save(commit=False) 114 | challenge.competition = ctf 115 | challenge.last_viewed = datetime.now() 116 | challenge.slug = slugify(challenge.name) 117 | challenge.save() 118 | return redirect(ctf.get_absolute_url()) 119 | 120 | return render_to_response('ctf/challenge/add.html', data, RequestContext(request)) 121 | 122 | 123 | @login_required 124 | @require_safe 125 | def view_challenge(request, ctf_slug, chall_slug): 126 | ctf = get_object_or_404(Competition.objects, slug=ctf_slug) 127 | challenge = get_object_or_404(Challenge.objects.prefetch_related('files'), competition=ctf, slug=chall_slug) 128 | 129 | data = { 130 | 'ctf': ctf, 131 | 'challenge': challenge, 132 | 'files': challenge.files.all(), 133 | 'hash': hashlib.sha1(challenge.name.encode('utf8') + settings.SECRET_KEY.encode('utf8')).hexdigest() 134 | } 135 | 136 | return render_to_response('ctf/challenge/overview.html', data, RequestContext(request)) 137 | 138 | 139 | @login_required 140 | @require_http_methods(['GET', 'POST']) 141 | def update_challenge(request, ctf_slug, chall_slug): 142 | ctf = get_object_or_404(Competition.objects, slug=ctf_slug) 143 | challenge = get_object_or_404(Challenge.objects, competition=ctf, slug=chall_slug) 144 | if request.method == 'GET': 145 | data = { 146 | 'ctf': ctf, 147 | 'challenge': challenge, 148 | 'form': ChallengeModelForm(instance=challenge) 149 | } 150 | return render_to_response('ctf/challenge/update.html', data, RequestContext(request)) 151 | 152 | elif request.method == 'POST': 153 | form = ChallengeModelForm(request.POST, instance=challenge) 154 | saved = False 155 | if form.is_valid(): 156 | challenge = form.save(commit=False) 157 | challenge.last_viewed = datetime.now() 158 | challenge.save() 159 | saved = True 160 | return redirect(challenge.get_absolute_url()) 161 | 162 | data = { 163 | 'ctf': ctf, 164 | 'challenge': challenge, 165 | 'form': ChallengeModelForm(instance=challenge), 166 | 'saved': saved 167 | } 168 | return render_to_response('ctf/challenge/update.html', data, RequestContext(request)) 169 | 170 | 171 | @login_required 172 | @require_http_methods(['GET', 'POST']) 173 | def delete_challenge(request, ctf_slug, chall_slug): 174 | ctf = get_object_or_404(Competition.objects, slug=ctf_slug) 175 | challenge = get_object_or_404(Challenge.objects, competition=ctf, slug=chall_slug) 176 | data = { 177 | 'ctf': ctf, 178 | 'challenge': challenge 179 | } 180 | response = render_to_response('ctf/challenge/deleted.html', data, RequestContext(request)) 181 | challenge.delete() 182 | return response 183 | 184 | 185 | @login_required 186 | @require_http_methods(['GET', 'POST']) 187 | def add_file(request, ctf_slug, chall_slug): 188 | ctf = get_object_or_404(Competition.objects, slug=ctf_slug) 189 | challenge = get_object_or_404(Challenge.objects, competition=ctf, slug=chall_slug) 190 | 191 | data = { 192 | 'ctf': ctf, 193 | 'challenge': challenge, 194 | } 195 | 196 | if request.method == 'GET': 197 | data['form'] = ChallengeFileModelForm() 198 | return render_to_response('ctf/challenge/files/add.html', data, RequestContext(request)) 199 | elif request.method == 'POST': 200 | form = ChallengeFileModelForm(request.POST, request.FILES) 201 | if form.is_valid(): 202 | upfile = form.save(commit=False) 203 | upfile.challenge = challenge 204 | upfile.ctime = datetime.now() 205 | upfile.mtime = datetime.now() 206 | upfile.save() 207 | return redirect('view_challenge', ctf_slug=ctf_slug, chall_slug=chall_slug) 208 | else: 209 | data['form'] = ChallengeFileModelForm() 210 | return render_to_response('ctf/challenge/files/add.html', data, RequestContext(request)) -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "collabCTF.settings") 7 | 8 | from django.core.management import execute_from_command_line 9 | 10 | execute_from_command_line(sys.argv) 11 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django==1.7.1 2 | Markdown>=2.5.1 3 | django-crispy-forms>=1.4.0 4 | crispy-forms-foundation>=0.3.5 -------------------------------------------------------------------------------- /tools/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackUCF/collabCTF/463f4e5a303b5eb6ae6b5d4aab916e02a3bb2ae0/tools/__init__.py -------------------------------------------------------------------------------- /tools/ajax.py: -------------------------------------------------------------------------------- 1 | import json 2 | import sys 3 | 4 | from django.contrib.auth.decorators import login_required 5 | from django.views.decorators.http import require_POST 6 | 7 | from tools import crypto 8 | from tools.forms import HashForm, RotForm, BaseConversionForm, XORForm, URLUnquoteForm, URLQuoteForm 9 | from tools.misc import JSONResponse, JSONResponseBadRequest 10 | 11 | 12 | if sys.version_info.major == 2: 13 | from urllib import quote as url_quote, unquote as url_unquote 14 | elif sys.version_info.major == 3: 15 | # noinspection PyCompatibility 16 | # noinspection PyUnresolvedReferences 17 | from urllib.parse import quote as url_quote, unquote as url_unquote 18 | 19 | 20 | @login_required 21 | @require_POST 22 | def hash_val(request): 23 | form = HashForm(request.POST) 24 | if form.is_valid(): 25 | cd = form.cleaned_data 26 | value = cd['value'] 27 | if sys.version_info.major == 3: 28 | value = value.encode('utf8') 29 | 30 | jdata = json.dumps({ 31 | 'result': crypto.hash_value(cd['hash_type'], value) 32 | }) 33 | return JSONResponse(jdata) 34 | 35 | else: 36 | jdata = json.dumps({ 37 | 'error': form.errors 38 | }) 39 | return JSONResponseBadRequest(jdata) 40 | 41 | 42 | @login_required 43 | @require_POST 44 | def rot_val(request): 45 | form = RotForm(request.POST) 46 | if form.is_valid(): 47 | cd = form.cleaned_data 48 | jdata = json.dumps({ 49 | 'result': crypto.rot(cd['rot_type'], cd['value'], cd['encode']) 50 | }) 51 | return JSONResponse(jdata) 52 | else: 53 | jdata = json.dumps({ 54 | 'error': form.errors 55 | }) 56 | return JSONResponseBadRequest(jdata) 57 | 58 | 59 | @login_required 60 | @require_POST 61 | def base_conversion_val(request): 62 | form = BaseConversionForm(request.POST) 63 | if form.is_valid(): 64 | cd = form.cleaned_data 65 | jdata = json.dumps({ 66 | 'result': crypto.base_conversions(cd['value'], cd['base'], cd['currBase']) 67 | }) 68 | return JSONResponse(jdata) 69 | else: 70 | jdata = json.dumps({ 71 | 'error': form.errors 72 | }) 73 | return JSONResponseBadRequest(jdata) 74 | 75 | 76 | @login_required 77 | @require_POST 78 | def xor_val(request): 79 | form = XORForm(request.POST) 80 | if form.is_valid(): 81 | cd = form.cleaned_data 82 | jdata = json.dumps({ 83 | 'result': crypto.xor_tool(cd['value'], cd['key']) 84 | }) 85 | return JSONResponse(jdata) 86 | else: 87 | jdata = json.dumps({ 88 | 'error': form.errors 89 | }) 90 | return JSONResponseBadRequest(jdata) 91 | 92 | @login_required 93 | @require_POST 94 | def quote_url(request): 95 | form = URLQuoteForm(request.POST) 96 | if form.is_valid(): 97 | cd = form.cleaned_data 98 | jdata = json.dumps({ 99 | 'result': url_quote(cd['value']) 100 | }) 101 | return JSONResponse(jdata) 102 | else: 103 | jdata = json.dumps({ 104 | 'error': form.errors 105 | }) 106 | return JSONResponseBadRequest(jdata) 107 | 108 | 109 | @login_required 110 | @require_POST 111 | def unquote_url(request): 112 | form = URLUnquoteForm(request.POST) 113 | if form.is_valid(): 114 | cd = form.cleaned_data 115 | jdata = json.dumps({ 116 | 'result': url_unquote(cd['value']) 117 | }) 118 | return JSONResponse(jdata) 119 | else: 120 | jdata = json.dumps({ 121 | 'error': form.errors 122 | }) 123 | return JSONResponseBadRequest(jdata) 124 | 125 | -------------------------------------------------------------------------------- /tools/crypto.py: -------------------------------------------------------------------------------- 1 | __author__ = 'broglea' 2 | 3 | import hashlib 4 | import string 5 | import itertools 6 | 7 | 8 | def hash_value(type=None, value=None): 9 | if type is None: 10 | return 'You must specify a type' 11 | if value is None: 12 | return 'You must specify a value' 13 | if type == 'MD5': 14 | return hashlib.md5(value).hexdigest() 15 | if type == 'SHA1': 16 | return hashlib.sha1(value).hexdigest() 17 | if type == 'SHA256': 18 | return hashlib.sha256(value).hexdigest() 19 | if type == 'SHA512': 20 | return hashlib.sha512(value).hexdigest() 21 | return 'Specified type not supported' 22 | 23 | 24 | # rotational cipher encoder/decoder 25 | def rot(shift, value, encode): 26 | try: 27 | alphabet = string.ascii_lowercase 28 | dic = {} 29 | #If we want to encode this 30 | if encode == "True": 31 | for i in range(0, len(alphabet)): 32 | dic[alphabet[i]] = alphabet[(i + int(shift, 10)) % len(alphabet)] 33 | 34 | #If we want to decode a rotational cipher 35 | else: 36 | for i in range(0, len(alphabet)): 37 | dic[alphabet[i]] = alphabet[(i + (26 - (int(shift, 10) % 26))) % len(alphabet)] 38 | 39 | #Convert each letter of plaintext to the corresponding 40 | #encrypted letter in our dictionary creating the cryptext 41 | ciphertext = "" 42 | for l in value.lower(): 43 | if l in dic: 44 | l = dic[l] 45 | ciphertext += l 46 | 47 | return ciphertext 48 | except: 49 | return "An error occurred" 50 | 51 | 52 | # main base conversion function 53 | def base_conversions(value=None, base=None, currBase=10): 54 | try: 55 | if base is None: 56 | return 'You must specify a base' 57 | if value is None: 58 | return 'You must specify a value' 59 | if base < 2: 60 | return 'Base must be greater than 1' 61 | 62 | base = int(str(base), 10) 63 | currBase = int(str(currBase), 10) 64 | 65 | if currBase == 10: 66 | value = int(str(value), 10) 67 | return int_to_base(value, base) 68 | else: 69 | value = int(str(value), currBase) 70 | return int_to_base(value, base) 71 | except: 72 | return "An error occurred" 73 | 74 | 75 | # converts any integer to any base; only used internally, should never be called from the actual site 76 | def int_to_base(value, base): 77 | try: 78 | alphanum = string.digits + string.ascii_lowercase 79 | 80 | if value < 0: 81 | sign = -1 82 | 83 | elif value == 0: 84 | return '0' 85 | else: 86 | sign = 1 87 | value *= sign 88 | digits = [] 89 | while value: 90 | digits.append(alphanum[value % base]) 91 | value /= base 92 | if sign < 0: 93 | digits.append('-') 94 | digits.reverse() 95 | return ''.join(digits) 96 | except: 97 | return "An error occurred" 98 | 99 | 100 | def xor_tool(val=None, xor_key=None): 101 | if val is None: 102 | return 'You must specify a base' 103 | if xor_key is None: 104 | return 'You must specify a value' 105 | 106 | return ''.join(chr(ord(a) ^ ord(b)) for a, b in zip(val, itertools.cycle(xor_key))) 107 | -------------------------------------------------------------------------------- /tools/forms.py: -------------------------------------------------------------------------------- 1 | from crispy_forms.helper import FormHelper 2 | from crispy_forms_foundation.layout import Layout, Fieldset, ButtonHolder, Submit, Reset 3 | from django import forms 4 | from django.core.urlresolvers import reverse 5 | 6 | 7 | class HashForm(forms.Form): 8 | HASH_CHOICES = ( 9 | ('MD5', 'MD5'), 10 | ('SHA1', 'SHA1'), 11 | ('SHA256', 'SHA256'), 12 | ('SHA512', 'SHA512'), 13 | ) 14 | 15 | hash_type = forms.ChoiceField(choices=HASH_CHOICES) 16 | value = forms.CharField(widget=forms.Textarea(attrs={'rows': 4, 'cols': 40})) 17 | result = forms.CharField(widget=forms.Textarea(attrs={'rows': 4, 'cols': 40, 'id': "hash-result"}), required=False) 18 | 19 | def __init__(self, *args, **kwargs): 20 | super(HashForm, self).__init__(*args, **kwargs) 21 | self.helper = FormHelper() 22 | self.helper.form_id = 'hash-form' 23 | self.helper.form_method = 'post' 24 | self.helper.form_action = reverse("tools_hash") 25 | self.helper.layout = Layout( 26 | Fieldset( 27 | 'Hashing Tools', 28 | 'hash_type', 29 | 'value', 30 | 'result' 31 | ), 32 | ButtonHolder( 33 | Submit('submit', 'Submit'), 34 | Reset('reset', 'Reset'), 35 | css_class='text-right' 36 | ) 37 | ) 38 | 39 | 40 | class RotForm(forms.Form): 41 | ENCODE_CHOICE = ( 42 | ("True", 'Encode'), 43 | ("False", 'Decode') 44 | ) 45 | rot_type = forms.CharField(widget=forms.TextInput(attrs={'size': 2, 'type': 'number'})) 46 | value = forms.CharField(widget=forms.Textarea(attrs={'rows': 4, 'cols': 40})) 47 | encode = forms.ChoiceField(choices=ENCODE_CHOICE, label="") 48 | result = forms.CharField(widget=forms.Textarea(attrs={'rows': 4, 'cols': 40, 'id': "rot-result"}), required=False) 49 | 50 | def __init__(self, *args, **kwargs): 51 | super(RotForm, self).__init__(*args, **kwargs) 52 | self.helper = FormHelper() 53 | self.helper.form_id = 'rot-form' 54 | self.helper.form_method = 'post' 55 | self.helper.form_action = reverse("tools_rot") 56 | self.helper.layout = Layout( 57 | Fieldset( 58 | 'ROT=* Encoder/Decoder', 59 | 'rot_type', 60 | 'value', 61 | 'encode', 62 | 'result' 63 | ), 64 | ButtonHolder( 65 | Submit('submit', 'Submit'), 66 | Reset('reset', 'Reset'), 67 | css_class='text-right' 68 | ) 69 | ) 70 | 71 | 72 | class BaseConversionForm(forms.Form): 73 | value = forms.CharField(widget=forms.Textarea(attrs={'rows': 4, 'cols': 40})) 74 | base = forms.CharField(widget=forms.TextInput(attrs={'size': 2})) 75 | currBase = forms.CharField(widget=forms.TextInput(attrs={'size': 2}), label="Current Base") 76 | result = forms.CharField(widget=forms.Textarea(attrs={'rows': 4, 'cols': 40, 'id': "base-conversion-result"}), 77 | required=False) 78 | 79 | def __init__(self, *args, **kwargs): 80 | super(BaseConversionForm, self).__init__(*args, **kwargs) 81 | self.helper = FormHelper() 82 | self.helper.form_id = 'base-conversion-form' 83 | self.helper.form_method = 'post' 84 | self.helper.form_action = reverse("tools_base_conversion") 85 | self.helper.layout = Layout( 86 | Fieldset( 87 | 'Base Conversion Tool', 88 | 'value', 89 | 'base', 90 | 'currBase', 91 | 'result' 92 | ), 93 | ButtonHolder( 94 | Submit('submit', 'Submit'), 95 | Reset('reset', 'Reset'), 96 | css_class='text-right' 97 | ) 98 | ) 99 | 100 | 101 | class XORForm(forms.Form): 102 | value = forms.CharField(widget=forms.Textarea(attrs={'rows': 4, 'cols': 40})) 103 | key = forms.CharField(widget=forms.TextInput(attrs={'size': 40})) 104 | result = forms.CharField(widget=forms.Textarea(attrs={'rows': 4, 'cols': 40, 'id': "xor-result"}), required=False) 105 | 106 | def __init__(self, *args, **kwargs): 107 | super(XORForm, self).__init__(*args, **kwargs) 108 | self.helper = FormHelper() 109 | self.helper.form_id = 'xor-form' 110 | self.helper.form_method = 'post' 111 | self.helper.form_action = reverse("tools_xor") 112 | self.helper.layout = Layout( 113 | Fieldset( 114 | 'String XOR Tool', 115 | 'value', 116 | 'key', 117 | 'result' 118 | ), 119 | ButtonHolder( 120 | Submit('submit', 'Submit'), 121 | Reset('reset', 'Reset'), 122 | css_class='text-right' 123 | ) 124 | ) 125 | 126 | 127 | class URLQuoteForm(forms.Form): 128 | value = forms.CharField(widget=forms.Textarea(attrs={'rows': 4, 'cols': 40}), label='Plaintext') 129 | result = forms.CharField(widget=forms.Textarea(attrs={'rows': 4, 'cols': 40, 'id': "quote-result"}), 130 | required=False) 131 | 132 | def __init__(self, *args, **kwargs): 133 | super(URLQuoteForm, self).__init__(*args, **kwargs) 134 | self.helper = FormHelper() 135 | self.helper.form_id = 'quote-form' 136 | self.helper.form_method = 'post' 137 | self.helper.form_action = reverse('tools_quote') 138 | self.helper.layout = Layout( 139 | Fieldset( 140 | 'URL Encoding', 141 | 'value', 142 | 'result' 143 | ), 144 | ButtonHolder( 145 | Submit('submit', 'Submit'), 146 | Reset('reset', 'Reset'), 147 | css_class='text-right' 148 | ) 149 | ) 150 | 151 | 152 | class URLUnquoteForm(forms.Form): 153 | value = forms.CharField(widget=forms.Textarea(attrs={'rows': 4, 'cols': 40})) 154 | result = forms.CharField(widget=forms.Textarea(attrs={'rows': 4, 'cols': 40, 'id': "unquote-result"}), 155 | required=False) 156 | 157 | def __init__(self, *args, **kwargs): 158 | super(URLUnquoteForm, self).__init__(*args, **kwargs) 159 | self.helper = FormHelper() 160 | self.helper.form_id = 'unquote-form' 161 | self.helper.form_method = 'post' 162 | self.helper.form_action = reverse('tools_unquote') 163 | self.helper.layout = Layout( 164 | Fieldset( 165 | 'URL Decoding', 166 | 'value', 167 | 'result' 168 | ), 169 | ButtonHolder( 170 | Submit('submit', 'Submit'), 171 | Reset('reset', 'Reset'), 172 | css_class='text-right' 173 | ) 174 | ) -------------------------------------------------------------------------------- /tools/misc.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | from django.http import HttpResponse 3 | 4 | 5 | ZERO = datetime.timedelta(0) 6 | HOUR = datetime.timedelta(hours=1) 7 | 8 | 9 | class UTC(datetime.tzinfo): 10 | """UTC 11 | 12 | Optimized UTC implementation. It unpickles using the single module global 13 | instance defined beneath this class declaration. 14 | """ 15 | zone = "UTC" 16 | 17 | _utcoffset = ZERO 18 | _dst = ZERO 19 | _tzname = zone 20 | 21 | def fromutc(self, dt): 22 | if dt.tzinfo is None: 23 | return self.localize(dt) 24 | return super(utc.__class__, self).fromutc(dt) 25 | 26 | def utcoffset(self, dt): 27 | return ZERO 28 | 29 | def tzname(self, dt): 30 | return "UTC" 31 | 32 | def dst(self, dt): 33 | return ZERO 34 | 35 | def __reduce__(self): 36 | return _UTC, () 37 | 38 | def localize(self, dt, is_dst=False): 39 | '''Convert naive time to local time''' 40 | if dt.tzinfo is not None: 41 | raise ValueError('Not naive datetime (tzinfo is already set)') 42 | return dt.replace(tzinfo=self) 43 | 44 | def normalize(self, dt, is_dst=False): 45 | '''Correct the timezone information on the given datetime''' 46 | if dt.tzinfo is self: 47 | return dt 48 | if dt.tzinfo is None: 49 | raise ValueError('Naive time - no tzinfo set') 50 | return dt.astimezone(self) 51 | 52 | def __repr__(self): 53 | return "" 54 | 55 | def __str__(self): 56 | return "UTC" 57 | 58 | 59 | UTC = utc = UTC() # UTC is a singleton 60 | 61 | 62 | class JSONResponse(HttpResponse): 63 | def __init__(self, *args, **kwargs): 64 | # Content-Type override 65 | if 'content_type' not in kwargs: 66 | kwargs['content_type'] = 'application/json' 67 | super(JSONResponse, self).__init__(*args, **kwargs) 68 | 69 | 70 | class JSONResponseBadRequest(JSONResponse): 71 | status_code = 400 -------------------------------------------------------------------------------- /tools/views.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.decorators import login_required 2 | from django.shortcuts import render_to_response 3 | from django.template import RequestContext 4 | from django.views.decorators.http import require_safe 5 | 6 | from tools.forms import HashForm, RotForm, BaseConversionForm, XORForm, URLQuoteForm, URLUnquoteForm 7 | 8 | 9 | @login_required 10 | @require_safe 11 | def ctf_tools(request): 12 | data = { 13 | 'hash_form': HashForm(), 14 | 'rot_form': RotForm(), 15 | 'base_conversion_form': BaseConversionForm(), 16 | 'xor_form': XORForm(), 17 | 'quote_form': URLQuoteForm(), 18 | 'unquote_form': URLUnquoteForm() 19 | } 20 | return render_to_response('tools.html', data, RequestContext(request)) --------------------------------------------------------------------------------