CVE-2024-5824 (published 2024-06-27T19:15Z)

A path traversal vulnerability in the `/set_personality_config` endpoint of parisneo/lollms version 9.4.0 allows an attacker to overwrite the `configs/config.yaml` file. This can lead to remote code execution by changing server configuration properties such as `force_accept_remote_access` and `turn_on_code_validation`.


Weakness information:
cwe_id: CWE-22
cwe_name: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
description: The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.
extended_description: 
            Many file operations are intended to take place within a restricted directory. By using special elements such as ".." and "/" separators, attackers can escape outside of the restricted location to access files or directories that are elsewhere on the system. One of the most common special elements is the "../" sequence, which in most modern operating systems is interpreted as the parent directory of the current location. This is referred to as relative path traversal. Path traversal also covers the use of absolute pathnames such as "/usr/local/bin" to access unexpected files. This is referred to as absolute path traversal.
         
         

Fix information:
repo_url: https://github.com/parisneo/lollms
commit_details: repo_url: https://github.com/parisneo/lollms
author: Saifeddine ALOUI
author_date: 2024-05-19 17:38:38+02:00
msg: Added sanitize_svg function and forbid_remote_access is used for setting personality settings
repository: repo_url: https://github.com/parisneo/lollms
repo_name: ParisNeo/lollms
description: Lord of LLMS
repo_language: Python
owner: parisneo
file_changes: filename: security.py
change_type: ModificationType.MODIFY
diff: @@ -8,6 +8,12 @@ import os
 import re
 import platform
 import string
+from lollms.utilities import PackageManager
+
+if not PackageManager.check_package_installed("lxml"):
+    PackageManager.install_package("lxml")
+
+import lxml.etree as ET
 
 def check_access(lollmsElfServer, client_id):
     client = lollmsElfServer.session.get_client(client_id)
@@ -60,6 +66,30 @@ def sanitize_after_whitelisted_command(line, command):
         return line[:command_end_index + len(sanitized_rest)].strip()
 
 
+def sanitize_svg(svg_content):
+    try:
+        parser = ET.XMLParser(remove_comments=True, remove_pis=True)
+        tree = ET.fromstring(svg_content, parser=parser)
+
+        # Remove any script elements
+        for script in tree.xpath('//svg:script', namespaces={'svg': 'http://www.w3.org/2000/svg'}):
+            parent = script.getparent()
+            if parent is not None:
+                parent.remove(script)
+
+        # Remove any 'on*' event attributes
+        for element in tree.xpath('//*[@*[starts-with(name(), "on")]]'):
+            for attr in list(element.attrib):
+                if attr.startswith('on'):
+                    del element.attrib[attr]
+
+        # Convert the tree back to an SVG string
+        sanitized_svg = ET.tostring(tree, encoding='unicode', method='xml')
+        return sanitized_svg
+    except ET.XMLSyntaxError as e:
+        raise ValueError("Invalid SVG content") from e
+
+
 def sanitize_shell_code(code, whitelist=None):
     """
     Securely sanitizes a block of code by allowing commands from a provided whitelist,

programming_language: Python
filename: lollms_personalities_infos.py
change_type: ModificationType.MODIFY
diff: @@ -16,7 +16,7 @@ from lollms.server.elf_server import LOLLMSElfServer
 from lollms.personality import AIPersonality, InstallOption
 from ascii_colors import ASCIIColors
 from lollms.utilities import load_config, trace_exception, gc, show_yes_no_dialog
-from lollms.security import check_access
+from lollms.security import check_access, forbid_remote_access
 from pathlib import Path
 from typing import List, Optional
 import psutil
@@ -329,6 +329,8 @@ def get_personality_config(data:PersonalityDataRequest):
     print("- Recovering personality config")
     category = sanitize_path(data.category)
     name = sanitize_path(data.name)
+    if category=="":
+        return {"status":False, "error":"category must not be empty."}
 
     package_path = f"{category}/{name}"
     if category=="custom_personalities":
@@ -352,12 +354,15 @@ class PersonalityConfig(BaseModel):
 
 @router.post("/set_personality_config")
 def set_personality_config(data:PersonalityConfig):
+    forbid_remote_access(lollmsElfServer)
     check_access(lollmsElfServer, data.client_id)
     print("- Recovering personality config")
     category = sanitize_path(data.category)
     name = sanitize_path(data.name)
     config = data.config
-
+    if category=="":
+        return {"status":False, "error":"category must not be empty."}
+    
     package_path = f"{category}/{name}"
     if category=="custom_personalities":
         package_full_path = lollmsElfServer.lollms_paths.custom_personalities_path/f"{name}"
@@ -388,6 +393,8 @@ def mount_personality(data:PersonalityMountingInfos):
     category = sanitize_path(data.category)
     name = sanitize_path(data.folder)
     language = data.language #.get('language', None)
+    if category=="":
+        return {"status":False, "error":"category must not be empty."}
 
     package_path = f"{category}/{name}"
     if category=="custom_personalities":
@@ -443,6 +450,8 @@ def remount_personality(data:PersonalityMountingInfos):
     name = sanitize_path(data.folder)
     language = data.language #.get('language', None)
 
+    if category=="":
+        return {"status":False, "error":"category must not be empty."}
 
     package_path = f"{category}/{name}"
     if category=="custom_personalities":
@@ -497,6 +506,9 @@ def unmount_personality(data:PersonalityMountingInfos):
     name = sanitize_path(data.folder)
     language = data.language #.get('language', None)
 
+    if category=="":
+        return {"status":False, "error":"category must not be empty."}
+
     try:
         personality_id = f"{category}/{name}" if language is None or language=="" else f"{category}/{name}:{language}"
         index = lollmsElfServer.config["personalities"].index(personality_id)
@@ -595,6 +607,8 @@ def get_personality_settings(data:PersonalityMountingInfos):
     print("- Retreiving personality settings")
     category = sanitize_path(data.category)
     name = sanitize_path(data.folder)
+    if category=="":
+        return {"status":False, "error":"category must not be empty."}
 
     if category == "custom_personalities":
         personality_folder = lollmsElfServer.lollms_paths.personal_personalities_path/f"{name}"

programming_language: Python
filename: utilities.py
change_type: ModificationType.MODIFY
diff: @@ -37,7 +37,6 @@ import git
 
 import mimetypes
 import subprocess
-from lollms.security import sanitize_shell_code
 
 from functools import partial
 
@@ -59,6 +58,7 @@ def install_conda_utility():
 
 
 def create_conda_env(env_name, python_version):
+    from lollms.security import sanitize_shell_code
     env_name = sanitize_shell_code(env_name)
     python_version = sanitize_shell_code(python_version)
     # Activate the Conda environment
@@ -132,6 +132,7 @@ def get_conda_path():
         return Path(sys.executable).parent.parent.parent / "miniconda3" / "bin" / "conda"
 
 def environment_exists(env_name):
+    from lollms.security import sanitize_shell_code
     env_name = sanitize_shell_code(env_name)
     conda_path = get_conda_path()
     result = subprocess.run(f'{conda_path} env list --json', shell=True, capture_output=True, text=True)
@@ -140,6 +141,7 @@ def environment_exists(env_name):
     return env_name in env_names
 
 def get_python_version(env_name):
+    from lollms.security import sanitize_shell_code
     env_name = sanitize_shell_code(env_name)
     conda_path = get_conda_path()
     if environment_exists(env_name):
@@ -149,6 +151,7 @@ def get_python_version(env_name):
         return "Environment does not exist."
 
 def remove_environment(env_name):
+    from lollms.security import sanitize_shell_code
     env_name = sanitize_shell_code(env_name)
     conda_path = get_conda_path()
     if environment_exists(env_name):

programming_language: Python