#31 Fix vulnerabilities with the remember cookie

Злито
sindre злито 4 комітів з session-cookie до master 5 роки тому
  1. +9
    -2
      mysql/sql/init.sql
  2. +73
    -0
      src/app/models/session.py
  3. +28
    -31
      src/app/views/login.py
  4. +9
    -1
      src/app/views/logout.py

+ 9
- 2
mysql/sql/init.sql Переглянути файл

@@ -14,6 +14,14 @@ CREATE TABLE users (
PRIMARY KEY (userid)
);

CREATE TABLE cookies (
token VARCHAR(100) UNIQUE NOT NULL,
userid INT UNSIGNED NOT NULL,
expiry INT NOT NULL,
PRIMARY KEY (token),
FOREIGN KEY (userid) REFERENCES users(userid)
);

CREATE TABLE project_category (
categoryid INT UNSIGNED AUTO_INCREMENT,
category_name VARCHAR(200) UNIQUE NOT NULL,
@@ -84,9 +92,8 @@ insert into project_category values (NULL, "Grocery shopping");


/*
Create default database user
Create default database user
*/

CREATE USER 'root'@'10.5.0.6' IDENTIFIED BY 'root';
GRANT ALL PRIVILEGES ON db.* TO 'root'@'10.5.0.6';


+ 73
- 0
src/app/models/session.py Переглянути файл

@@ -0,0 +1,73 @@
from models.database import db
import mysql.connector


def set_cookie(userid, token, expiry):
"""
Register a persistant login token for an user
:param userid: The ID of the user
:param token: A random unique token for the cookie
"""
db.connect()
cursor = db.cursor()
query = ("INSERT INTO cookies (userid, token, expiry) VALUES (%s, %s, %s)")

try:
cursor.execute(query, (userid, token, expiry))
db.commit()
except mysql.connector.Error as err:
print("Failed executing query: {}".format(err))
cursor.fetchall()
exit(1)
finally:
cursor.close()
db.close()


def get_cookie(token):
"""
Get the userid of the user with the given persistant login token
:param token: The token to search for
"""
db.connect()
cursor = db.cursor()
query = ("SELECT userid, expiry FROM cookies WHERE token = %s")
userid = None
expiry = None

try:
cursor.execute(query, (token,))
users = cursor.fetchall()
if len(users):
userid = users[0][0]
expiry = users[0][1]
except mysql.connector.Error as err:
print("Failed executing query: {}".format(err))
cursor.fetchall()
exit(1)
finally:
cursor.close()
db.close()

return userid, expiry


def delete_cookie(token):
"""
Get the userid of the user with the given persistant login token
:param token: The token to delete
"""
db.connect()
cursor = db.cursor()
query = ("DELETE FROM cookies WHERE token = %s")

try:
cursor.execute(query, (token,))
db.commit()
except mysql.connector.Error as err:
print("Failed executing query: {}".format(err))
cursor.fetchall()
exit(1)
finally:
cursor.close()
db.close()

+ 28
- 31
src/app/views/login.py Переглянути файл

@@ -1,13 +1,19 @@
import web
from views.forms import login_form
import models.session
import models.user
from views.utils import get_nav_bar
import os, hmac, base64, pickle
import random
import string
import hashlib
import time

# Get html templates
render = web.template.render('templates/')

# The remember cookie should be valid for a week
remember_timeout = 3600*24*7


class Login():

@@ -17,7 +23,7 @@ class Login():
def GET(self):
"""
Show the login page
:return: The login page showing other users if logged in
"""
session = web.ctx.session
@@ -40,7 +46,7 @@ class Login():
# Validate login credential with database query
password_hash = hashlib.md5(b'TDT4237' + data.password.encode('utf-8')).hexdigest()
user = models.user.match_user(data.username, password_hash)
# If there is a matching user/password in the database the user is logged in
if user:
self.login(user[1], user[0], data.remember)
@@ -56,51 +62,42 @@ class Login():
session.username = username
session.userid = userid
if remember:
rememberme = self.rememberme()
web.setcookie('remember', rememberme , 300000000)
rememberme = self.rememberme(remember_timeout)
path = web.ctx.homepath + "/"
web.ctx.headers.append(('Set-Cookie', f'remember={rememberme}; Max-Age={remember_timeout}; Path={path}; Secure; HttpOnly; SameSite=Strict'))

def check_rememberme(self):
"""
Validate the rememberme cookie and log in
"""
username = ""
sign = ""
userid = None
# If the user selected 'remember me' they log in automatically
try:
# Fetch the users cookies if it exists
cookies = web.cookies()
# Fetch the remember cookie and convert from string to bytes
remember_hash = bytes(cookies.remember[2:][:-1], 'ascii')
# Decode the hash
decode = base64.b64decode(remember_hash)
# Load the decoded hash to receive the host signature and the username
username, sign = pickle.loads(decode)
except AttributeError as e:
remember_token = cookies.remember
userid, expiry = models.session.get_cookie(remember_token)
except AttributeError:
# The user did not have the stored remember me cookie
pass

# If the users signed cookie matches the host signature then log in
if self.sign_username(username) == sign:
userid = models.user.get_user_id_by_name(username)
if userid is not None and expiry > time.time():
username = models.user.get_user_name_by_id(userid)
self.login(username, userid, False)

def rememberme(self):
def rememberme(self, timeout):
"""
Encode a base64 object consisting of the username signed with the
host secret key and the username. Can be reassembled with the
hosts secret key to validate user.
:return: base64 object consisting of signed username and username
Generate a random token for the user, and store it in the database.
"""
session = web.ctx.session
creds = [ session.username, self.sign_username(session.username) ]
return base64.b64encode(pickle.dumps(creds))
alphabet = string.ascii_uppercase + string.digits

@classmethod
def sign_username(self, username):
"""
Sign the current users name with the hosts secret key
:return: The users signed name
"""
secret = base64.b64decode(self.secret)
return hmac.HMAC(secret, username.encode('ascii')).hexdigest()
while True:
token = ''.join(random.SystemRandom().choice(alphabet) for _ in range(20))
if models.session.get_cookie(token)[0] is None:
break

models.session.set_cookie(session.userid, token, int(time.time() + timeout))
return token

+ 9
- 1
src/app/views/logout.py Переглянути файл

@@ -1,5 +1,6 @@
import web
from views.utils import get_nav_bar
import models.session

# Get html templates
render = web.template.render('templates/')
@@ -12,9 +13,16 @@ class Logout:
Log out of the application (kill session and reset variables)
:return: Redirect to main page
"""
try:
remember_token = web.cookies().remember
models.session.delete_cookie(remember_token)
except AttributeError:
# The user did not have the stored remember me cookie
pass

session = web.ctx.session
session.username = None
session.userid = None
web.setcookie('remember', '', 0)
session.kill()
session.kill()
raise web.seeother('/')

Завантаження…
Відмінити
Зберегти