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 import Match
from tools import debug
def parse_message(msg: str):
# TODO: make it more robust by validating each part of a message
msg_obj = json.loads(msg)
if "type" not in msg_obj or "data" not in msg_obj:
print("got strange message")
debug("got strange message")
return None
return msg_obj
@ -33,7 +35,7 @@ class Connection(object):
try:
await self.websocket.send(msg)
except Exception as e:
print("error sending message to user " +
debug("error sending message to user " +
self.user_name + ". Reason: " + str(e))
async def close(self):
@ -44,7 +46,7 @@ class Connection(object):
self.is_closed = True
except Exception as e:
print("error closing session to user " +
debug("error closing session to user " +
self.user_name + ". Reason: " + str(e))
@ -114,7 +116,7 @@ class ConnectionHandler(object):
socket: websockets.WebSocketServerProtocol,
login_msg: str):
msg = parse_message(login_msg)
print("new incomming connection...")
debug("new incomming connection...")
if msg is None:
return None
@ -395,7 +397,7 @@ class ConnectionHandler(object):
)
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):
db_matches = self.match_manager.get_matches_for_user(conn.user_name)
@ -432,7 +434,7 @@ class ConnectionHandler(object):
if match is not None:
match_state = match.to_json_state()
print(match_state)
debug(match_state)
await conn.send(json.dumps({
'type': 'match_update',
@ -580,7 +582,7 @@ class ConnectionHandler(object):
async def handle_message(self, conn, msg_str):
msg = parse_message(msg_str)
print("incoming message" + str(msg))
debug("incoming message" + str(msg))
if msg is None:
return None
@ -607,4 +609,4 @@ class ConnectionHandler(object):
await self._on_unfriend_request(conn, msg['data'])
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()
@staticmethod
def global_single_query(query):
def global_single_query(query, params=None):
if ';' in query:
# Possible injection!
raise SQLInjectionError()
with DatabaseConnection.global_cursor() as c:
c.execute(query)
if params is None:
c.execute(query)
else:
c.execute(query, params)
return c.fetchall()
@staticmethod
def global_single_execution(sql_statement):
def global_single_execution(sql_statement, params=None):
if ';' in sql_statement:
# Possible injection detected!
raise SQLInjectionError()
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()
def __init__(self,
@ -84,7 +91,7 @@ class DatabaseConnection(object):
":" +
str(port) +
"\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):
return self.connection.cursor()
@ -96,6 +103,7 @@ class DatabaseConnection(object):
def commit(self):
self.connection.commit()
def test_connection():
import settings
DatabaseConnection(settings.db_host,
@ -103,4 +111,4 @@ def test_connection():
settings.db_user,
settings.db_pw,
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
import datetime
from tools import debug
um = UserManager()
sm = SessionManager(datetime.timedelta(hours=12))
mm = MatchManager()
@ -29,7 +31,6 @@ DatabaseConnection(db_host,
async def socket_worker(websocket, path):
connection = None
try:
raw_msg = await websocket.recv()
@ -37,21 +38,25 @@ async def socket_worker(websocket, path):
if connection is None:
return
print("successfull logged in user: " + connection.user_name)
debug("successfull logged in user: " + connection.user_name)
async for m in websocket:
await ch.handle_message(connection, m)
except websockets.ConnectionClosed as e:
# nothing suspicious here
pass
except Exception as e:
# TODO: each disconnect is an exception so far
if connection is not None:
print("catched exception in worker for user: " +
debug("catched exception in worker for user: " +
connection.user_name + ": " + str(e))
else:
print("catched exception in worker for unknown user: " + str(e))
print(traceback.print_exc())
debug("catched exception in worker for unknown user: " + str(e))
debug(traceback.debug_exc())
finally:
id = None
@ -62,7 +67,7 @@ async def socket_worker(websocket, path):
if connection is None:
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:
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)

View File

@ -3,6 +3,8 @@ import numpy as np
import json
import base64
from tools import debug
# 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
if not self.is_move_valid(sub_x, sub_y, x, y):
print("invalid move")
debug("invalid move")
return False
# else: move!
@ -169,7 +171,7 @@ class Match(object):
# check whether this indicates changes in the global field:
if self.global_field[sub_y, sub_x] != FIELD_EMPTY:
print("field not empty")
debug("field not empty")
return False
subgrid = self.complete_field[sub_y * self.n: (

View File

@ -5,14 +5,16 @@ import uuid
import settings
from match import Match
from tools import debug
class MatchManager(object):
def __init__(self):
pass
def get_match(self, id):
query = f"SELECT * FROM matches WHERE id='{id}'"
result = DatabaseConnection.global_single_query(query)
query = "SELECT * FROM matches WHERE id=%s"
result = DatabaseConnection.global_single_query(query, (id))
if len(result) == 0:
return None
match = Match(n=settings.n, match_id=id, player_a_name=result[0]['user_a'],
@ -20,45 +22,46 @@ class MatchManager(object):
return match
def get_matches_for_user(self, user_name):
query = f"SELECT * FROM matches WHERE user_a='{user_name}' OR user_b='{user_name}'"
return DatabaseConnection.global_single_query(query)
query = "SELECT * FROM matches WHERE user_a=%s OR user_b=%s"
return DatabaseConnection.global_single_query(query, (user_name, user_name))
def create_new_match(self, user_a, user_b):
match_id = uuid.uuid4().hex
# 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)
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()
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)}')"
DatabaseConnection.global_single_execution(query)
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, (match_id, user_a, user_b, match.to_json_state(), match.get_current_player(), get_sql_time(now)))
return match
def update_match(self, match_id, match):
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}'"
DatabaseConnection.global_single_execution(query)
query = "UPDATE matches SET match_state=%s, active_user=%s, last_active=%s WHERE id=%s"
DatabaseConnection.global_single_execution(
query, (match.to_json_state(), match.get_current_player(), now, match_id))
def apply_move(self, move_data):
match = self.get_match(move_data['id'])
if match is 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
def get_session_by_id(self, session_id):
query = f"SELECT * FROM sessions WHERE id='{session_id}'"
return DatabaseConnection.global_single_query(query)
query = "SELECT * FROM sessions WHERE id=%s"
return DatabaseConnection.global_single_query(query, (session_id))
def touch_session(self, session_id):
query = f"UPDATE sessions SET last_seen='{datetime.datetime.now()}' WHERE id='{session_id}'"
DatabaseConnection.global_single_execution(query)
query = "UPDATE sessions SET last_seen=%s WHERE id=%s"
DatabaseConnection.global_single_execution(query, (datetime.datetime.now(), session_id))
def get_session_by_registered_user(self, user_name):
query = f"SELECT * FROM sessions WHERE registered_user='{user_name}'"
return DatabaseConnection.global_single_query(query)
query = "SELECT * FROM sessions WHERE registered_user=%s"
return DatabaseConnection.global_single_query(query, (user_name))
def get_session_by_temp_user(self, user_name):
query = f"SELECT * FROM sessions WHERE temp_user='{user_name}'"
return DatabaseConnection.global_single_query(query)
query = "SELECT * FROM sessions WHERE temp_user=%s"
return DatabaseConnection.global_single_query(query, (user_name))
def create_session_for_registered_user(self, user_name):
new_id = uuid.uuid4().hex
# 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:
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()}')"
DatabaseConnection.global_single_execution(query)
# delete other active sessions:
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
def create_session_for_temp_user(self, user_name):
new_id = uuid.uuid4().hex
# 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:
return self.create_session_for_registered_user(user_name)
# delete other active sessions:
query = f"DELETE FROM sessions WHERE temp_user='{user_name}'"
DatabaseConnection.global_single_execution(query)
query = f"INSERT INTO sessions (id, registered_user, temp_user, last_seen) VALUES ( '{new_id}', NULL, '{user_name}', '{datetime.datetime.now()}')"
DatabaseConnection.global_single_execution(query)
query = "DELETE FROM sessions WHERE temp_user=%s"
DatabaseConnection.global_single_execution(query, (user_name))
query = "INSERT INTO sessions (id, registered_user, temp_user, last_seen) VALUES ( %s, NULL, %s, %s)"
DatabaseConnection.global_single_execution(query, (new_id, user_name, datetime.datetime.now()))
return new_id
def delete_session(self, session_id):
query = f"DELETE FROM sessions WHERE id='{session_id}'"
DatabaseConnection.global_single_execution(query)
query = "DELETE FROM sessions WHERE id=%s"
DatabaseConnection.global_single_execution(query, (session_id))
def revoke_inactive_sessions(self):
revoke_time = datetime.datetime.now() - self.session_lifespan_timedelta
query = f"SELECT * from sessions WHERE last_seen < '{get_sql_time(revoke_time)}'"
revoked_sessions = DatabaseConnection.global_single_query(query)
query = f"DELETE FROM sessions WHERE last_seen < '{get_sql_time(revoke_time)}'"
DatabaseConnection.global_single_execution(query)
query = "SELECT * from sessions WHERE last_seen < %s"
revoked_sessions = DatabaseConnection.global_single_query(query, (get_sql_time(revoke_time)))
query = "DELETE FROM sessions WHERE last_seen < %s"
DatabaseConnection.global_single_execution(query, (get_sql_time(revoke_time)))
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
def get_user(self, user_name):
query = f"SELECT name, last_seen FROM users where name='{user_name}'"
return DatabaseConnection.global_single_query(query)
query = "SELECT name, last_seen FROM users where name=%s"
return DatabaseConnection.global_single_query(query, (user_name))
def delete_user(self, user_name):
query = f"DELETE FROM users where name='{user_name}'"
DatabaseConnection.global_single_execution(query)
query = "DELETE FROM users where name=%s"
DatabaseConnection.global_single_execution(query, (user_name))
# remove from friends:
query = f"DELETE FROM friends WHERE user='{user_name}'"
DatabaseConnection.global_single_execution(query)
query = "DELETE FROM friends WHERE user=%s"
DatabaseConnection.global_single_execution(query, (user_name))
query = f"DELETE FROM friends WHERE friend='{user_name}'"
DatabaseConnection.global_single_execution(query)
query = "DELETE FROM friends WHERE friend=%s"
DatabaseConnection.global_single_execution(query, (user_name))
def verify_user(self, user_name, pw):
query = f"SELECT * FROM users where name='{user_name}'"
users = DatabaseConnection.global_single_query(query)
query = "SELECT * FROM users where name=%s"
users = DatabaseConnection.global_single_query(query, (user_name))
if len(self.get_user(user_name)) == 0:
return False
@ -41,33 +41,33 @@ class UserManager(object):
assert len(self.get_user(user_name)) == 0
pw_salt = uuid.uuid4().hex
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()}')"
DatabaseConnection.global_single_execution(query)
query = "INSERT INTO users (name, pw_hash, pw_salt, last_seen) VALUES ( %s, %s, %s, %s)"
DatabaseConnection.global_single_execution(query, (user_name, pw_hash, pw_salt, datetime.datetime.now()))
def touch_user(self, user_name):
matches = self.get_user(user_name)
assert len(matches) == 1
query = f"UPDATE users SET last_seen='{datetime.datetime.now()}' WHERE name='{user_name}'"
DatabaseConnection.global_single_execution(query)
query = "UPDATE users SET last_seen=%s WHERE name=%s"
DatabaseConnection.global_single_execution(query, (datetime.datetime.now(), user_name))
def add_friend_to_user(self, user_name, friend_name):
if len(self.get_user(friend_name)) > 0:
query = f"INSERT INTO friends (user, friend) VALUES ( '{user_name}', '{friend_name}')"
DatabaseConnection.global_single_execution(query)
query = "INSERT INTO friends (user, friend) VALUES ( %s, %s)"
DatabaseConnection.global_single_execution(query, (user_name, friend_name))
return True
return False
def get_friends_for_user(self, user_name):
query = f"SELECT friend FROM friends WHERE user='{user_name}'"
tmp = DatabaseConnection.global_single_query(query)
query = "SELECT friend FROM friends WHERE user=%s"
tmp = DatabaseConnection.global_single_query(query, (user_name))
friends = set()
for entry in tmp:
friends.add(entry['friend'])
return friends
def remove_friend_from_user(self, user_name, friend_name):
query = f"DELETE FROM friends WHERE user='{user_name}' AND friend='{friend_name}'"
DatabaseConnection.global_single_execution(query)
query = "DELETE FROM friends WHERE user=%s AND friend=%s"
DatabaseConnection.global_single_execution(query, (user_name, friend_name))
def get_all_users(self):
query = "SELECT name, last_seen FROM users"