Ever sit down to learn something and at first the examples are great, but then when it comes to getting advanced – you’re lost wondering what happened inbetween? When learning I always found it easy to get started, but hard to get anywhere meaningful as all the examples felt like this picture
It was always hard to find comprehensive code examples that covered a wide range of topics, so I figured I would share this project in hopes someone takes away some newfound information or motivation.
All the pieces at work in the project come from the below technologies and documentation.
In short what I’ve done here is written my own web app, conformed it to the specs demonstrated in the Flask blog tutorial, then added my own supporting structures around it.
Whenever there was a need for a specific feature, I would check the following docs to make sure I wasn’t reinventing the wheel. Otherwise it was poignant to roll my own code for the feature.
- Theory - Software Development Life Cycle
- Database - SQLAlchemy
- Database - PyMySql
- Back End - Flask Tutorial
- Front End - Bootstrap4
- Front End - Jinja2/Flask
- Tests - PyTests/Flask
- Tests - UnitTest/Selenium
- Automation - Ansible
- AWS Deployment - Terraform
Sometimes you may find features in libraries that kind of meet your use case, if you’re lucky you can extend or build upon their code to make it work for you.
Coming back from DEFCON I was beyond amped to write some code. It was about time to finally complete a full stack web project. Having participated in the OPENCTF at DC26, I had a great idea. Why not make a little hacking game to play with some colleagues?
The mission of this writeup: by showing some different aspects of creating a webapp that isnt a standard example, I hope to illustrate different ways basic concepts come together to achieve more nuanced features. Getting started was never a problem, but we’ve all spent alot of time staring at the blinking cursor wondering what next.
So what is a CTF?
Capture the Flag (CTF) is a kind of computer security competition. CTF contests are usually designed to serve as an educational exercise to give participants experience in securing a machine, as well as conducting and reacting to the sort of attacks found in the real world.
Having a clear cut goal from the beginning is key to building clean code. We want to set our scope and stick to it.
In our case, all we want is to make a web application that will receive flags and track a user’s score on the scoreboard.
For added robustness we will add some admin features and tools to make our lives easier. Without further ado, lets dive in.
Project Directory Structure
Theres obviously many more subdirectories, but for explanation we only need to concern ourselves with these four.
ezctf ├── ezctf-alpha ├── ezctf-ansible ├── ezctf-terraform ├── linted_ctf 39 directories, 71 files ---
- alpha - The first version of the app where all functionality was prototyped
- linted - The cleaned up version of the app where all functionality is modular and code coverage is provided by Tests
- Ansible - The Configuration Management tools used to configure Vagrant and Linux to run our application
- Terraform - Infrastructure As Code providing us with both infrastructure deployment and provisioning
Due to the ease of use and speed of prototyping you can achieve,
Python was the natural choice for the back end. Here we use
Flask as it has minimal overhead allowing us to pick and choose our pieces.
This is an overly simplistic explanation of the back end, if you want to learn how
Flask works read the linked above tut.
Originally in the alpha everything was in one
app.py file, thats obviously not great, so we split it out. Using
Blueprints from the
Flask tutorial we wind up with 3 files running our app.
app/ ├── auth.py ├── cruds.py ├── extensions.py ├── __init__.py
__init__.py provides an entry point for
Gunicorn, which is what will handle the
HTTP requests so that Flask doesnt have to, Gunicorn is typically what goes infront of Flask or Django in production environments.
auth.py is fairly self explanatory, it handles
authentication for our users.
cruds.py is the bulk of our logic and the most important part here. When planning functionality of our app,
CRUDS provides a great starting point.
This helps us layout initial functionality for our app, from there we can begin to add CTF specific features – such as when
Challenge to show add a completion, update the users score who solved it as well.
So theres some of our backend functioanlity to handle scoring, we can elaborate more on the
SQL in the database portion. When users solve the challenges all of this is put into the
Scoreboard page, a fixture CTFs everywhere.
Front end design is not a favorite of mine, so we’ll let
Jinja2 do all the work for us.
The goal here isnt to teach you to make a front end or use
Jinaj2 like a pro, its to hopefully give you something to draw upon when you’re trying to think of the code you need to put in your application.
Jinja2 is a templating engine, so we can give it a placeholders and
Flask will inject the content and render it as HTML on the front end.
Next up, we’ve got the
Challenges page laid out in a jeopardy style. It was a mild pain at first to make the grid layout but a little help from the Modulus operator alongside the
Jinja2 docs and we’ve got the code to render it in our desired layout.
Which turns out to look like this
Hitting the floor of our front end, the last thing to look at is the
Challenges page. We use
Flask to render parts of page based on a users context such as;
Achieved easily by using some simple conditionals and passing the
Session object into the template.
Logged out view
Logged in view
Test coverage – a measure used to describe the degree to which the source code of a program is executed when a particular test suite runs.
We have two kinds of test coverage at play here.
- PyTest - Used before committing code, based off the example laid out in the
- UnitTest/Selenium - Used post deployment to check renders and verify page content.
Oh would you look at that, a failure. We changed the
About page to be
Rules so we need to change our tests to match, hence the second one succeeding.
For those familiar with test driven development – the above picture probably made you twitch a little, but after debugging failed tests you begin to understand how the creator mustve felt.
You can run Selenium headless, which works great if we want to shove it into a CI/CD pipeline in order to validate our deployments.
Otherwise running it with the Chrome Driver we can see the tests iterating over our application.
Each suite has its own structure and fixtures you can use to simplify to your testing, just go with what fits your use caser best..
In general it can be difficult to wrap your head around unit tests but by working them into your code you will be able to prevent against
Undefined behavior, which in alot of cases leads to security issues. My unit tests don’t hit 100% code coverage but in an ideal world that is what we would want to strive for.
To be continued
This post was dragging on quite a bit so check out part 2 for the remaining topics