CVE-2023-0227: Insufficient Session Expiration (CWE-613)
value: Insufficient Session Expiration in GitHub repository pyload/pyload prior to 0.5.0b3.dev36.
CWE description: According to WASC, "Insufficient Session Expiration is when a web site permits an attacker to reuse old session credentials or session IDs for authorization."
CVSS v3 base score: 6.5
Published: 2023-01-12T01:15Z
Repository: https://github.com/pyload/pyload
Language: Python
Files changed: src/pyload/core/api/__init__.py, src/pyload/core/database/user_database.py, src/pyload/webui/app/helpers.py
Commit date: 2023-01-12 02:00:29 +0200
Commit message: fix issue that caused the session not expire after user was deleted

Diff: diff --git a/src/pyload/core/api/__init__.py b/src/pyload/core/api/__init__.py
index 280dcbcbe7..6b0ef87cde 100644
--- a/src/pyload/core/api/__init__.py
+++ b/src/pyload/core/api/__init__.py
@@ -1115,11 +1115,11 @@ class Api:
 
     @legacy("getEvents")
     @permission(Perms.STATUS)
     def get_events(self, uuid):
         """
-        Lists occured events, may be affected to changes in future.
+        Lists occurred events, may be affected to changes in the future.
 
         :param uuid:
         :return: list of `Events`
         """
         events = self.pyload.event_manager.get_events(uuid)
@@ -1228,10 +1228,19 @@ class Api:
         :param password:
         :return: dict with info, empty when login is incorrect
         """
         return self.pyload.db.check_auth(username, password)
 
+    def user_exists(self, username):
+        """
+        Check if a user actually exists in the database.
+
+        :param username:
+        :return: boolean
+        """
+        return self.pyload.db.user_exists(username)
+
     @legacy("isAuthorized")
     def is_authorized(self, func, userdata):
         """
         checks if the user is authorized for specific method.
 
diff --git a/src/pyload/core/database/user_database.py b/src/pyload/core/database/user_database.py
index 628da74711..607fa8373f 100644
--- a/src/pyload/core/database/user_database.py
+++ b/src/pyload/core/database/user_database.py
@@ -90,10 +90,15 @@ class UserDatabaseMethods:
 
     @style.async_
     def set_role(self, user, role):
         self.c.execute("UPDATE users SET role=? WHERE name=?", (role, user))
 
+    @style.queue
+    def user_exists(self, user):
+        self.c.execute("SELECT name FROM users WHERE name=?", (user,))
+        return self.c.fetchone() is not None
+
     @style.queue
     def list_users(self):
         self.c.execute("SELECT name FROM users")
         users = []
         for row in self.c:
diff --git a/src/pyload/webui/app/helpers.py b/src/pyload/webui/app/helpers.py
index 9f1e9c0703..6cdaf46ea4 100644
--- a/src/pyload/webui/app/helpers.py
+++ b/src/pyload/webui/app/helpers.py
@@ -162,13 +162,15 @@ def apiver_check(func):
 
     return wrapper
 
 
 def is_authenticated(session=flask.session):
-    return session.get("name") and session.get(
-        "authenticated"
-    )  # NOTE: why checks name?
+    api = flask.current_app.config["PYLOAD_API"]
+    user = session.get("name")
+    authenticated = session.get("authenticated")
+
+    return authenticated and api.user_exists(user)
 
 
 def login_required(perm):
     def decorator(func):
         @wraps(func)

Vulnerable code:         Lists occured events, may be affected to changes in future.
    return session.get("name") and session.get(
        "authenticated"
    )  # NOTE: why checks name?
Fixed code:         Lists occurred events, may be affected to changes in the future.
    def user_exists(self, username):
        """
        Check if a user actually exists in the database.

        :param username:
        :return: boolean
        """
        return self.pyload.db.user_exists(username)

    @style.queue
    def user_exists(self, user):
        self.c.execute("SELECT name FROM users WHERE name=?", (user,))
        return self.c.fetchone() is not None

    api = flask.current_app.config["PYLOAD_API"]
    user = session.get("name")
    authenticated = session.get("authenticated")

    return authenticated and api.user_exists(user)