Skip to content

Commit

Permalink
created challenges section,with categories & tags. Now using ReprMixi…
Browse files Browse the repository at this point in the history
…n for model representation and TimeMixin for timestamp fields. dockerfile updated
  • Loading branch information
eshaan7 committed Apr 30, 2020
1 parent 2908f96 commit 5cf7d97
Show file tree
Hide file tree
Showing 34 changed files with 847 additions and 322 deletions.
4 changes: 2 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ before_install:

install:
- "pip install -r src/requirements.txt"
- "python src/create_db.py"
- "python src/create_db.test.py"

before_script:
- black . --check
script:
- flake8 . --count --max-line-length=88 --exclude="src/FlaskRTBCTF/utils/__init__.py" --show-source --statistics
- flake8 . --count --max-line-length=88 --show-source --statistics
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ $ black .
```

```bash
$ flake8 src/ ---max-line-length=88 --show-source --statistics
$ flake8 src/ --max-line-length=88 --show-source --statistics
```

if flake8 shows any errors or warnings, please fix the changes in a new commit and squash all the commits into one before submitting the PR.
Expand Down
28 changes: 23 additions & 5 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,10 +1,28 @@
FROM python:3
FROM python:3.8.2-alpine3.11

MAINTAINER [email protected]

# Env
RUN export DATABASE_URL="postgres://${DB_USER}:${DB_PASSWORD}@postgres:${DB_PORT}/${DB_NAME}" \
&& export REDIS_URL="redis://redis:6379/0"

# update and install packages
RUN apk update \
&& apk add libpq postgresql-dev \
&& apk add build-base \
&& apk add --no-cache git libssl1.1 g++ make libffi-dev

# Add a new low-privileged user
RUN adduser --shell /sbin/login www-data -DH

# Install RTB-CTF-Framework
WORKDIR /usr/src/app
COPY src ./
RUN pip install --no-cache-dir -r requirements.txt
EXPOSE 8080
RUN chown -R 1001:1001 .
USER 1001
RUN pip install --no-cache-dir -r requirements.txt \
&& chown -R www-data ./

USER www-data

EXPOSE 8000
RUN chmod +x /usr/src/app/docker-entrypoint.sh
ENTRYPOINT [ "/usr/src/app/docker-entrypoint.sh" ]
28 changes: 8 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
<a href="https://travis-ci.com/abs0lut3pwn4g3/RTB-CTF-Framework" target="_blank">
<img alt="Build Status" src="https://travis-ci.com/abs0lut3pwn4g3/RTB-CTF-Framework.svg?branch=gssoc20-dev"/>
</a>
<!-- <a href="https://lgtm.com/projects/g/abs0lut3pwn4g3/RTB-CTF-Framework/context:python">
<a href="https://lgtm.com/projects/g/abs0lut3pwn4g3/RTB-CTF-Framework/context:python">
<img alt="Language grade: Python" src="https://img.shields.io/lgtm/grade/python/g/abs0lut3pwn4g3/RTB-CTF-Framework.svg?logo=lgtm&logoWidth=18"/>
</a> -->
</a>
<a href="https://github.com/psf/black" target="_blank">
<img alt="Code style: black" src="https://img.shields.io/badge/code%20style-black-000000.svg"/>
</a>
Expand All @@ -30,24 +30,17 @@ The main purpose of this project is to serve as a scoring engine and CTF manager

## Features

##### For CTF hosters
* A page to show relevant details about the machine such as name, IP, OS, points and difficulty level.
* Machines listing name, IP, OS, points and difficulty level.
* Challenges listing with title, description, URL, points.
* Totally configurable settings such running time, organization details, CTF name.
* Automatic strong password for administrator
* Well implemented controls for administrators providing features such as issuing notifications, database CRUD operations, full fledged logging,
* Simple User Registration/login process, account management, Forgot password functionalities,
* Flag submission (currently 2 flags: user and root),
* Real time scoreboard tracking,
* Efficient caching so it's fast
* Easily deployable on Heroku.

##### For Developers & Contributors
* Flask-blueprints for modularity and clean codebase,
* Flask-admin for Admin views and easy realtime management,
* Flask-SQLAlchemy for SQL models,
* Flask-login for session handling,
* Flask-wtf for responsive forms,
* Flask-mail for mail service,
* Flask-bcrypt for password hashing and security,

## Build locally

Please see [INSTALLATION.md](INSTALLATION.md).
Expand Down Expand Up @@ -95,11 +88,6 @@ Please see [INSTALLATION.md](INSTALLATION.md).

For further guidelines, Please refer to [CONTRIBUTING.md](CONTRIBUTING.md)

## Screenshots

> Why look at static pictures, when you can use a demo ? Visit: <https://rtblivedemo.herokuapp.com/>.
<img src="screenshots/home_ss.png" width=400 />
<img src="screenshots/scoreboard_ss.png" width=400 />
<img src="screenshots/machine_ss.png" width=400 />
## Live Demo

** Live Demo: <https://rtblivedemo.herokuapp.com/> (login with `admin:admin`)
53 changes: 50 additions & 3 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,55 @@
version: "3"

services:
rtbd:
rtbctf:
build: .
container_name: rtb_gunicorn
restart: unless-stopped
ports:
- 80:8080
restart: unless-stopped
- "80:8000"
expose:
- 8000
environment:
- DEBUG=False
- SECRET_KEY=changeme
- DB_USER=eshaan
- DB_PASSWORD=eshaan
- DB_NAME=rtbctf
- DB_PORT=5432
- WORKERS=8
- ADMIN_PASS=admin
depends_on:
- postgres
- redis

postgres:
image: library/postgres:12.1-alpine
container_name: rtb_postgres
restart: unless-stopped
expose:
- "5432"
environment:
- POSTGRES_USER=eshaan
- POSTGRES_PASSWORD=eshaan
- POSTGRES_DB=rtbctf

redis:
image: redis:6.0-rc4-alpine
container_name: rtb_redis
restart: unless-stopped
expose:
- "6379"


# nginx:
# image: library/nginx:1.16.1-alpine
# container_name: rtb_nginx
# restart: unless-stopped
# hostname: nginx
# volumes:
# - ./rtb_nginx_http:/etc/nginx/conf.d/default.conf
# ports:
# - "80:80"
# - "443:443"
# depends_on:
# - rtbctf
16 changes: 16 additions & 0 deletions rtb_nginx_http
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# the upstream component nginx needs to connect to
upstream flask {
server rtbctf:8000 fail_timeout=30s;
}


server {
listen 80;

server_name rtbctf.com;

location / {
proxy_pass http://localhost:8000/
}

}
Binary file removed screenshots/home_ss.png
Binary file not shown.
Binary file removed screenshots/machine_ss.png
Binary file not shown.
Binary file removed screenshots/scoreboard_ss.png
Binary file not shown.
70 changes: 69 additions & 1 deletion src/FlaskRTBCTF/admin/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,16 @@
from flask_admin.form import SecureForm
from flask_admin.contrib.sqla import ModelView

from ..utils.cache import cache
from ..utils.helpers import clear_points_cache


class BaseModelView(ModelView):
export_types = ("csv", "json")
can_export = True
form_base_class = SecureForm
column_display_pk = True # optional, but I like to see the IDs in the list
form_excluded_columns = ("created_on", "updated_on")

def is_accessible(self):
if not current_user.is_authenticated or not current_user.isAdmin:
Expand All @@ -31,6 +36,7 @@ def _handle_view(self, name, **kwargs):


class UserAdminView(BaseModelView):
can_view_details = True
column_exclude_list = ("password",)
form_exclude_list = ("password",)
column_searchable_list = ("username", "email")
Expand All @@ -40,8 +46,14 @@ def create_view(self):
flash("Please use registration form for creating new users.", "info")
return redirect("/admin/user")

@staticmethod
def after_model_delete(model):
cache.clear(key="scoreboard")
return


class MachineAdminView(BaseModelView):
can_view_details = True
column_searchable_list = ("name", "ip")

@expose("/new/")
Expand All @@ -54,6 +66,62 @@ def edit_view(self):
return redirect(url_for("ctf.edit_machine", id=id))


class ChallengeAdminView(BaseModelView):
can_view_details = True
column_searchable_list = ("title", "url")
form_choices = {
"difficulty": [
("easy", "Easy"),
("medium", "Medium"),
("hard", "Hard"),
("insane", "Insane"),
]
}

@staticmethod
def after_model_change(form, model, is_created):
cache.delete(key="challenges")
return

@staticmethod
def after_model_delete(model):
cache.delete(key="challenges")
return


class UserChallengeAdminView(BaseModelView):
column_filters = ("completed",)
column_list = ("user_id", "challenge_id", "completed")

@staticmethod
def after_model_change(form, model, is_created):
if form.completed != model.completed:
clear_points_cache(userId=model.user_id, mode="c")
return

@staticmethod
def after_model_delete(model):
clear_points_cache(userId=model.user_id, mode="c")
return


class UserMachineAdminView(BaseModelView):
column_filters = ("owned_user", "owned_root")
column_list = ("user_id", "machine_id", "owned_user", "owned_root")

@staticmethod
def after_model_change(form, model, is_created):
if (form.owned_user != model.owned_user) or (
form.owned_root != model.owned_root
):
clear_points_cache(userId=model.user_id, mode="m")
return

@staticmethod
def after_model_delete(model):
clear_points_cache(userId=model.user_id, mode="m")
return


class NotificationAdminView(BaseModelView):
column_searchable_list = ("title",)
form_excluded_columns = ("timestamp",)
17 changes: 15 additions & 2 deletions src/FlaskRTBCTF/ctf/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from wtforms import StringField, SubmitField, HiddenField, RadioField
from wtforms.validators import DataRequired, Length, ValidationError, IPAddress
from wtforms.fields.html5 import IntegerField
from .models import Machine
from .models import Machine, Challenge


class MachineForm(FlaskForm):
Expand All @@ -23,7 +23,7 @@ class MachineForm(FlaskForm):
ip = StringField(
"IPv4 address of machine", validators=[DataRequired(), IPAddress()]
)
hardness = RadioField(
difficulty = RadioField(
"Difficuly Level",
validators=[DataRequired()],
choices=(
Expand Down Expand Up @@ -67,3 +67,16 @@ def validate_root_hash(self, root_hash):
pass
else:
raise ValidationError("Incorrect Root Hash.")


class ChallengeFlagForm(FlaskForm):
challenge_id = HiddenField("Challenge ID", validators=[DataRequired()])
flag = StringField("Flag", validators=[DataRequired(), Length(min=4)])
submit_flag = SubmitField("Submit")

def validate_flag(self, flag):
ch = Challenge.query.get(int(self.challenge_id.data))
if not ch:
raise ValidationError("No challenge with that ID exists")
elif ch.flag != str(flag.data):
raise ValidationError("Incorrect flag.")
Loading

0 comments on commit 5cf7d97

Please sign in to comment.