diff --git a/README.md b/README.md index 747c07a..e50af04 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,10 @@ File & Directory Information This directory holds some code from a recent CTF platform I tried to build on my own. I am using elements from it, so I just snagged to code to be able to cherry-pick off of it and change what I need to get this project rolling. __Do not consider it a part of this project; it is _not_. It is just included as a convenience for me while the BearShop project is still be developed.__ +* [`static/`](static/) + + This folder is required by [Flask] and it is where all "static" files live, like things for [CSS] or images or fonts or [HTML] templates. + * [`schema.sql`](schema.sql) This is the [SQL] schema file that is used to create the database for the webpage. It should create and define tables for things like users, the products on sale, and anything else that may be deemed necessary. @@ -25,10 +29,15 @@ File & Directory Information This is the [`bash`][bash] script that I planned on using to initially create the server. It sets up the database, creates private keys to be used, and modifies a "base" rendition of the server [Python] script to add all of the configuration variables that can be set _in the_ [`setup.sh`](setup.sh) script. +* [`server_base.py`](server_base.py) + + This is actual source code of the web application. __! Remember, it is _not_ what you will actually run to deploy the server; the [`setup.sh`](setup.sh) script will take it and configure it, to create a new, functional `server.py` which is what you will run when you deploy.__ [Python]: https://www.python.org/ [Flask]: http://flask.pocoo.org/ [SQL]: https://en.wikipedia.org/wiki/SQL -[bash]: https://en.wikipedia.org/wiki/Bash_(Unix_shell) \ No newline at end of file +[bash]: https://en.wikipedia.org/wiki/Bash_(Unix_shell) +[CSS]: https://www.w3.org/Style/CSS/Overview.en.html +[HTML]: https://en.wikipedia.org/wiki/HTML \ No newline at end of file diff --git a/ctf_skeleton/server_base.py b/ctf_skeleton/server_base.py new file mode 100644 index 0000000..da05d28 --- /dev/null +++ b/ctf_skeleton/server_base.py @@ -0,0 +1,287 @@ +#!/usr/bin/env python + +from flask import Flask +from flask import render_template, request, session, g, url_for, flash, get_flashed_messages, redirect +import sqlite3 +import json +import sys, os +from colorama import * +import sys +from threading import Thread +from time import sleep + +from uuid import uuid4 + + +from passlib.hash import sha256_crypt +from contextlib import closing + + +debug = True + +init( autoreset = True ) + +if (debug): + + def success( string ): + print Fore.GREEN + Style.BRIGHT + "[+] " + string + + def error( string ): + sys.stderr.write( Fore.RED + Style.BRIGHT + "[-] " + string + "\n" ) + + def warning( string ): + print Fore.YELLOW + "[!] " + string + +else: + def success( string ): pass + def error( string ): pass + def warning( string ): pass + +# =========================================================================== + +DATABASE = '$DATABASE' +CERTIFICATE = '$CERTIFICATE_FILE' +PRIVATE_KEY = '$PRIVATEKEY_FILE' + +SECRET_KEY = 'this_key_needs_to_be_used_for_session_variables' + +if DATABASE == '$DATABASE': + error("This server has not yet been configured with a database file!") + exit(-1) + +if CERTIFICATE == '$CERTIFICATE_FILE': + error("This server has not yet been configured with a certificate!") + exit(-1) + +if PRIVATE_KEY == '$PRIVATEKEY_FILE': + error("This server has not yet been configured with a private key!") + exit(-1) + +app = Flask( __name__ ) + +app.config.from_object(__name__) + +def init_db(): + with closing(connect_db()) as db: + with app.open_resource('schema.sql', mode='r') as f: + db.cursor().executescript(f.read()) + db.commit() + +def connect_db(): + return sqlite3.connect( app.config['DATABASE'] ) + +@app.before_request +def before_request(): + g.db = connect_db() + +@app.teardown_request +def teardown_request(exception): + db = getattr(g, 'db', None) + if db is not None: + db.close() + +# -------------------------------------------------------------------- + +@app.route("/") +def index(): + + if not ( session['logged_in'] ): + return redirect('login') + +@app.route("/login", methods=["GET", "POST"]) +def login(): + + error = "" + if request.method == "POST": + + cur = g.db.execute('select username, password from users') + # username, password_hash + users = dict(( row[0], row[1] ) for row in cur.fetchall()) + + if not request.form['username'] in users.iterkeys(): + error = 'This username is not in the database!' + else: + + if not ( sha256_crypt.verify( request.form['password'], users[request.form['username']] ) ): + error = "Incorrect password!" + else: + + session_login( request.form['username'] ) + + return redirect( "about" ) + + return render_template( 'login.html' ) + + +# @app.route("/register", methods=["GET", "POST"]) +# def register(): + +# cur = g.db.execute('select username from users') + +# usernames = [row[0] for row in cur.fetchall() ] + +# error = "" +# if request.method == "POST": + +# if unicode(request.form['username']) in usernames: +# error = 'This username is already in use!' +# elif (request.form['password'] == ""): +# error = "You must supply a password!" +# elif request.form['password'] != request.form['confirm']: +# error = 'Your passwords do not match!' +# else: + +# # I use this for command-line submission... +# identifier = str(uuid4()) + + +# cur = g.db.execute('insert into users (username, password, solved_challenges, score, last_submission, uuid) values ( ?, ?, ?, ?, ?, ? )', [ +# request.form['username'], +# sha256_crypt.encrypt( request.form['password']), +# "", # No challenges completed +# 0, # no score. +# 0, # no last submission time, +# identifier # and a completely unique idenitifier +# ] ) + +# g.db.commit() + + +# flash("Hello " + request.form['username'] + ", you have successfully registered!") +# session_login( request.form['username'] ) +# return redirect( "challenges" ) + +# return render( 'register.html', error = error ) + + +# @app.route("/scoreboard") +# def scoreboard(): + +# cur = g.db.execute('select username, score from users order by score desc, last_submission asc') +# response = cur.fetchall() + +# users = [ { "username": row[0], "score": row[1] } for row in response] + +# return render("scoreboard.html", users = users ) + +# @app.route("/logout") +# def logout(): + +# session_logout() +# return redirect("about") + +# @app.route("/") +# @app.route("/about") +# def about(): return render("about.html", app_about=configuration['app_about']) + +# @app.route("/challenges") +# def challenges_page(): + +# if not ( session['logged_in'] ): +# return render("login.html", error = "You must log in to be able to see the challenges!") +# try: +# cur = g.db.execute('select uuid from users where username =?', +# [ session['username'],] ) + +# uuid = cur.fetchone()[0] +# except Exception as e: +# print error(e.message) +# uuid = '' + +# return render("challenges.html", challenges = configuration['services'], url=request.url_root, session_value = uuid ) + +# @app.route("/check_answer", methods=["GET", "POST"]) +# def check_answer(): + +# global correct_answers + +# if request.method == "POST": +# if request.form['answer'] in session['solved_challenges']: + +# return json.dumps({'correct': -1}); + +# if ( request.form['answer'] in correct_answers.keys() ): + +# flag = request.form['answer'] + +# new_score = int(session['score']) + correct_answers[flag] +# cur = g.db.execute("update users set score = (?), last_submission = (SELECT strftime('%s')) where username = (?)", [ +# new_score, +# session['username'] +# ] ); + +# session['solved_challenges'].append( request.form['answer'] ) +# session['score'] = new_score +# g.db.commit(); + +# return json.dumps({'correct': 1, 'new_score': new_score}); +# else: +# return json.dumps({'correct': 0}); + +# @app.route("/submit", methods=[ "POST" ]) +# def submit(): + +# global correct_answers + +# if request.method == "POST": + +# if ( request.form['flag'] in correct_answers.keys() ): + +# flag = request.form['flag'] + +# cur = g.db.execute('select score, solved_challenges from users where uuid = (?)', +# [ request.form['uuid'], ]) + + +# current_score, solved_challenges = cur.fetchone() + +# solved_challenges = solved_challenges.split() + +# if ( flag in solved_challenges ): +# return 'You already submitted this flag!\n' + +# print solved_challenges + +# new_score = current_score + correct_answers[flag] +# solved_challenges.append( flag + " " ) +# cur = g.db.execute("update users set score = (?), last_submission = (SELECT strftime('%s')), solved_challenges = (?) where uuid = (?)", [ +# new_score, +# ' '.join(solved_challenges), +# request.form['uuid'] +# ] ); + +# # session['solved_challenges'].append( request.form['flag'] ) +# session['score'] = new_score +# g.db.commit(); + +# # return json.dumps({'correct': 1, 'new_score': new_score}); +# return 'Correct!\n'; +# else: +# # return json.dumps({'correct': 0}); +# return 'Incorrect!\n'; + +def session_login( username ): + + flash("You were successfully logged in!") + + # cur = g.db.execute('select solved_challenges, score from users where username = (?)', + # [username]) + + # solved_challenges, score = cur.fetchone() + + session['logged_in'] = True + # session['username'] = username + # session['score'] = score + # session['solved_challenges'] = [] + +def session_logout(): + + flash("You have been successfully logged out.") + + session['logged_in'] = False + # session.pop('username') + # session.pop('score') + +if ( __name__ == "__main__" ): + context = (CERTIFICATE, PRIVATE_KEY) + app.run( host="0.0.0.0", debug=False, ssl_context=context, port = 444 ) \ No newline at end of file diff --git a/server_base.py b/server_base.py new file mode 100644 index 0000000..da05d28 --- /dev/null +++ b/server_base.py @@ -0,0 +1,287 @@ +#!/usr/bin/env python + +from flask import Flask +from flask import render_template, request, session, g, url_for, flash, get_flashed_messages, redirect +import sqlite3 +import json +import sys, os +from colorama import * +import sys +from threading import Thread +from time import sleep + +from uuid import uuid4 + + +from passlib.hash import sha256_crypt +from contextlib import closing + + +debug = True + +init( autoreset = True ) + +if (debug): + + def success( string ): + print Fore.GREEN + Style.BRIGHT + "[+] " + string + + def error( string ): + sys.stderr.write( Fore.RED + Style.BRIGHT + "[-] " + string + "\n" ) + + def warning( string ): + print Fore.YELLOW + "[!] " + string + +else: + def success( string ): pass + def error( string ): pass + def warning( string ): pass + +# =========================================================================== + +DATABASE = '$DATABASE' +CERTIFICATE = '$CERTIFICATE_FILE' +PRIVATE_KEY = '$PRIVATEKEY_FILE' + +SECRET_KEY = 'this_key_needs_to_be_used_for_session_variables' + +if DATABASE == '$DATABASE': + error("This server has not yet been configured with a database file!") + exit(-1) + +if CERTIFICATE == '$CERTIFICATE_FILE': + error("This server has not yet been configured with a certificate!") + exit(-1) + +if PRIVATE_KEY == '$PRIVATEKEY_FILE': + error("This server has not yet been configured with a private key!") + exit(-1) + +app = Flask( __name__ ) + +app.config.from_object(__name__) + +def init_db(): + with closing(connect_db()) as db: + with app.open_resource('schema.sql', mode='r') as f: + db.cursor().executescript(f.read()) + db.commit() + +def connect_db(): + return sqlite3.connect( app.config['DATABASE'] ) + +@app.before_request +def before_request(): + g.db = connect_db() + +@app.teardown_request +def teardown_request(exception): + db = getattr(g, 'db', None) + if db is not None: + db.close() + +# -------------------------------------------------------------------- + +@app.route("/") +def index(): + + if not ( session['logged_in'] ): + return redirect('login') + +@app.route("/login", methods=["GET", "POST"]) +def login(): + + error = "" + if request.method == "POST": + + cur = g.db.execute('select username, password from users') + # username, password_hash + users = dict(( row[0], row[1] ) for row in cur.fetchall()) + + if not request.form['username'] in users.iterkeys(): + error = 'This username is not in the database!' + else: + + if not ( sha256_crypt.verify( request.form['password'], users[request.form['username']] ) ): + error = "Incorrect password!" + else: + + session_login( request.form['username'] ) + + return redirect( "about" ) + + return render_template( 'login.html' ) + + +# @app.route("/register", methods=["GET", "POST"]) +# def register(): + +# cur = g.db.execute('select username from users') + +# usernames = [row[0] for row in cur.fetchall() ] + +# error = "" +# if request.method == "POST": + +# if unicode(request.form['username']) in usernames: +# error = 'This username is already in use!' +# elif (request.form['password'] == ""): +# error = "You must supply a password!" +# elif request.form['password'] != request.form['confirm']: +# error = 'Your passwords do not match!' +# else: + +# # I use this for command-line submission... +# identifier = str(uuid4()) + + +# cur = g.db.execute('insert into users (username, password, solved_challenges, score, last_submission, uuid) values ( ?, ?, ?, ?, ?, ? )', [ +# request.form['username'], +# sha256_crypt.encrypt( request.form['password']), +# "", # No challenges completed +# 0, # no score. +# 0, # no last submission time, +# identifier # and a completely unique idenitifier +# ] ) + +# g.db.commit() + + +# flash("Hello " + request.form['username'] + ", you have successfully registered!") +# session_login( request.form['username'] ) +# return redirect( "challenges" ) + +# return render( 'register.html', error = error ) + + +# @app.route("/scoreboard") +# def scoreboard(): + +# cur = g.db.execute('select username, score from users order by score desc, last_submission asc') +# response = cur.fetchall() + +# users = [ { "username": row[0], "score": row[1] } for row in response] + +# return render("scoreboard.html", users = users ) + +# @app.route("/logout") +# def logout(): + +# session_logout() +# return redirect("about") + +# @app.route("/") +# @app.route("/about") +# def about(): return render("about.html", app_about=configuration['app_about']) + +# @app.route("/challenges") +# def challenges_page(): + +# if not ( session['logged_in'] ): +# return render("login.html", error = "You must log in to be able to see the challenges!") +# try: +# cur = g.db.execute('select uuid from users where username =?', +# [ session['username'],] ) + +# uuid = cur.fetchone()[0] +# except Exception as e: +# print error(e.message) +# uuid = '' + +# return render("challenges.html", challenges = configuration['services'], url=request.url_root, session_value = uuid ) + +# @app.route("/check_answer", methods=["GET", "POST"]) +# def check_answer(): + +# global correct_answers + +# if request.method == "POST": +# if request.form['answer'] in session['solved_challenges']: + +# return json.dumps({'correct': -1}); + +# if ( request.form['answer'] in correct_answers.keys() ): + +# flag = request.form['answer'] + +# new_score = int(session['score']) + correct_answers[flag] +# cur = g.db.execute("update users set score = (?), last_submission = (SELECT strftime('%s')) where username = (?)", [ +# new_score, +# session['username'] +# ] ); + +# session['solved_challenges'].append( request.form['answer'] ) +# session['score'] = new_score +# g.db.commit(); + +# return json.dumps({'correct': 1, 'new_score': new_score}); +# else: +# return json.dumps({'correct': 0}); + +# @app.route("/submit", methods=[ "POST" ]) +# def submit(): + +# global correct_answers + +# if request.method == "POST": + +# if ( request.form['flag'] in correct_answers.keys() ): + +# flag = request.form['flag'] + +# cur = g.db.execute('select score, solved_challenges from users where uuid = (?)', +# [ request.form['uuid'], ]) + + +# current_score, solved_challenges = cur.fetchone() + +# solved_challenges = solved_challenges.split() + +# if ( flag in solved_challenges ): +# return 'You already submitted this flag!\n' + +# print solved_challenges + +# new_score = current_score + correct_answers[flag] +# solved_challenges.append( flag + " " ) +# cur = g.db.execute("update users set score = (?), last_submission = (SELECT strftime('%s')), solved_challenges = (?) where uuid = (?)", [ +# new_score, +# ' '.join(solved_challenges), +# request.form['uuid'] +# ] ); + +# # session['solved_challenges'].append( request.form['flag'] ) +# session['score'] = new_score +# g.db.commit(); + +# # return json.dumps({'correct': 1, 'new_score': new_score}); +# return 'Correct!\n'; +# else: +# # return json.dumps({'correct': 0}); +# return 'Incorrect!\n'; + +def session_login( username ): + + flash("You were successfully logged in!") + + # cur = g.db.execute('select solved_challenges, score from users where username = (?)', + # [username]) + + # solved_challenges, score = cur.fetchone() + + session['logged_in'] = True + # session['username'] = username + # session['score'] = score + # session['solved_challenges'] = [] + +def session_logout(): + + flash("You have been successfully logged out.") + + session['logged_in'] = False + # session.pop('username') + # session.pop('score') + +if ( __name__ == "__main__" ): + context = (CERTIFICATE, PRIVATE_KEY) + app.run( host="0.0.0.0", debug=False, ssl_context=context, port = 444 ) \ No newline at end of file diff --git a/setup.sh b/setup.sh old mode 100644 new mode 100755 diff --git a/static/css/master.css b/static/css/master.css new file mode 100644 index 0000000..2920d0b --- /dev/null +++ b/static/css/master.css @@ -0,0 +1,109 @@ +html{ + height: 100%; + width: 100%; +} + + +@font-face { + font-family: 'Share Tech'; + src: url('../fonts/Share Tech.ttf'); +} + +body{ + font-family: 'Share Tech'; + + margin: 0px 0px; + padding: 0px 0px; + background-color: gray; + + /* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#0043af+0,0056ce+46,ffb76b+52,ffa73d+57,ff7c00+88,ff7f04+100 */ +background: #0043af; /* Old browsers */ +background: -moz-linear-gradient(top, #0043af 0%, #0056ce 46%, #ffb76b 52%, #ffa73d 57%, #ff7c00 88%, #ff7f04 100%); /* FF3.6-15 */ +background: -webkit-linear-gradient(top, #0043af 0%,#0056ce 46%,#ffb76b 52%,#ffa73d 57%,#ff7c00 88%,#ff7f04 100%); /* Chrome10-25,Safari5.1-6 */ +background: linear-gradient(to bottom, #0043af 0%,#0056ce 46%,#ffb76b 52%,#ffa73d 57%,#ff7c00 88%,#ff7f04 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ +filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#0043af', endColorstr='#ff7f04',GradientType=0 ); /* IE6-9 */ +} + + +#login_box_background{ + max-width: 600px; + max-height: 400px; + + padding: 2%; + + position: absolute; + top:0; + bottom: 0; + left: 0; + right: 0; + + margin: auto; + background-color: white; + opacity: .1; + +} + +#login_box{ + /*font-family: 'Share Tech';*/ + /*background-color: white;*/ + color: white; + font-size: larger; + /*margin-top: 30%;*/ + border-top: 1px solid black; + border-bottom: 1px solid black; + max-width: 600px; + max-height: 400px; + + padding: 2%; + + position: absolute; + top:0; + bottom: 0; + left: 0; + right: 0; + + margin: auto; + + /*width: 100%;*/ + + /*padding: 10%;*/ +} +input{ + display: block; + margin:auto; + width: 90%; + height: 40px; + font-size: x-large; + padding: 5px; +} +h1{ + + border-bottom: 1px solid white; +} +#login{ + + text-transform: uppercase; + text-align: center; + width: 95%; + height: 60px; + background-color: red; + border: none; + display: block; + font-family: 'Share Tech'; + font-size: xx-large; + color: white; + /*vertical-align: middle;*/ + margin: 10px; +} +#register{ + text-transform: uppercase; + text-align: center; + width: 95%; + line-height: 60px; + background-color: blue; + display: block; + font-size: xx-large; + margin: 10px; + /*font-weight: bold;*/ + /*vertical-align: middle;*/ +} diff --git a/static/fonts/Share Tech.ttf b/static/fonts/Share Tech.ttf new file mode 100644 index 0000000..787ba83 Binary files /dev/null and b/static/fonts/Share Tech.ttf differ diff --git a/static/fonts/big_noodle_titling.ttf b/static/fonts/big_noodle_titling.ttf new file mode 100644 index 0000000..5577c00 Binary files /dev/null and b/static/fonts/big_noodle_titling.ttf differ diff --git a/static/fonts/bignoodle_titling.zip b/static/fonts/bignoodle_titling.zip new file mode 100644 index 0000000..78cd6e0 Binary files /dev/null and b/static/fonts/bignoodle_titling.zip differ diff --git a/static/login.html b/static/login.html new file mode 100644 index 0000000..bdcf046 --- /dev/null +++ b/static/login.html @@ -0,0 +1,30 @@ + + + +
+ ++ Please login below with your EDU e-mail and your BearShop password. +
+ + + + + REGISTER +