Skip to content
This repository has been archived by the owner on Mar 7, 2023. It is now read-only.

Commit

Permalink
Implement source IP tracking and automatic CSS team creation
Browse files Browse the repository at this point in the history
  • Loading branch information
sourque committed Jul 3, 2020
1 parent 98af7c8 commit 94f046a
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 19 deletions.
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Minos Scoring Engine

This is a scoring engine meant to imitate the functionality of UTSA's CIAS CyberPatriot Scoring Engine with an emphasis on simplicity. It acts as an uptime scorer (ex. your service has been up 50% of the time, and is down right now). It is based on DSU's DefSec Club [Scoring Engine](https://github.com/DSUDefSec/ScoringEngine). Named after the Greek myth of King Minos, judge of the dead.
Minos acts as a service uptime scorer (ex. your service has been up 50% of the time, and is down right now). It is based on DSU's DefSec Club [Scoring Engine](https://github.com/DSUDefSec/ScoringEngine).

## Installation

Expand Down Expand Up @@ -215,7 +215,7 @@ password = "HackersArentReal"
teams = [ "TEAM-12398fhn",
"TEAM-qwertyui" ]
# If specified, replace the above team IDs
# If specified, replace the below team IDs
# with the below aliases (one to one)
# Note: aliases without team IDs above
# will cause an error. A greater number
Expand All @@ -225,6 +225,14 @@ teams = [ "TEAM-12398fhn",
team_aliases = [ "team1",
"team2" ]
# If specified, the below emails will be
# included in the CSV report, matching the index
# of the team ids and aliases. This can be any
# identifier that you don't want to be in the unique
# id nor the scoreboard
team_emails = [ "[email protected]",
"[email protected]" ]
# If specified, only allow score updates
# for the following image names
images = [ "supercoolimage1",
Expand Down Expand Up @@ -276,7 +284,7 @@ points = 500
**Service won't restart**

![no_restart](setup/imgs/broken_restart.png)
- On some operating systems (notably DigitalOcean boxes), the init.d script fails to manage the service correctly.
- In some operating environments (notably DigitalOcean boxes), the init.d script fails to manage the service correctly.
- You can manually kill the python and uwsgi processes.
- `sudo pkill -9 python3; pkill -p uwsgi`

Expand Down
49 changes: 36 additions & 13 deletions engine/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ def get_uid(username):

def engine_status():
config = read_running_config()
if config and config["settings"]["running"] == 1:
return True
if "settings" in config and "running" in config["settings"] and config["settings"]["running"] == 1:
return True
else:
return False

Expand Down Expand Up @@ -165,7 +165,7 @@ def insert_totals_score(team, type, points, check_round):
# CSS FUNCTIONS #
#################

def get_css_csv(remote):
def get_css_csv(remote, ips):
http_csv = str.encode("")
teams = get_css_teams(remote)
images = get_css_images(remote)
Expand All @@ -176,7 +176,11 @@ def get_css_csv(remote):
for image in images:
try:
image_sum = execute("SELECT `points` FROM `css_results` WHERE team=? AND image=? ORDER BY time DESC", (team, image), one=True)[0]
http_csv += str.encode(find_alias(index, remote) + "," + team + "," + image + "," + str(image_sum) + "," + get_css_play_time(team, image=image) + "," + get_css_elapsed_time(team) + "\n")
if team in ips:
ip = ips[team]
else:
ip = "N/A"
http_csv += str.encode(find_email(index, remote) + "," + find_alias(index, remote) + "," + team + "," + image + "," + str(image_sum) + "," + ip + "," + get_css_play_time(team, image=image) + "," + get_css_elapsed_time(team) + "\n")
except:
# Ignoring images that a team hasn't started yet
pass
Expand Down Expand Up @@ -229,6 +233,8 @@ def get_css_scores(remote):
team_scores.reverse()
return(team_scores)


# TODO optimize this
def get_css_score(team, remote):
image_data = {}

Expand Down Expand Up @@ -296,7 +302,6 @@ def get_css_score(team, remote):
image[4] = "|-|".join(vuln_info[2:])
except Exception as e:
print("[ERROR] Error decoding hex vulns from databases! (" + str(e) + ")")

return(labels, image_data, scores)

def get_css_elapsed_time(team):
Expand Down Expand Up @@ -335,11 +340,23 @@ def insert_css_score(team, image, points, vulns):
execute("INSERT INTO `css_results` ('team', 'image', 'points', 'vulns') VALUES (?, ?, ?, ?)", (team, image, points, vulns))

def find_alias(team_index, remote):
aliases = remote["team_aliases"]
try:
aliases = remote["team_aliases"]
except:
aliases = []
if not team_index > len(aliases) - 1:
return aliases[team_index]
return "N/A"

def find_email(team_index, remote):
try:
emails = remote["team_emails"]
except:
emails = []
if not team_index > len(emails) - 1:
return emails[team_index]
return "N/A"

def apply_aliases(team_scores, remote):
for score_index, team in enumerate(team_scores):
for index, team_id in enumerate(remote["teams"]):
Expand Down Expand Up @@ -376,6 +393,12 @@ def validate_alphanum(string):
return True
return False

def validate_email(string):
# Courtesy of regular-expressions.info
if re.compile("(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)").match(string):
return True
return False

def copy_config():
print("[INFO] Copying config into running-config...")
write_running_config(read_config())
Expand All @@ -388,22 +411,22 @@ def read_config():
def read_running_config():
try:
with open(path + 'engine/running-config.cfg', 'r') as f:
config = toml.load(f)
if "settings" not in config:
config["settings"] = {}
config_tmp = toml.load(f)
if "settings" not in config_tmp:
config_tmp["settings"] = {}
except OSError:
print("[WARN] File running-config.cfg not found.")
config = None
return config
config_tmp = None
return config_tmp

def get_css_colors():
try:
return read_config()["remote"]["colors"]
except: pass

def write_running_config(config):
def write_running_config(config_tmp):
with open(path + 'engine/running-config.cfg', 'w') as f:
toml.dump(config, f)
toml.dump(config_tmp, f)

def stop_engine():
config = read_running_config()
Expand Down
5 changes: 4 additions & 1 deletion engine/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,10 @@ def start():
em = EngineModel()
while True:
em.load()
running = em.settings['running']
if "running" in em.settings:
running = em.settings['running']
else:
running = 1
if "interval" in em.settings:
interval = em.settings['interval']
else:
Expand Down
74 changes: 72 additions & 2 deletions engine/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,21 @@
wm = WebModel()
em = engine.EngineModel()
app = Flask(__name__)
app.secret_key = 'this is a secret!! lol'
# Should ideally store key in config file-- but this works pretty well
app.secret_key = os.urandom(24).hex()
login_manager = LoginManager()
login_manager.init_app(app)

# CSS hardcoded values
permitted_new_css_ip = "10.10.0.2" # Put your whitelisted IP here

# Caching and refreshing...
refresh_threshold = timedelta(seconds=15)
scoreboard_time_refresh = datetime.now() - refresh_threshold
team_time_refresh = datetime.now() - refresh_threshold
team_scores = None # For scoreboard
team_data = {} # For details
ips = {} # Stalking incoming IPs for CSS leaderboard :eyes:

@login_manager.user_loader
def load_user(uid):
Expand Down Expand Up @@ -292,12 +297,77 @@ def css_update():
if success:
vulns = db.printAsHex("|-|".join(vulns).encode())
db.insert_css_score(team, image, score, vulns)
ips[team] = request.remote_addr
return("OK")
else:
print("[ERROR] Vuln data decryption failed.")
return("FAIL")
return("FAIL")

@app.route('/scores/css/new', methods=['POST'])
def css_new_team():
em.load()
request_ip = request.remote_addr
if request_ip != permitted_new_css_ip:
print("[ERROR] New team ID request wasn't from the authorized IP.")
return("FAIL")
try:
team = request.form["team"].rstrip().strip()
id = request.form["id"].rstrip()
if "email" in request.form:
email = request.form["email"]
else:
email = None
except:
print("[ERROR] New team ID request did not have all required fields (team, id).")
return("FAIL")
if not db.validate_alphanum(team) or not db.validate_alphanum(id):
print("[ERROR] New team or id contained illegal characters.")
return("FAIL")
config = db.read_running_config()
try:
if team in config["remote"]["team_aliases"]:
print("[ERROR] Duplicate team alias (team)")
return("FAIL")
except:
pass
try:
if id in config["remote"]["teams"]:
print("[ERROR] Duplicate team id (id)")
return("FAIL")
except:
pass
try:
if email in config["remote"]["team_emails"]:
print("[ERROR] Duplicate team email (email)")
return("FAIL")
except:
pass
if not "remote" in config:
config["remote"] = {}
if email and not db.validate_email(email):
print("[ERROR] Email was not null and contained illegal characters.")
return("FAIL")
else:
if "team_emails" in config["remote"]:
config["remote"]["team_emails"].append(email)
else:
config["remote"]["team_emails"] = [email]
if len(config["remote"]["team_emails"]) <= len(config["remote"]["teams"]):
for x in range(len(config["remote"]["teams"]) - len(config["remote"]["team_emails"]) + 1):
config["remote"]["team_emails"].insert(0, "N/A")
if "teams" in config["remote"]:
config["remote"]["teams"].append(id)
else:
config["remote"]["teams"] = [id]
if "team_aliases" in config["remote"]:
config["remote"]["team_aliases"].append(team)
else:
config["remote"]["team_aliases"] = [team]
print(config)
db.write_running_config(config)
return("OK")

@app.route('/scores/css/status')
def css_status():
return("OK")
Expand All @@ -306,7 +376,7 @@ def css_status():
def css_csv():
em.load()
csv_buffer = BytesIO()
csv_buffer.write(db.get_css_csv(em.remote))
csv_buffer.write(db.get_css_csv(em.remote, ips))
csv_buffer.seek(0)
return send_file(csv_buffer, as_attachment=True,
attachment_filename='score_report.csv',
Expand Down

0 comments on commit 94f046a

Please sign in to comment.