hardening server

This commit is contained in:
Jonas Weinz 2019-03-27 17:23:04 +01:00
parent 86b766e0b5
commit e1d31cc291
8 changed files with 128 additions and 102 deletions

View File

@ -6,12 +6,14 @@ from user_manager import UserManager
from match_manager import MatchManager from match_manager import MatchManager
from match import Match from match import Match
from tools import debug
def parse_message(msg: str): def parse_message(msg: str):
# TODO: make it more robust by validating each part of a message # TODO: make it more robust by validating each part of a message
msg_obj = json.loads(msg) msg_obj = json.loads(msg)
if "type" not in msg_obj or "data" not in msg_obj: if "type" not in msg_obj or "data" not in msg_obj:
print("got strange message") debug("got strange message")
return None return None
return msg_obj return msg_obj
@ -33,7 +35,7 @@ class Connection(object):
try: try:
await self.websocket.send(msg) await self.websocket.send(msg)
except Exception as e: except Exception as e:
print("error sending message to user " + debug("error sending message to user " +
self.user_name + ". Reason: " + str(e)) self.user_name + ". Reason: " + str(e))
async def close(self): async def close(self):
@ -44,7 +46,7 @@ class Connection(object):
self.is_closed = True self.is_closed = True
except Exception as e: except Exception as e:
print("error closing session to user " + debug("error closing session to user " +
self.user_name + ". Reason: " + str(e)) self.user_name + ". Reason: " + str(e))
@ -114,7 +116,7 @@ class ConnectionHandler(object):
socket: websockets.WebSocketServerProtocol, socket: websockets.WebSocketServerProtocol,
login_msg: str): login_msg: str):
msg = parse_message(login_msg) msg = parse_message(login_msg)
print("new incomming connection...") debug("new incomming connection...")
if msg is None: if msg is None:
return None return None
@ -395,7 +397,7 @@ class ConnectionHandler(object):
) )
except Exception as e: except Exception as e:
print("error processing match request: " + str(data) + str(e)) debug("error processing match request: " + str(data) + str(e))
async def _on_match_state_req(self, conn, data): async def _on_match_state_req(self, conn, data):
db_matches = self.match_manager.get_matches_for_user(conn.user_name) db_matches = self.match_manager.get_matches_for_user(conn.user_name)
@ -432,7 +434,7 @@ class ConnectionHandler(object):
if match is not None: if match is not None:
match_state = match.to_json_state() match_state = match.to_json_state()
print(match_state) debug(match_state)
await conn.send(json.dumps({ await conn.send(json.dumps({
'type': 'match_update', 'type': 'match_update',
@ -580,7 +582,7 @@ class ConnectionHandler(object):
async def handle_message(self, conn, msg_str): async def handle_message(self, conn, msg_str):
msg = parse_message(msg_str) msg = parse_message(msg_str)
print("incoming message" + str(msg)) debug("incoming message" + str(msg))
if msg is None: if msg is None:
return None return None
@ -607,4 +609,4 @@ class ConnectionHandler(object):
await self._on_unfriend_request(conn, msg['data']) await self._on_unfriend_request(conn, msg['data'])
else: else:
print("could not interpret message: " + msg_str) debug("could not interpret message: " + msg_str)

View File

@ -36,23 +36,30 @@ class DatabaseConnection(object):
DatabaseConnection.instance.commit() DatabaseConnection.instance.commit()
@staticmethod @staticmethod
def global_single_query(query): def global_single_query(query, params=None):
if ';' in query: if ';' in query:
# Possible injection! # Possible injection!
raise SQLInjectionError() raise SQLInjectionError()
with DatabaseConnection.global_cursor() as c: with DatabaseConnection.global_cursor() as c:
c.execute(query) if params is None:
c.execute(query)
else:
c.execute(query, params)
return c.fetchall() return c.fetchall()
@staticmethod @staticmethod
def global_single_execution(sql_statement): def global_single_execution(sql_statement, params=None):
if ';' in sql_statement: if ';' in sql_statement:
# Possible injection detected! # Possible injection detected!
raise SQLInjectionError() raise SQLInjectionError()
with DatabaseConnection.global_cursor() as c: with DatabaseConnection.global_cursor() as c:
c.execute(sql_statement) if params is None:
c.execute(sql_statement)
else:
c.execute(sql_statement, params)
DatabaseConnection.global_commit() DatabaseConnection.global_commit()
def __init__(self, def __init__(self,
@ -84,7 +91,7 @@ class DatabaseConnection(object):
":" + ":" +
str(port) + str(port) +
"\nCheck the configuration in settings.py!\n") "\nCheck the configuration in settings.py!\n")
raise Exception('could not connec to database') raise Exception('could not connect to database')
def get_cursor(self): def get_cursor(self):
return self.connection.cursor() return self.connection.cursor()
@ -96,6 +103,7 @@ class DatabaseConnection(object):
def commit(self): def commit(self):
self.connection.commit() self.connection.commit()
def test_connection(): def test_connection():
import settings import settings
DatabaseConnection(settings.db_host, DatabaseConnection(settings.db_host,
@ -103,4 +111,4 @@ def test_connection():
settings.db_user, settings.db_user,
settings.db_pw, settings.db_pw,
settings.db_db, settings.db_db,
settings.db_charset) settings.db_charset)

23
main.py
View File

@ -12,6 +12,8 @@ from user_manager import UserManager
from database_connection import DatabaseConnection from database_connection import DatabaseConnection
import datetime import datetime
from tools import debug
um = UserManager() um = UserManager()
sm = SessionManager(datetime.timedelta(hours=12)) sm = SessionManager(datetime.timedelta(hours=12))
mm = MatchManager() mm = MatchManager()
@ -29,7 +31,6 @@ DatabaseConnection(db_host,
async def socket_worker(websocket, path): async def socket_worker(websocket, path):
connection = None connection = None
try: try:
raw_msg = await websocket.recv() raw_msg = await websocket.recv()
@ -37,21 +38,25 @@ async def socket_worker(websocket, path):
if connection is None: if connection is None:
return return
print("successfull logged in user: " + connection.user_name) debug("successfull logged in user: " + connection.user_name)
async for m in websocket: async for m in websocket:
await ch.handle_message(connection, m) await ch.handle_message(connection, m)
except websockets.ConnectionClosed as e:
# nothing suspicious here
pass
except Exception as e: except Exception as e:
# TODO: each disconnect is an exception so far # TODO: each disconnect is an exception so far
if connection is not None: if connection is not None:
print("catched exception in worker for user: " + debug("catched exception in worker for user: " +
connection.user_name + ": " + str(e)) connection.user_name + ": " + str(e))
else: else:
print("catched exception in worker for unknown user: " + str(e)) debug("catched exception in worker for unknown user: " + str(e))
print(traceback.print_exc()) debug(traceback.debug_exc())
finally: finally:
id = None id = None
@ -62,7 +67,7 @@ async def socket_worker(websocket, path):
if connection is None: if connection is None:
id = "unknown_user" id = "unknown_user"
print("close connection to user: " + id) debug("close connection to user: " + id)
if cert_file is not None and key_file is not None: if cert_file is not None and key_file is not None:
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)

View File

@ -3,6 +3,8 @@ import numpy as np
import json import json
import base64 import base64
from tools import debug
# decoder/encoder took from: https://stackoverflow.com/a/19271311 # decoder/encoder took from: https://stackoverflow.com/a/19271311
@ -158,7 +160,7 @@ class Match(object):
player_mark = FIELD_USER_A if self.is_player_a else FIELD_USER_B player_mark = FIELD_USER_A if self.is_player_a else FIELD_USER_B
if not self.is_move_valid(sub_x, sub_y, x, y): if not self.is_move_valid(sub_x, sub_y, x, y):
print("invalid move") debug("invalid move")
return False return False
# else: move! # else: move!
@ -169,7 +171,7 @@ class Match(object):
# check whether this indicates changes in the global field: # check whether this indicates changes in the global field:
if self.global_field[sub_y, sub_x] != FIELD_EMPTY: if self.global_field[sub_y, sub_x] != FIELD_EMPTY:
print("field not empty") debug("field not empty")
return False return False
subgrid = self.complete_field[sub_y * self.n: ( subgrid = self.complete_field[sub_y * self.n: (

View File

@ -5,14 +5,16 @@ import uuid
import settings import settings
from match import Match from match import Match
from tools import debug
class MatchManager(object): class MatchManager(object):
def __init__(self): def __init__(self):
pass pass
def get_match(self, id): def get_match(self, id):
query = f"SELECT * FROM matches WHERE id='{id}'" query = "SELECT * FROM matches WHERE id=%s"
result = DatabaseConnection.global_single_query(query) result = DatabaseConnection.global_single_query(query, (id))
if len(result) == 0: if len(result) == 0:
return None return None
match = Match(n=settings.n, match_id=id, player_a_name=result[0]['user_a'], match = Match(n=settings.n, match_id=id, player_a_name=result[0]['user_a'],
@ -20,45 +22,46 @@ class MatchManager(object):
return match return match
def get_matches_for_user(self, user_name): def get_matches_for_user(self, user_name):
query = f"SELECT * FROM matches WHERE user_a='{user_name}' OR user_b='{user_name}'" query = "SELECT * FROM matches WHERE user_a=%s OR user_b=%s"
return DatabaseConnection.global_single_query(query) return DatabaseConnection.global_single_query(query, (user_name, user_name))
def create_new_match(self, user_a, user_b): def create_new_match(self, user_a, user_b):
match_id = uuid.uuid4().hex match_id = uuid.uuid4().hex
# check if already existent (but should not be the case) # check if already existent (but should not be the case)
if len(DatabaseConnection.global_single_query(f"SELECT id FROM matches WHERE id='{match_id}'")) > 0: if len(DatabaseConnection.global_single_query("SELECT id FROM matches WHERE id=%s", (match_id))) > 0:
return self.create_new_match(user_a, user_b) return self.create_new_match(user_a, user_b)
match = Match(n=settings.n, match_id=match_id, player_a_name=user_a, player_b_name=user_b) match = Match(n=settings.n, match_id=match_id,
player_a_name=user_a, player_b_name=user_b)
now = datetime.datetime.now() now = datetime.datetime.now()
query = f"INSERT INTO matches (id, user_a, user_b, match_state, active_user, last_active) VALUES ('{match_id}', '{user_a}', '{user_b}', '{match.to_json_state()}', '{match.get_current_player()}','{get_sql_time(now)}')" query = "INSERT INTO matches (id, user_a, user_b, match_state, active_user, last_active) VALUES (%s, %s, %s, %s, %s,%s)"
DatabaseConnection.global_single_execution(query) DatabaseConnection.global_single_execution(
query, (match_id, user_a, user_b, match.to_json_state(), match.get_current_player(), get_sql_time(now)))
return match return match
def update_match(self, match_id, match): def update_match(self, match_id, match):
now = get_sql_time(datetime.datetime.now()) now = get_sql_time(datetime.datetime.now())
query = f"UPDATE matches SET match_state='{match.to_json_state()}', active_user='{match.get_current_player()}', last_active='{now}' WHERE id='{match_id}'" query = "UPDATE matches SET match_state=%s, active_user=%s, last_active=%s WHERE id=%s"
DatabaseConnection.global_single_execution(query) DatabaseConnection.global_single_execution(
query, (match.to_json_state(), match.get_current_player(), now, match_id))
def apply_move(self, move_data): def apply_move(self, move_data):
match = self.get_match(move_data['id']) match = self.get_match(move_data['id'])
if match is None: if match is None:
return None return None
if not match.move(move_data):
print("error applying match move")
return None
self.update_match(move_data['id'], match)
print("updated match")
return match
def is_match(self, player_a, player_b):
query = f"SELECT * FROM matches WHERE (user_a='{player_a}' AND user_b='{player_b}') OR (user_a='{player_b}' AND user_b='{player_a}')"
return len(DatabaseConnection.global_single_query(query))
def delete_match(self, match_id):
query = f"DELETE FROM matches WHERE id='{match_id}'"
DatabaseConnection.global_single_execution(query)
if not match.move(move_data):
debug("error applying match move")
return None
self.update_match(move_data['id'], match)
debug("updated match")
return match
def is_match(self, player_a, player_b):
query = "SELECT * FROM matches WHERE (user_a=%s AND user_b=%s) OR (user_a=%s AND user_b=%s)"
return len(DatabaseConnection.global_single_query(query, (player_a, player_b, player_b, player_a)))
def delete_match(self, match_id):
query = "DELETE FROM matches WHERE id=%s"
DatabaseConnection.global_single_execution(query, (match_id))

View File

@ -9,59 +9,60 @@ class SessionManager(object):
self.session_lifespan_timedelta = session_lifespan_timedelta self.session_lifespan_timedelta = session_lifespan_timedelta
def get_session_by_id(self, session_id): def get_session_by_id(self, session_id):
query = f"SELECT * FROM sessions WHERE id='{session_id}'" query = "SELECT * FROM sessions WHERE id=%s"
return DatabaseConnection.global_single_query(query) return DatabaseConnection.global_single_query(query, (session_id))
def touch_session(self, session_id): def touch_session(self, session_id):
query = f"UPDATE sessions SET last_seen='{datetime.datetime.now()}' WHERE id='{session_id}'" query = "UPDATE sessions SET last_seen=%s WHERE id=%s"
DatabaseConnection.global_single_execution(query) DatabaseConnection.global_single_execution(query, (datetime.datetime.now(), session_id))
def get_session_by_registered_user(self, user_name): def get_session_by_registered_user(self, user_name):
query = f"SELECT * FROM sessions WHERE registered_user='{user_name}'" query = "SELECT * FROM sessions WHERE registered_user=%s"
return DatabaseConnection.global_single_query(query) return DatabaseConnection.global_single_query(query, (user_name))
def get_session_by_temp_user(self, user_name): def get_session_by_temp_user(self, user_name):
query = f"SELECT * FROM sessions WHERE temp_user='{user_name}'" query = "SELECT * FROM sessions WHERE temp_user=%s"
return DatabaseConnection.global_single_query(query) return DatabaseConnection.global_single_query(query, (user_name))
def create_session_for_registered_user(self, user_name): def create_session_for_registered_user(self, user_name):
new_id = uuid.uuid4().hex new_id = uuid.uuid4().hex
# check if already existent (but should not be the case) # check if already existent (but should not be the case)
if len(DatabaseConnection.global_single_query(f"SELECT id FROM sessions WHERE id='{new_id}'")) > 0: if len(DatabaseConnection.global_single_query("SELECT id FROM sessions WHERE id=%s", (str(new_id)))) > 0:
# okay, next try: # okay, next try:
return self.create_session_for_registered_user(user_name) return self.create_session_for_registered_user(user_name)
# delete other active sessions:
query = f"DELETE FROM sessions WHERE registered_user='{user_name}'"
DatabaseConnection.global_single_execution(query)
query = f"INSERT INTO sessions (id, registered_user, temp_user, last_seen) VALUES ( '{new_id}', '{user_name}', NULL, '{datetime.datetime.now()}')" # delete other active sessions:
DatabaseConnection.global_single_execution(query) query = "DELETE FROM sessions WHERE registered_user=%s"
DatabaseConnection.global_single_execution(query, (user_name))
query = "INSERT INTO sessions (id, registered_user, temp_user, last_seen) VALUES ( %s, %s, NULL, %s)"
DatabaseConnection.global_single_execution(query, (new_id, user_name, datetime.datetime.now()))
return new_id return new_id
def create_session_for_temp_user(self, user_name): def create_session_for_temp_user(self, user_name):
new_id = uuid.uuid4().hex new_id = uuid.uuid4().hex
# check if already existent (but should not be the case) # check if already existent (but should not be the case)
if len(DatabaseConnection.global_single_query(f"SELECT id FROM sessions WHERE id='{new_id}'")) > 0: if len(DatabaseConnection.global_single_query("SELECT id FROM sessions WHERE id=%s", (new_id))) > 0:
# okay, next try: # okay, next try:
return self.create_session_for_registered_user(user_name) return self.create_session_for_registered_user(user_name)
# delete other active sessions: # delete other active sessions:
query = f"DELETE FROM sessions WHERE temp_user='{user_name}'" query = "DELETE FROM sessions WHERE temp_user=%s"
DatabaseConnection.global_single_execution(query) DatabaseConnection.global_single_execution(query, (user_name))
query = f"INSERT INTO sessions (id, registered_user, temp_user, last_seen) VALUES ( '{new_id}', NULL, '{user_name}', '{datetime.datetime.now()}')" query = "INSERT INTO sessions (id, registered_user, temp_user, last_seen) VALUES ( %s, NULL, %s, %s)"
DatabaseConnection.global_single_execution(query) DatabaseConnection.global_single_execution(query, (new_id, user_name, datetime.datetime.now()))
return new_id return new_id
def delete_session(self, session_id): def delete_session(self, session_id):
query = f"DELETE FROM sessions WHERE id='{session_id}'" query = "DELETE FROM sessions WHERE id=%s"
DatabaseConnection.global_single_execution(query) DatabaseConnection.global_single_execution(query, (session_id))
def revoke_inactive_sessions(self): def revoke_inactive_sessions(self):
revoke_time = datetime.datetime.now() - self.session_lifespan_timedelta revoke_time = datetime.datetime.now() - self.session_lifespan_timedelta
query = f"SELECT * from sessions WHERE last_seen < '{get_sql_time(revoke_time)}'" query = "SELECT * from sessions WHERE last_seen < %s"
revoked_sessions = DatabaseConnection.global_single_query(query) revoked_sessions = DatabaseConnection.global_single_query(query, (get_sql_time(revoke_time)))
query = f"DELETE FROM sessions WHERE last_seen < '{get_sql_time(revoke_time)}'" query = "DELETE FROM sessions WHERE last_seen < %s"
DatabaseConnection.global_single_execution(query) DatabaseConnection.global_single_execution(query, (get_sql_time(revoke_time)))
return revoked_sessions return revoked_sessions

5
tools.py Normal file
View File

@ -0,0 +1,5 @@
import datetime
def debug(msg):
print("[" + datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + "]: " + msg)

View File

@ -10,23 +10,23 @@ class UserManager(object):
pass pass
def get_user(self, user_name): def get_user(self, user_name):
query = f"SELECT name, last_seen FROM users where name='{user_name}'" query = "SELECT name, last_seen FROM users where name=%s"
return DatabaseConnection.global_single_query(query) return DatabaseConnection.global_single_query(query, (user_name))
def delete_user(self, user_name): def delete_user(self, user_name):
query = f"DELETE FROM users where name='{user_name}'" query = "DELETE FROM users where name=%s"
DatabaseConnection.global_single_execution(query) DatabaseConnection.global_single_execution(query, (user_name))
# remove from friends: # remove from friends:
query = f"DELETE FROM friends WHERE user='{user_name}'" query = "DELETE FROM friends WHERE user=%s"
DatabaseConnection.global_single_execution(query) DatabaseConnection.global_single_execution(query, (user_name))
query = f"DELETE FROM friends WHERE friend='{user_name}'" query = "DELETE FROM friends WHERE friend=%s"
DatabaseConnection.global_single_execution(query) DatabaseConnection.global_single_execution(query, (user_name))
def verify_user(self, user_name, pw): def verify_user(self, user_name, pw):
query = f"SELECT * FROM users where name='{user_name}'" query = "SELECT * FROM users where name=%s"
users = DatabaseConnection.global_single_query(query) users = DatabaseConnection.global_single_query(query, (user_name))
if len(self.get_user(user_name)) == 0: if len(self.get_user(user_name)) == 0:
return False return False
@ -41,33 +41,33 @@ class UserManager(object):
assert len(self.get_user(user_name)) == 0 assert len(self.get_user(user_name)) == 0
pw_salt = uuid.uuid4().hex pw_salt = uuid.uuid4().hex
pw_hash = hashlib.sha512(pw.encode() + pw_salt.encode()).hexdigest() pw_hash = hashlib.sha512(pw.encode() + pw_salt.encode()).hexdigest()
query = f"INSERT INTO users (name, pw_hash, pw_salt, last_seen) VALUES ( '{user_name}', '{pw_hash}', '{pw_salt}', '{datetime.datetime.now()}')" query = "INSERT INTO users (name, pw_hash, pw_salt, last_seen) VALUES ( %s, %s, %s, %s)"
DatabaseConnection.global_single_execution(query) DatabaseConnection.global_single_execution(query, (user_name, pw_hash, pw_salt, datetime.datetime.now()))
def touch_user(self, user_name): def touch_user(self, user_name):
matches = self.get_user(user_name) matches = self.get_user(user_name)
assert len(matches) == 1 assert len(matches) == 1
query = f"UPDATE users SET last_seen='{datetime.datetime.now()}' WHERE name='{user_name}'" query = "UPDATE users SET last_seen=%s WHERE name=%s"
DatabaseConnection.global_single_execution(query) DatabaseConnection.global_single_execution(query, (datetime.datetime.now(), user_name))
def add_friend_to_user(self, user_name, friend_name): def add_friend_to_user(self, user_name, friend_name):
if len(self.get_user(friend_name)) > 0: if len(self.get_user(friend_name)) > 0:
query = f"INSERT INTO friends (user, friend) VALUES ( '{user_name}', '{friend_name}')" query = "INSERT INTO friends (user, friend) VALUES ( %s, %s)"
DatabaseConnection.global_single_execution(query) DatabaseConnection.global_single_execution(query, (user_name, friend_name))
return True return True
return False return False
def get_friends_for_user(self, user_name): def get_friends_for_user(self, user_name):
query = f"SELECT friend FROM friends WHERE user='{user_name}'" query = "SELECT friend FROM friends WHERE user=%s"
tmp = DatabaseConnection.global_single_query(query) tmp = DatabaseConnection.global_single_query(query, (user_name))
friends = set() friends = set()
for entry in tmp: for entry in tmp:
friends.add(entry['friend']) friends.add(entry['friend'])
return friends return friends
def remove_friend_from_user(self, user_name, friend_name): def remove_friend_from_user(self, user_name, friend_name):
query = f"DELETE FROM friends WHERE user='{user_name}' AND friend='{friend_name}'" query = "DELETE FROM friends WHERE user=%s AND friend=%s"
DatabaseConnection.global_single_execution(query) DatabaseConnection.global_single_execution(query, (user_name, friend_name))
def get_all_users(self): def get_all_users(self):
query = "SELECT name, last_seen FROM users" query = "SELECT name, last_seen FROM users"