diff --git a/.travis.yml b/.travis.yml
index 5fbd182..f265a73 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -21,5 +21,7 @@ install:
- "pip install -r src/requirements.txt"
- "python src/create_db.py"
+before_script:
+ - black . --check
script:
- - pytest --flake8
+ - flake8 . --count --max-line-length=88 --show-source --statistics
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..2ec4f6e
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,71 @@
+# Code of Conduct
+
+## Our Pledge
+
+As contributors and maintainers of the RTB-CTF-Framework project, and in the interest
+of fostering an open and welcoming community, we pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, sex characteristics, gender identity and expression,
+level of experience, education, socio-economic status, nationality, personal
+appearance, race, religion, or sexual identity and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or
+ advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic
+ address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned to this Code of Conduct, or to ban temporarily or
+permanently any contributor for other behaviors that they deem inappropriate,
+threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. Examples of
+representing a project or community include using an official project e-mail
+address, posting via an official social media account, or acting as an appointed
+representative at an online or offline event. Representation of a project may be
+further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by contacting the project team at our Slack channel. All
+complaints will be reviewed and investigated and will result in a response that
+is deemed necessary and appropriate to the circumstances. The project team is
+obligated to maintain confidentiality with regard to the reporter of an incident.
+Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good
+faith may face temporary or permanent repercussions as determined by other
+members of the project's leadership.
+
+[homepage]: https://www.contributor-covenant.org
+
+For answers to common questions about this code of conduct, see
+https://www.contributor-covenant.org/faq
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..c274d21
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,83 @@
+
+# Contributing to RTB-CTF-Framework
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## This project makes use of the following Flask libraries
+
+* 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,
+
+## Style Guide
+
+Keeping to a consistent code style throughout the project makes it easier to contribute and collaborate. Please stick to the guidelines in PEP8, [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) and the Google Style Guide unless there’s a very good reason not to.
+
+## Contact
+
+##### 👨 Project Owner
+
+- Eshaan Bansal ([github](https://github.com/eshaan7),[linkedin](https://www.linkedin.com/in/eshaan7/))
+
+##### 👬 Mentors
+
+- Sombuddha Chakravarty ([github](https://github.com/sammy1997),[linkedin](https://www.linkedin.com/in/sombuddha-chakravarty-9482b5131/))
+
+Feel free to ask your queries!! 🙌
+
+##### Slack Channel
+
+- [#proj_root-the-box-ctf-framework](https://app.slack.com/client/TRN1H1V43/CUC71PDD2)
+
+## Where to start ?
+
+See: [Issues](https://github.com/abs0lut3pwn4g3/RTB-CTF-Framework/issues) and the following To-do list. Or just ping one of the mentors with new ideas.
+
+> Note: All PRs within the GSSoC'20 period will be merged in the `gssoc20-dev` branch.
+
+## To-do
+
+- [ ] Ideas for additional logging techniques to prevent flag sharing, cheating and such. (Issue: [#7](https://github.com/abs0lut3pwn4g3/RTB-CTF-Framework/issues/7))
+- [ ] Support for *n* number of boxes (accordions? seperate route?). (Issue: [#17](https://github.com/abs0lut3pwn4g3/RTB-CTF-Framework/issues/17))
+- [ ] Rating system: Average Box rating - input, calculate, output. (Issue: [#14](https://github.com/abs0lut3pwn4g3/RTB-CTF-Framework/issues/14))
+- [ ] Dark theme for `admin control` panel. (Issue: [#16](https://github.com/abs0lut3pwn4g3/RTB-CTF-Framework/issues/16))
+- [ ] Testing Password reset functionality, the mail-server setup, etc.
+- [ ] More info on `home.html`
+- [ ] Need to implement `account.html`
+- [ ] Support for more hashes per box (not a priority)
+
+
+
+- [x] Freeze Scoreboard automatically past running time specified (Issue: [#3](https://github.com/abs0lut3pwn4g3/RTB-CTF-Framework/issues/3))
+- [x] Adding a `Deploy to Heroku` button. (Issue: [#15](https://github.com/abs0lut3pwn4g3/RTB-CTF-Framework/issues/15))
+- [x] Adding CI, Linting, Formatting specs. (Issue: [#18](https://github.com/abs0lut3pwn4g3/RTB-CTF-Framework/issues/18))
+- [x] db relationship between User and Score Tables (priority | issue: #5)
+- [x] isAdmin column in User table and Admin views (priority)
+- [x] Notifications
+- [x] Use Flask Blueprints
+- [x] Finalize black theme?
+- [x] Error messages not appearing in `/submit`
+- [x] Implement `machine.html` to server a page where one can download/serve machines
\ No newline at end of file
diff --git a/README.md b/README.md
index 76f1fd9..2f767c0 100644
--- a/README.md
+++ b/README.md
@@ -1,25 +1,24 @@
# RootTheBox CTF Framework
-
-
-
+
+
+
-
+
+
+
+
-
-
-
-
-
+
+
+
-
-
-
-
-A lightweight, easy to deploy CTF framework(in Flask) for HackTheBox style machines.
+A lightweight, easy to deploy CTF framework (in Flask) for HackTheBox style machines.
The main purpose of this project is to serve as a scoring engine and CTF manager.
@@ -27,10 +26,19 @@ The main purpose of this project is to serve as a scoring engine and CTF manager
A live demo of the app is available at: .
- You can login and mess around as 2 users: `admin:admin` and `test:test`(i.e. username:password combinations)
+ You can login and mess around as 2 users: `admin:admin` and `test:test` (i.e. username:password combinations)
## Features
+##### For CTF hosters
+* A page to show relevant details about the machine such as name, IP, OS, points and difficulty level.
+* 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,
+* Easily deployable on Heroku.
+
##### For Developers & Contributors
* Flask-blueprints for modularity and clean codebase,
* Flask-admin for Admin views and easy realtime management,
@@ -38,13 +46,35 @@ The main purpose of this project is to serve as a scoring engine and CTF manager
* Flask-wtf for forms,
* Flask-mail for mail service.
-##### For CTF hosters
-* A page to show relevant details about the machine such as name, IP, OS, points and difficulty level.
-* 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 hashes: user and root),
-* Real time scoreboard tracking,
-* Easily deployable on Heroku.
+## Deployment
+
+### Heroku
+
+[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy)
+
+or do it manually,
+
+1. Create your heroku app using `heroku` cli tool.
+
+ Follow the official guide by Heroku: https://devcenter.heroku.com/articles/getting-started-with-python#prepare-the-app
+
+2. Provision Database add-on.
+
+ Add the following add on to your new app: https://elements.heroku.com/addons/heroku-postgresql
+
+3. Creating database instance. In your heroku app directory,
+
+ ```bash
+ $ heroku run bash
+ [heroku]$ python create_db.py
+ ```
+4. Your app should be live now. You can run `heroku open` to open it in browser.
+
+### Docker
+
+```bash
+$ docker-compose up
+```
## How To Use
@@ -78,36 +108,13 @@ $ cd src/
[venv]$ python run.py
```
-### Deployment using Heroku
+### Configuration For Your CTF
-[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy)
-
-or do it manually,
-
-1. Create your heroku app using `heroku` cli tool.
-
- Follow the official guide by Heroku: https://devcenter.heroku.com/articles/getting-started-with-python#prepare-the-app
-
-2. Provision Database add-on.
-
- Add the following add on to your new app: https://elements.heroku.com/addons/heroku-postgresql
-
-3. Creating database instance. In your heroku app directory,
-
- ```bash
- $ heroku run bash
- [heroku]$ python create_db.py
- ```
-4. Your app should be live now. You can run `heroku open` to open it in browser.
-
-
-## For Your CTF
-
-Using this as simple as anything.
+Using this as simple as anything.
1. Just configure your CTF settings in [`config.py`](https://github.com/abs0lut3pwn4g3/RTB-CTF-Framework/blob/master/src/FlaskRTBCTF/config.py).
-2. DO NOT FORGET to change admin credentials from [`create_db.py`](https://github.com/abs0lut3pwn4g3/RTB-CTF-Framework/blob/master/src/create_db.py)
+2. When you run [`create_db.py`](https://github.com/abs0lut3pwn4g3/RTB-CTF-Framework/blob/master/src/create_db.py), a strong and random 16 char password for the **admin** user is created and set in the environment variable `ADMIN_PASS`. On Heroku, you can reveal this password from your application's dashboard settings.
3. See database instance creation steps under How To Use.
@@ -117,7 +124,7 @@ Bonus: You can manage the database CRUD operations from admin views GUI as well
## Contributing
-
+
@@ -126,61 +133,20 @@ Bonus: You can manage the database CRUD operations from admin views GUI as well
-
-
-
-
-
-
-
-
-
-
-
-
-Keeping to a consistent code style throughout the project makes it easier to contribute and collaborate. Please stick to the guidelines in PEP8 and the Google Style Guide unless there’s a very good reason not to.
-Please see: [Issues](https://github.com/abs0lut3pwn4g3/RTB-CTF-Framework/issues) and the following To-do list.
-
-> Note: All PRs within the GSSoC'20 period will be merged in the `gssoc20-dev` branch.
##### 👨 Project Owner
-- Eshaan Bansal ([github](https://github.com/eshaan7),[linkedin](https://www.linkedin.com/in/eshaan7/))
+- Eshaan Bansal ([github](https://github.com/eshaan7), [linkedin](https://www.linkedin.com/in/eshaan7/))
##### 👬 Mentors
-- Sombuddha Chakravarty ([github](https://github.com/sammy1997),[linkedin](https://www.linkedin.com/in/sombuddha-chakravarty-9482b5131/))
+- Sombuddha Chakravarty ([github](https://github.com/sammy1997), [linkedin](https://www.linkedin.com/in/sombuddha-chakravarty-9482b5131/))
-Feel free to ask your queries!! 🙌
-
-##### Slack Channel
+##### Slack Channel for GSSoC 2020
- [#proj_root-the-box-ctf-framework](https://app.slack.com/client/TRN1H1V43/CUC71PDD2)
-## To-do
-
-- [ ] Ideas for additional logging techniques to prevent flag sharing, cheating and such. (Issue: [#7](https://github.com/abs0lut3pwn4g3/RTB-CTF-Framework/issues/7))
-- [ ] Support for *n* number of boxes (accordions? seperate route?). (Issue: [#17](https://github.com/abs0lut3pwn4g3/RTB-CTF-Framework/issues/17))
-- [ ] Rating system: Average Box rating - input, calculate, output. (Issue: [#14](https://github.com/abs0lut3pwn4g3/RTB-CTF-Framework/issues/14))
-- [ ] Dark theme for `admin control` panel. (Issue: [#16](https://github.com/abs0lut3pwn4g3/RTB-CTF-Framework/issues/16))
-- [ ] Testing Password reset functionality, the mail-server setup, etc.
-- [ ] More info on `home.html`
-- [ ] Support for more hashes per box (not a priority)
-- [ ] Need to implement `account.html` (not a priority)
-
-
-
-- [x] Freeze Scoreboard automatically past running time specified (Issue: [#3](https://github.com/abs0lut3pwn4g3/RTB-CTF-Framework/issues/3))
-- [x] Adding a `Deploy to Heroku` button. (Issue: [#15](https://github.com/abs0lut3pwn4g3/RTB-CTF-Framework/issues/15))
-- [x] Adding CI, Linting, Formatting specs. (Issue: [#18](https://github.com/abs0lut3pwn4g3/RTB-CTF-Framework/issues/18))
-- [x] db relationship between User and Score Tables (priority | issue: #5)
-- [x] isAdmin column in User table and Admin views (priority)
-- [x] Notifications
-- [x] Use Flask Blueprints
-- [x] Finalize black theme?
-- [x] Error messages not appearing in `/submit`
-- [x] Implement `machine.html` to server a page where one can download/serve machines
-
+For further guidelines, Please refer to [CONTRIBUTING.md](CONTRIBUTING.md)
## Screenshots
diff --git a/app.json b/app.json
index 72a5e87..fad7aa1 100644
--- a/app.json
+++ b/app.json
@@ -1,6 +1,6 @@
{
"name": "RootTheBox CTF Framework",
- "description": "A lightweight, easy to deploy CTF framework(in Flask) for HackTheBox style machines.",
+ "description": "A lightweight, easy to deploy CTF framework (in Flask) for HackTheBox style machines.",
"repository": "https://github.com/abs0lut3pwn4g3/RTB-CTF-Framework",
"addons": [
{
diff --git a/setup.cfg b/setup.cfg
index 7586879..aaf258b 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,3 +1,3 @@
-# content of setup.cfg
+# content of setup.cfg (deprecated atm)
[tool:pytest]
flake8-ignore = W191
diff --git a/src/FlaskRTBCTF/__init__.py b/src/FlaskRTBCTF/__init__.py
index 23e773a..c97b393 100644
--- a/src/FlaskRTBCTF/__init__.py
+++ b/src/FlaskRTBCTF/__init__.py
@@ -11,8 +11,8 @@
bcrypt = Bcrypt()
login_manager = LoginManager()
admin_manager = Admin()
-login_manager.login_view = 'users.login'
-login_manager.login_message_category = 'info'
+login_manager.login_view = "users.login"
+login_manager.login_message_category = "info"
mail = Mail()
@@ -27,6 +27,7 @@ def create_app(config_class=Config):
# Add model views
from FlaskRTBCTF.admin.views import MyModelView
from FlaskRTBCTF.models import User, Score, Notification, Machine
+
if LOGGING:
from FlaskRTBCTF.models import Logs
admin_manager.add_view(MyModelView(User, db.session))
@@ -38,13 +39,15 @@ def create_app(config_class=Config):
mail.init_app(app)
from flask_sslify import SSLify
+
# only trigger SSLify if the app is running on Heroku
- if 'DYNO' in os.environ:
+ if "DYNO" in os.environ:
_ = SSLify(app)
from FlaskRTBCTF.users.routes import users
from FlaskRTBCTF.ctf.routes import ctf
from FlaskRTBCTF.main.routes import main
+
app.register_blueprint(users)
app.register_blueprint(ctf)
app.register_blueprint(main)
diff --git a/src/FlaskRTBCTF/admin/views.py b/src/FlaskRTBCTF/admin/views.py
index 2d72f9c..36cf1e6 100644
--- a/src/FlaskRTBCTF/admin/views.py
+++ b/src/FlaskRTBCTF/admin/views.py
@@ -1,4 +1,4 @@
-''' Admin Model Views '''
+""" Admin Model Views. """
from flask import abort
from flask_login import current_user
@@ -7,7 +7,7 @@
class MyModelView(ModelView):
- column_exclude_list = ('password',)
+ column_exclude_list = ("password",)
def is_accessible(self):
if not current_user.is_authenticated or not current_user.isAdmin:
@@ -18,9 +18,8 @@ def is_accessible(self):
return False
def _handle_view(self, name, **kwargs):
- """
- Override builtin _handle_view in order to redirect users when a view is
- not accessible.
+ """ Override builtin _handle_view in order to redirect users when a
+ view is not accessible.
"""
if not self.is_accessible():
if current_user.is_authenticated:
diff --git a/src/FlaskRTBCTF/config.py b/src/FlaskRTBCTF/config.py
index 70a78e3..5c05241 100644
--- a/src/FlaskRTBCTF/config.py
+++ b/src/FlaskRTBCTF/config.py
@@ -2,30 +2,28 @@
from datetime import datetime
import pytz
+from .helpers import handle_secret_key
-''' Flask related Configurations
- Note: DO NOT FORGET TO CHANGE 'SECRET_KEY' ! '''
+# Flask related Configurations
+# Note: DO NOT FORGET TO CHANGE 'SECRET_KEY' !
class Config:
- SECRET_KEY = os.environ.get('SECRET_KEY') or 'you-will-never-guess'
- SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') \
- or 'sqlite:///site.db'
+ SECRET_KEY = handle_secret_key()
+ SQLALCHEMY_DATABASE_URI = os.environ.get("DATABASE_URL") or "sqlite:///site.db"
# For local use, one can simply use SQLlite with: 'sqlite:///site.db'
# For deployment on Heroku use: `os.environ.get('DATABASE_URL')`
# in all other cases: `os.environ.get('SQLALCHEMY_DATABASE_URI')`
SQLALCHEMY_TRACK_MODIFICATIONS = False
DEBUG = False # Turn DEBUG OFF before deployment
- MAIL_SERVER = 'smtp.googlemail.com'
+ MAIL_SERVER = "smtp.googlemail.com"
MAIL_PORT = 587
MAIL_USE_TLS = True
- MAIL_USERNAME = os.environ.get('EMAIL_USER')
- MAIL_PASSWORD = os.environ.get('EMAIL_PASS')
-
-
-''' CTF related Configuration '''
+ MAIL_USERNAME = os.environ.get("EMAIL_USER")
+ MAIL_PASSWORD = os.environ.get("EMAIL_PASS")
+# CTF related Configuration
# Add some information about organization and specify CTF name
organization = {
@@ -33,28 +31,22 @@ class Config:
"name": "Abs0lut3Pwn4g3",
"website": {
"url": "https://Abs0lut3Pwn4g3.github.io/",
- "name": "Official Abs0lut3Pwn4g3 Website"
- },
- "website_2": {
- "url": "https://twitter.com/abs0lut3pwn4g3",
- "name": "Twitter"
+ "name": "Official Abs0lut3Pwn4g3 Website",
},
- "website_3": {
- "url": "https://github.com/abs0lut3pwn4g3",
- "name": "Github"
- }
+ "website_2": {"url": "https://twitter.com/abs0lut3pwn4g3", "name": "Twitter"},
+ "website_3": {"url": "https://github.com/abs0lut3pwn4g3", "name": "GitHub"},
}
# Specify CTFs Running Time
+# We do not recommend changing the Timezone.
RunningTime = {
"from": datetime(2019, 7, 7, 15, 00, 00, 0, pytz.utc),
- "to": datetime(2030, 7, 8, 0, 00, 00, 0, pytz.utc),
- "TimeZone": "UTC"
-} # We do not recommend changing the Timezone.
+ "to": datetime(2023, 7, 8, 0, 00, 00, 0, pytz.utc),
+ "TimeZone": "UTC",
+}
# Logging: Set to 'True' to enable Logging in Admin Views.
+# We recommend to leave it on. It is more than just errors ;)
-LOGGING = True # We recommend to leave it on. It is more than just errors ;)
-
-# NOTE: CHANGE DEFAULT ADMIN CREDENTIALS in create_db.py !!!
+LOGGING = True
diff --git a/src/FlaskRTBCTF/ctf/forms.py b/src/FlaskRTBCTF/ctf/forms.py
index 704b4ab..0dc20e2 100644
--- a/src/FlaskRTBCTF/ctf/forms.py
+++ b/src/FlaskRTBCTF/ctf/forms.py
@@ -4,20 +4,14 @@
class UserHashForm(FlaskForm):
- userHash = StringField('User hash',
- validators=[
- DataRequired(),
- Length(min=32, max=32)
- ]
- )
- submit = SubmitField('Submit')
+ userHash = StringField(
+ "User hash", validators=[DataRequired(), Length(min=32, max=32)]
+ )
+ submit = SubmitField("Submit")
class RootHashForm(FlaskForm):
- rootHash = StringField('Root hash',
- validators=[
- DataRequired(),
- Length(min=32, max=32)
- ]
- )
- submit = SubmitField('Submit')
+ rootHash = StringField(
+ "Root hash", validators=[DataRequired(), Length(min=32, max=32)]
+ )
+ submit = SubmitField("Submit")
diff --git a/src/FlaskRTBCTF/ctf/routes.py b/src/FlaskRTBCTF/ctf/routes.py
index 96a66f4..c009128 100644
--- a/src/FlaskRTBCTF/ctf/routes.py
+++ b/src/FlaskRTBCTF/ctf/routes.py
@@ -1,4 +1,4 @@
-''' views / routes '''
+""" views / routes. """
from datetime import datetime
@@ -15,10 +15,10 @@
from FlaskRTBCTF.models import Logs
-ctf = Blueprint('ctf', __name__)
+ctf = Blueprint("ctf", __name__)
-''' Scoreboard '''
+# Scoreboard
@ctf.route("/scoreboard")
@@ -27,16 +27,16 @@ def scoreboard():
scores = Score.query.order_by(Score.points.desc(), Score.timestamp).all()
userNameScoreList = []
for score in scores:
- userNameScoreList.append({
- 'username': User.query.get(score.user_id).username,
- 'score': score.points
- })
+ userNameScoreList.append(
+ {"username": User.query.get(score.user_id).username, "score": score.points}
+ )
- return render_template('scoreboard.html', scores=userNameScoreList,
- organization=organization)
+ return render_template(
+ "scoreboard.html", scores=userNameScoreList, organization=organization
+ )
-''' Machine Info '''
+# Machine Info
@ctf.route("/machine")
@@ -53,16 +53,21 @@ def machine():
rootHashForm = RootHashForm()
end_date_time = RunningTime["to"]
current_date_time = datetime.now(pytz.utc)
- return render_template('machine.html', userHashForm=userHashForm,
- rootHashForm=rootHashForm,
- organization=organization, box=box,
- current=current_date_time, end=end_date_time)
+ return render_template(
+ "machine.html",
+ userHashForm=userHashForm,
+ rootHashForm=rootHashForm,
+ organization=organization,
+ box=box,
+ current=current_date_time,
+ end=end_date_time,
+ )
-''' Hash Submission Management '''
+# Hash Submission Management
-@ctf.route("/validateRootHash", methods=['POST'])
+@ctf.route("/validateRootHash", methods=["POST"])
@login_required
def validateRootHash():
box = Machine.query.filter(Machine.ip == "127.0.0.1").first()
@@ -85,25 +90,33 @@ def validateRootHash():
log = Logs.query.get(current_user.id)
log.rootSubmissionIP = request.access_route[0]
log.rootSubmissionTime = datetime.utcnow()
- log.rootOwnTime = str(
- log.rootSubmissionTime - log.machineVisitTime
- )
+ log.rootOwnTime = str(log.rootSubmissionTime - log.machineVisitTime)
db.session.commit()
flash("Congrats! correct system hash.", "success")
else:
flash("Sorry! Wrong system hash", "danger")
- return render_template('machine.html', userHashForm=userHashForm,
- rootHashForm=rootHashForm, box=box,
- organization=organization,
- current=current_date_time, end=end_date_time)
+ return render_template(
+ "machine.html",
+ userHashForm=userHashForm,
+ rootHashForm=rootHashForm,
+ box=box,
+ organization=organization,
+ current=current_date_time,
+ end=end_date_time,
+ )
else:
- return render_template('machine.html', userHashForm=userHashForm,
- rootHashForm=rootHashForm, box=box,
- organization=organization,
- current=current_date_time, end=end_date_time)
-
-
-@ctf.route("/validateUserHash", methods=['POST'])
+ return render_template(
+ "machine.html",
+ userHashForm=userHashForm,
+ rootHashForm=rootHashForm,
+ box=box,
+ organization=organization,
+ current=current_date_time,
+ end=end_date_time,
+ )
+
+
+@ctf.route("/validateUserHash", methods=["POST"])
@login_required
def validateUserHash():
box = Machine.query.filter(Machine.ip == "127.0.0.1").first()
@@ -126,19 +139,27 @@ def validateUserHash():
log = Logs.query.get(current_user.id)
log.userSubmissionIP = request.access_route[0]
log.userSubmissionTime = datetime.utcnow()
- log.userOwnTime = str(
- log.userSubmissionTime - log.machineVisitTime
- )
+ log.userOwnTime = str(log.userSubmissionTime - log.machineVisitTime)
db.session.commit()
flash("Congrats! correct user hash.", "success")
else:
flash("Sorry! Wrong user hash", "danger")
- return render_template('machine.html', userHashForm=userHashForm,
- rootHashForm=rootHashForm,
- organization=organization, box=box,
- current=current_date_time, end=end_date_time)
+ return render_template(
+ "machine.html",
+ userHashForm=userHashForm,
+ rootHashForm=rootHashForm,
+ organization=organization,
+ box=box,
+ current=current_date_time,
+ end=end_date_time,
+ )
else:
- return render_template('machine.html', userHashForm=userHashForm,
- rootHashForm=rootHashForm,
- organization=organization, box=box,
- current=current_date_time, end=end_date_time)
+ return render_template(
+ "machine.html",
+ userHashForm=userHashForm,
+ rootHashForm=rootHashForm,
+ organization=organization,
+ box=box,
+ current=current_date_time,
+ end=end_date_time,
+ )
diff --git a/src/FlaskRTBCTF/helpers.py b/src/FlaskRTBCTF/helpers.py
new file mode 100644
index 0000000..35a01a4
--- /dev/null
+++ b/src/FlaskRTBCTF/helpers.py
@@ -0,0 +1,20 @@
+""" Helper functions """
+
+import os
+import secrets
+
+
+def handle_secret_key():
+ sk = os.environ.get("SECRET_KEY", None)
+ if not sk:
+ sk = secrets.token_hex(16)
+ os.environ["SECRET_KEY"] = sk
+ return sk
+
+
+def handle_admin_pass():
+ passwd = os.environ.get("ADMIN_PASS", None)
+ if not passwd:
+ passwd = secrets.token_hex(16)
+ os.environ["ADMIN_PASS"] = passwd
+ return passwd
diff --git a/src/FlaskRTBCTF/main/routes.py b/src/FlaskRTBCTF/main/routes.py
index ebf7811..eb46acf 100644
--- a/src/FlaskRTBCTF/main/routes.py
+++ b/src/FlaskRTBCTF/main/routes.py
@@ -2,20 +2,25 @@
from FlaskRTBCTF.config import organization, RunningTime
from FlaskRTBCTF.models import Notification
-main = Blueprint('main', __name__)
+main = Blueprint("main", __name__)
-''' Index page '''
+""" Index page """
@main.route("/")
@main.route("/home")
def home():
- return render_template('home.html', organization=organization,
- RunningTime=RunningTime)
+ return render_template(
+ "home.html", organization=organization, RunningTime=RunningTime
+ )
@main.route("/notifications")
def notifications():
notifs = Notification.query.order_by(Notification.timestamp.desc()).all()
- return render_template('notifications.html', organization=organization,
- title="Notifications", notifs=notifs)
+ return render_template(
+ "notifications.html",
+ organization=organization,
+ title="Notifications",
+ notifs=notifs,
+ )
diff --git a/src/FlaskRTBCTF/models.py b/src/FlaskRTBCTF/models.py
index dea8ed6..c81e444 100644
--- a/src/FlaskRTBCTF/models.py
+++ b/src/FlaskRTBCTF/models.py
@@ -1,4 +1,4 @@
-''' Models '''
+""" Models. """
from datetime import datetime
@@ -15,7 +15,7 @@ def load_user(user_id):
return User.query.get(int(user_id))
-''' Machine Table '''
+# Machine Table
class Machine(db.Model):
@@ -29,10 +29,10 @@ class Machine(db.Model):
ip = db.Column(db.String(45), nullable=False)
hardness = db.Column(db.String(16), nullable=False, default="Easy")
- score = db.relationship('Score', backref='machine', lazy=True)
+ score = db.relationship("Score", backref="machine", lazy=True)
-''' User Table '''
+# User Table
class User(db.Model, UserMixin):
@@ -41,21 +41,19 @@ class User(db.Model, UserMixin):
email = db.Column(db.String(120), unique=True, nullable=False)
password = db.Column(db.String(60), nullable=False)
isAdmin = db.Column(db.Boolean, default=False)
- score = db.relationship('Score', backref='user', lazy=True,
- uselist=False)
+ score = db.relationship("Score", backref="user", lazy=True, uselist=False)
if LOGGING:
- logs = db.relationship('Logs', backref='user', lazy=True,
- uselist=False)
+ logs = db.relationship("Logs", backref="user", lazy=True, uselist=False)
def get_reset_token(self, expires_sec=1800):
- s = Serializer(current_app.config['SECRET_KEY'], expires_sec)
- return s.dumps({'user_id': self.id}).decode('utf-8')
+ s = Serializer(current_app.config["SECRET_KEY"], expires_sec)
+ return s.dumps({"user_id": self.id}).decode("utf-8")
@staticmethod
def verify_reset_token(token):
- s = Serializer(current_app.config['SECRET_KEY'])
+ s = Serializer(current_app.config["SECRET_KEY"])
try:
- user_id = s.loads(token)['user_id']
+ user_id = s.loads(token)["user_id"]
except Exception:
return None
return User.query.get(user_id)
@@ -64,24 +62,24 @@ def __repr__(self):
return f"User('{self.username}', '{self.email}'))"
-''' Score Table '''
+# Score Table
class Score(db.Model):
- user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False,
- primary_key=True)
+ user_id = db.Column(
+ db.Integer, db.ForeignKey("user.id"), nullable=False, primary_key=True
+ )
userHash = db.Column(db.Boolean, default=False)
rootHash = db.Column(db.Boolean, default=False)
points = db.Column(db.Integer)
timestamp = db.Column(db.DateTime(), default=datetime.utcnow)
- machine_id = db.Column(db.Integer, db.ForeignKey('machine.id'),
- nullable=False)
+ machine_id = db.Column(db.Integer, db.ForeignKey("machine.id"), nullable=False)
def __repr__(self):
return f"Score('{self.user_id}', '{self.points}')"
-''' Notifications Table '''
+# Notifications Table
class Notification(db.Model):
@@ -94,13 +92,15 @@ def __repr__(self):
return f"Notif('{self.title}', '{self.body}')"
-''' Logging Table '''
+# Logging Table
if LOGGING:
+
class Logs(db.Model):
- user_id = db.Column(db.Integer, db.ForeignKey('user.id'),
- nullable=False, primary_key=True)
+ user_id = db.Column(
+ db.Integer, db.ForeignKey("user.id"), nullable=False, primary_key=True
+ )
accountCreationTime = db.Column(db.DateTime, nullable=False)
visitedMachine = db.Column(db.Boolean, default=False)
machineVisitTime = db.Column(db.DateTime, nullable=True)
diff --git a/src/FlaskRTBCTF/users/forms.py b/src/FlaskRTBCTF/users/forms.py
index 864d7e5..d6fa371 100644
--- a/src/FlaskRTBCTF/users/forms.py
+++ b/src/FlaskRTBCTF/users/forms.py
@@ -2,61 +2,56 @@
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField, BooleanField
-from wtforms.validators import DataRequired, Length, Email, \
- EqualTo, ValidationError
+from wtforms.validators import DataRequired, Length, Email, EqualTo, ValidationError
from flask_login import current_user
class RegistrationForm(FlaskForm):
- username = StringField('Username',
- validators=[DataRequired(), Length(min=4, max=20)])
- email = StringField('Email',
- validators=[DataRequired(), Email()])
- password = PasswordField('Password', validators=[DataRequired()])
- confirm_password = PasswordField('Confirm Password',
- validators=[
- DataRequired(),
- EqualTo('password')
- ]
- )
- submit = SubmitField('Sign Up')
+ username = StringField(
+ "Username", validators=[DataRequired(), Length(min=4, max=20)]
+ )
+ email = StringField("Email", validators=[DataRequired(), Email()])
+ password = PasswordField("Password", validators=[DataRequired()])
+ confirm_password = PasswordField(
+ "Confirm Password", validators=[DataRequired(), EqualTo("password")]
+ )
+ submit = SubmitField("Sign Up")
def validate_username(self, username):
user = User.query.filter_by(username=username.data).first()
if user:
raise ValidationError(
- 'That username is taken. Please choose a different one.'
+ "That username is taken. Please choose a different one."
)
def validate_email(self, email):
user = User.query.filter_by(email=email.data).first()
if user:
- raise ValidationError(
- 'That email is taken. Please choose a different one.'
- )
+ raise ValidationError("That email is taken. Please choose a different one.")
class LoginForm(FlaskForm):
- username = StringField('Username',
- validators=[DataRequired(), Length(min=4, max=20)])
- password = PasswordField('Password', validators=[DataRequired()])
- remember = BooleanField('Remember Me')
- submit = SubmitField('Login')
+ username = StringField(
+ "Username", validators=[DataRequired(), Length(min=4, max=20)]
+ )
+ password = PasswordField("Password", validators=[DataRequired()])
+ remember = BooleanField("Remember Me")
+ submit = SubmitField("Login")
class UpdateAccountForm(FlaskForm):
- username = StringField('Username',
- validators=[DataRequired(), Length(min=4, max=20)])
- email = StringField('Email',
- validators=[DataRequired(), Email()])
- submit = SubmitField('Update')
+ username = StringField(
+ "Username", validators=[DataRequired(), Length(min=4, max=20)]
+ )
+ email = StringField("Email", validators=[DataRequired(), Email()])
+ submit = SubmitField("Update")
def validate_username(self, username):
if username.data != current_user.username:
user = User.query.filter_by(username=username.data).first()
if user:
raise ValidationError(
- 'That username is taken. Please choose a different one.'
+ "That username is taken. Please choose a different one."
)
def validate_email(self, email):
@@ -64,29 +59,25 @@ def validate_email(self, email):
user = User.query.filter_by(email=email.data).first()
if user:
raise ValidationError(
- 'That email is taken. Please choose a different one.'
+ "That email is taken. Please choose a different one."
)
class RequestResetForm(FlaskForm):
- email = StringField('Email',
- validators=[DataRequired(), Email()])
- submit = SubmitField('Request Password Reset')
+ email = StringField("Email", validators=[DataRequired(), Email()])
+ submit = SubmitField("Request Password Reset")
def validate_email(self, email):
user = User.query.filter_by(email=email.data).first()
if user is None:
raise ValidationError(
- 'There is no account with that email. You must register first.'
+ "There is no account with that email. You must register first."
)
class ResetPasswordForm(FlaskForm):
- password = PasswordField('Password', validators=[DataRequired()])
- confirm_password = PasswordField('Confirm Password',
- validators=[
- DataRequired(),
- EqualTo('password')
- ]
- )
- submit = SubmitField('Reset Password')
+ password = PasswordField("Password", validators=[DataRequired()])
+ confirm_password = PasswordField(
+ "Confirm Password", validators=[DataRequired(), EqualTo("password")]
+ )
+ submit = SubmitField("Reset Password")
diff --git a/src/FlaskRTBCTF/users/routes.py b/src/FlaskRTBCTF/users/routes.py
index bf26c20..cd87022 100644
--- a/src/FlaskRTBCTF/users/routes.py
+++ b/src/FlaskRTBCTF/users/routes.py
@@ -1,8 +1,12 @@
from datetime import datetime
import pytz
-from FlaskRTBCTF.users.forms import (RegistrationForm, LoginForm,
- RequestResetForm, ResetPasswordForm)
+from FlaskRTBCTF.users.forms import (
+ RegistrationForm,
+ LoginForm,
+ RequestResetForm,
+ ResetPasswordForm,
+)
from FlaskRTBCTF.users.utils import send_reset_email
from FlaskRTBCTF.config import organization, LOGGING
from FlaskRTBCTF.models import User, Score, Machine
@@ -15,59 +19,58 @@
from FlaskRTBCTF.models import Logs
-users = Blueprint('users', __name__)
+users = Blueprint("users", __name__)
-''' User management '''
+""" User management """
-@users.route("/register", methods=['GET', 'POST'])
+@users.route("/register", methods=["GET", "POST"])
def register():
if current_user.is_authenticated:
- flash('Already Authenticated', 'info')
- return redirect(url_for('main.home'))
+ flash("Already Authenticated", "info")
+ return redirect(url_for("main.home"))
box = Machine.query.filter(Machine.ip == "127.0.0.1").first()
form = RegistrationForm()
if form.validate_on_submit():
- hashed_password = bcrypt.generate_password_hash(form.password.data) \
- .decode('utf-8')
- user = User(
- username=form.username.data, email=form.email.data,
- password=hashed_password
+ hashed_password = bcrypt.generate_password_hash(form.password.data).decode(
+ "utf-8"
)
- score = Score(
- user=user, userHash=False,
- rootHash=False, points=0, machine=box
+ user = User(
+ username=form.username.data, email=form.email.data, password=hashed_password
)
+ score = Score(user=user, userHash=False, rootHash=False, points=0, machine=box)
if LOGGING:
log = Logs(
- user=user, accountCreationTime=datetime.now(pytz.utc),
- visitedMachine=False, machineVisitTime=None,
- userSubmissionTime=None, rootSubmissionTime=None,
- userSubmissionIP=None, rootSubmissionIP=None
+ user=user,
+ accountCreationTime=datetime.now(pytz.utc),
+ visitedMachine=False,
+ machineVisitTime=None,
+ userSubmissionTime=None,
+ rootSubmissionTime=None,
+ userSubmissionIP=None,
+ rootSubmissionIP=None,
)
db.session.add(log)
db.session.add(user)
db.session.add(score)
db.session.commit()
- flash(
- 'Your account has been created! You are now able to log in.',
- 'success'
- )
- return redirect(url_for('users.login'))
+ flash("Your account has been created! You are now able to log in.", "success")
+ return redirect(url_for("users.login"))
- return render_template('register.html', title='Register',
- form=form, organization=organization)
+ return render_template(
+ "register.html", title="Register", form=form, organization=organization
+ )
-@users.route("/login", methods=['GET', 'POST'])
+@users.route("/login", methods=["GET", "POST"])
def login():
if current_user.is_authenticated:
- flash('Already Authenticated', 'info')
- return redirect(url_for('main.home'))
+ flash("Already Authenticated", "info")
+ return redirect(url_for("main.home"))
form = LoginForm()
if form.validate_on_submit():
@@ -75,16 +78,13 @@ def login():
pw_chk = bcrypt.check_password_hash(user.password, form.password.data)
if user and pw_chk:
login_user(user, remember=form.remember.data, force=True)
- next_page = request.args.get('next')
- return redirect(next_page) if next_page else \
- redirect(url_for('main.home'))
+ next_page = request.args.get("next")
+ return redirect(next_page) if next_page else redirect(url_for("main.home"))
else:
- flash(
- 'Login Unsuccessful. Please check username and password.',
- 'danger'
- )
- return render_template('login.html', title='Login',
- form=form, organization=organization)
+ flash("Login Unsuccessful. Please check username and password.", "danger")
+ return render_template(
+ "login.html", title="Login", form=form, organization=organization
+ )
@users.route("/logout")
@@ -92,55 +92,56 @@ def login():
def logout():
logout_user()
flash("Logged out.", "info")
- return redirect(url_for('main.home'))
+ return redirect(url_for("main.home"))
@users.route("/account")
@login_required
def account():
- return render_template('account.html', title='Account',
- organization=organization)
+ return render_template("account.html", title="Account", organization=organization)
-@users.route("/reset_password", methods=['GET', 'POST'])
+@users.route("/reset_password", methods=["GET", "POST"])
def reset_request():
if current_user.is_authenticated:
- return redirect(url_for('main.home'))
+ return redirect(url_for("main.home"))
form = RequestResetForm()
if form.validate_on_submit():
user = User.query.filter_by(email=form.email.data).first()
send_reset_email(user)
flash(
- 'An email has been sent with instructions to reset your password.',
- 'info'
+ "An email has been sent with instructions to reset your password.", "info"
)
- return redirect(url_for('users.login'))
- return render_template('reset_request.html', title='Reset Password',
- form=form, organization=organization)
+ return redirect(url_for("users.login"))
+ return render_template(
+ "reset_request.html",
+ title="Reset Password",
+ form=form,
+ organization=organization,
+ )
-@users.route("/reset_password/", methods=['GET', 'POST'])
+@users.route("/reset_password/", methods=["GET", "POST"])
def reset_token(token):
if current_user.is_authenticated:
- return redirect(url_for('main.home'))
+ return redirect(url_for("main.home"))
user = User.verify_reset_token(token)
if user is None:
- flash('That is an invalid or expired token', 'warning')
- return redirect(url_for('users.reset_request'))
+ flash("That is an invalid or expired token", "warning")
+ return redirect(url_for("users.reset_request"))
form = ResetPasswordForm()
if form.validate_on_submit():
- hashed_password = bcrypt.generate_password_hash(form.password.data) \
- .decode('utf-8')
+ hashed_password = bcrypt.generate_password_hash(form.password.data).decode(
+ "utf-8"
+ )
user.password = hashed_password
db.session.commit()
- flash(
- 'Your password has been updated! You are now able to log in',
- 'success'
- )
- return redirect(url_for('users.login'))
+ flash("Your password has been updated! You are now able to log in", "success")
+ return redirect(url_for("users.login"))
- return render_template('reset_token.html', title='Reset Password',
- form=form, organization=organization)
+ return render_template(
+ "reset_token.html", title="Reset Password", form=form, organization=organization
+ )
diff --git a/src/FlaskRTBCTF/users/utils.py b/src/FlaskRTBCTF/users/utils.py
index f3ae0c9..b9969ae 100644
--- a/src/FlaskRTBCTF/users/utils.py
+++ b/src/FlaskRTBCTF/users/utils.py
@@ -1,4 +1,4 @@
-''' Utility Functions '''
+""" Utility Functions. """
from flask import url_for
from flask_mail import Message
@@ -7,13 +7,13 @@
def send_reset_email(user):
token = user.get_reset_token()
- msg = Message('Password Reset Request',
- sender='noreply@demo.com',
- recipients=[user.email])
- msg.body = f'''To reset your password, visit the following link:
+ msg = Message(
+ "Password Reset Request", sender="noreply@demo.com", recipients=[user.email]
+ )
+ msg.body = f"""To reset your password, visit the following link:
{url_for('users.reset_token', token=token, _external=True)}
If you did not make this request then simply ignore this email
and no changes will be made.
-'''
+"""
mail.send(msg)
diff --git a/src/create_db.py b/src/create_db.py
index 992c285..3d35944 100644
--- a/src/create_db.py
+++ b/src/create_db.py
@@ -1,9 +1,11 @@
-from datetime import datetime
import pytz
+from datetime import datetime
from FlaskRTBCTF import create_app, db, bcrypt
+from FlaskRTBCTF.helpers import handle_admin_pass
from FlaskRTBCTF.models import User, Score, Notification, Machine
from FlaskRTBCTF.config import organization, LOGGING
+
if LOGGING:
from FlaskRTBCTF.models import Logs
@@ -18,49 +20,42 @@
box = Machine(
name="My Awesome Pwnable Box",
- user_hash='A' * 32,
- root_hash='B' * 32,
+ user_hash="A" * 32,
+ root_hash="B" * 32,
user_points=10,
root_points=20,
os="Linux",
ip="127.0.0.1",
- hardness="You tell"
+ hardness="You tell",
)
db.session.add(box)
- # NOTE: CHANGE DEFAULT CREDENTIALS !!!
+ passwd = handle_admin_pass()
admin_user = User(
- username='admin',
- email='admin@admin.com',
- password=bcrypt.generate_password_hash('admin').decode('utf-8'),
- isAdmin=True
+ username="admin",
+ email="admin@admin.com",
+ password=bcrypt.generate_password_hash(passwd).decode("utf-8"),
+ isAdmin=True,
+ )
+ admin_score = Score(
+ user=admin_user, userHash=False, rootHash=False, points=0, machine=box
)
- admin_score = Score(user=admin_user, userHash=False,
- rootHash=False, points=0, machine=box)
db.session.add(admin_user)
db.session.add(admin_score)
notif = Notification(
title=f"Welcome to {organization['ctfname']}",
- body="The CTF is live now. Please read rules!"
+ body="The CTF is live now. Please read rules!",
)
db.session.add(notif)
- test_user = User(
- username='test',
- email='test@test.com',
- password=bcrypt.generate_password_hash('test').decode('utf-8')
- )
- test_score = Score(user=test_user, userHash=False,
- rootHash=False, points=0, machine=box)
- db.session.add(test_user)
- db.session.add(test_score)
-
if LOGGING:
- admin_log = Logs(user=admin_user, accountCreationTime=default_time,
- visitedMachine=True, machineVisitTime=default_time)
+ admin_log = Logs(
+ user=admin_user,
+ accountCreationTime=default_time,
+ visitedMachine=True,
+ machineVisitTime=default_time,
+ )
db.session.add(admin_log)
- test_log = Logs(user=test_user, accountCreationTime=default_time)
- db.session.add(test_log)
db.session.commit()
diff --git a/src/requirements.txt b/src/requirements.txt
index 7449900..ecdd4c1 100644
--- a/src/requirements.txt
+++ b/src/requirements.txt
@@ -1,4 +1,5 @@
bcrypt==3.1.7
+black==19.10b0
blinker==1.4
cffi==1.14.0
click==7.1.1
@@ -10,6 +11,7 @@ Flask-Mail==0.9.1
Flask-SQLAlchemy==2.4.1
Flask-SSLify==0.1.5
Flask-WTF==0.14.3
+flake8==3.7.9
gunicorn==20.0.4
itsdangerous==1.1.0
Jinja2==2.11.1
diff --git a/src/run.py b/src/run.py
index 4149120..ff3783a 100644
--- a/src/run.py
+++ b/src/run.py
@@ -2,5 +2,5 @@
app = create_app()
-if __name__ == '__main__':
- app.run(debug=app.config.get('DEBUG', False))
+if __name__ == "__main__":
+ app.run(debug=app.config.get("DEBUG", False))