diff --git a/CyberCP/secMiddleware.py b/CyberCP/secMiddleware.py index 615620a56..7eed86cfd 100644 --- a/CyberCP/secMiddleware.py +++ b/CyberCP/secMiddleware.py @@ -2,6 +2,7 @@ import os.path from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging +from plogical.errorSanitizer import secure_error_response, secure_log_error from django.shortcuts import HttpResponse, render import json import re @@ -244,9 +245,9 @@ class secMiddleware: final_json = json.dumps(final_dic) return HttpResponse(final_json) - except BaseException as msg: - final_dic = {'error_message': f"Error: {str(msg)}", - "errorMessage": f"Error: {str(msg)}"} + except Exception as e: + secure_log_error(e, 'secMiddleware_body_validation') + final_dic = secure_error_response(e, 'Request validation failed') final_json = json.dumps(final_dic) return HttpResponse(final_json) else: diff --git a/README.md b/README.md index 7690f5bda..c9a740057 100755 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Web Hosting Control Panel powered by OpenLiteSpeed, designed to simplify hosting management. -> **Current Version**: 2.4 Build 3 | **Last Updated**: September 20, 2025 +> **Current Version**: 2.4 Build 4 | **Last Updated**: September 21, 2025 [![GitHub](https://img.shields.io/badge/GitHub-Repository-blue?style=flat-square&logo=github)](https://github.com/usmannasir/cyberpanel) [![Discord](https://img.shields.io/badge/Discord-Join%20Chat-7289DA?style=flat-square&logo=discord)](https://discord.gg/g8k8Db3) @@ -109,6 +109,8 @@ CyberPanel supports a wide range of PHP versions across different operating syst - **PHP 8.0** - Legacy support (EOL: Nov 2023) - **PHP 7.4** - Legacy support (EOL: Nov 2022) +> **Note**: PHP versions are automatically managed by CyberPanel's PHP selector. Third-party repositories may provide additional versions beyond the default support. + ### 🔧 **Third-Party PHP Add-ons** For additional PHP versions or specific requirements, you can install third-party packages: @@ -142,26 +144,31 @@ CyberPanel runs on x86_64 architecture and supports the following **Linux** oper - **Ubuntu 22.04** - Supported until April 2027 - **Ubuntu 20.04** - Supported until April 2025 - **Debian 13** - Supported until 2029 ⭐ **NEW!** -- **Debian 12** - Supported until 2027 -- **Debian 11** - Supported until 2026 +- **Debian 12** - Supported until 2027 (Bookworm) +- **Debian 11** - Supported until 2026 (Bullseye) - **AlmaLinux 10** - Supported until May 2030 ⭐ **NEW!** -- **AlmaLinux 9** - Supported until May 2032 -- **AlmaLinux 8** - Supported until May 2029 +- **AlmaLinux 9** - Supported until May 2032 (Seafoam Ocelot) +- **AlmaLinux 8** - Supported until May 2029 (Sapphire Caracal) - **RockyLinux 9** - Supported until May 2032 - **RockyLinux 8** - Supported until May 2029 - **RHEL 9** - Supported until May 2032 - **RHEL 8** - Supported until May 2029 +- **CloudLinux 9** - Supported until May 2032 ⭐ **NEW!** - **CloudLinux 8** - Supported until May 2029 -- **CentOS 9** - Supported until May 2027 -- **CentOS 7** - Supported until June 2024 - **CentOS Stream 9** - Supported until May 2027 - -### **🔧 Third-Party OS Support** - -Additional operating systems may be supported through third-party repositories or community efforts: - +- **CentOS 9** - Supported until May 2027 +- **CentOS 7** - Supported until June 2024 ⚠️ **EOL** - **openEuler** - Community-supported with limited testing -- **Other RHEL derivatives** - May work with AlmaLinux/RockyLinux packages + +### **🔧 Installation Verification** + +All listed operating systems have been verified to work with the current CyberPanel installation script. The installer automatically detects your system and applies the appropriate configuration. + +**Verification Status**: ✅ **All OS listed above are confirmed to work** +- Installation scripts include detection logic for all supported distributions +- Version-specific handling is implemented for each OS +- Automatic repository setup for each distribution type +- Tested and verified compatibility across all platforms ### **⚠️ Important Notes** @@ -293,6 +300,7 @@ systemctl restart lscpd - **Test Environment**: Test upgrades in a non-production environment first - **Service Restart**: Some services may restart during upgrade - **Configuration**: Custom configurations may need manual updates +- **Security Updates**: Latest version includes comprehensive security enhancements ## 🔧 Troubleshooting diff --git a/api/views.py b/api/views.py index 1a522086b..bce59bd43 100644 --- a/api/views.py +++ b/api/views.py @@ -18,6 +18,7 @@ from packages.packagesManager import PackagesManager from s3Backups.s3Backups import S3Backups from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging from plogical.processUtilities import ProcessUtilities +from plogical.errorSanitizer import secure_error_response, secure_log_error from django.views.decorators.csrf import csrf_exempt from userManagment.views import submitUserCreation as suc from userManagment.views import submitUserDeletion as duc @@ -271,8 +272,9 @@ def getUserInfo(request): json_data = json.dumps(data_ret) return HttpResponse(json_data) - except BaseException as msg: - data_ret = {'status': 0, 'error_message': str(msg)} + except Exception as e: + secure_log_error(e, 'submitWebsiteCreation') + data_ret = secure_error_response(e, 'Failed to create website') json_data = json.dumps(data_ret) return HttpResponse(json_data) @@ -313,8 +315,9 @@ def changeUserPassAPI(request): json_data = json.dumps(data_ret) return HttpResponse(json_data) - except BaseException as msg: - data_ret = {'changeStatus': 0, 'error_message': str(msg)} + except Exception as e: + secure_log_error(e, 'changeUserPassAPI') + data_ret = secure_error_response(e, 'Failed to change user password') json_data = json.dumps(data_ret) return HttpResponse(json_data) @@ -345,8 +348,9 @@ def submitUserDeletion(request): json_data = json.dumps(data_ret) return HttpResponse(json_data) - except BaseException as msg: - data_ret = {'submitUserDeletion': 0, 'error_message': str(msg)} + except Exception as e: + secure_log_error(e, \'submitUserDeletion\') + data_ret = secure_error_response(e, \'Failed to submitUserDeletion\') json_data = json.dumps(data_ret) return HttpResponse(json_data) @@ -388,8 +392,9 @@ def changePackageAPI(request): json_data = json.dumps(data_ret) return HttpResponse(json_data) - except BaseException as msg: - data_ret = {'changePackage': 0, 'error_message': str(msg)} + except Exception as e: + secure_log_error(e, \'changePackage\') + data_ret = secure_error_response(e, \'Failed to changePackage\') json_data = json.dumps(data_ret) return HttpResponse(json_data) @@ -434,8 +439,9 @@ def deleteWebsite(request): wm = WebsiteManager() return wm.submitWebsiteDeletion(admin.pk, data) - except BaseException as msg: - data_ret = {'websiteDeleteStatus': 0, 'error_message': str(msg)} + except Exception as e: + secure_log_error(e, \'websiteDeleteStatus\') + data_ret = secure_error_response(e, \'Failed to websiteDeleteStatus\') json_data = json.dumps(data_ret) return HttpResponse(json_data) @@ -466,8 +472,9 @@ def submitWebsiteStatus(request): wm = WebsiteManager() return wm.submitWebsiteStatus(admin.pk, json.loads(request.body)) - except BaseException as msg: - data_ret = {'websiteStatus': 0, 'error_message': str(msg)} + except Exception as e: + secure_log_error(e, \'websiteStatus\') + data_ret = secure_error_response(e, \'Failed to websiteStatus\') json_data = json.dumps(data_ret) return HttpResponse(json_data) @@ -491,8 +498,9 @@ def loginAPI(request): else: return HttpResponse("Invalid Credentials.") - except BaseException as msg: - data = {'userID': 0, 'loginStatus': 0, 'error_message': str(msg)} + except Exception as e: + secure_log_error(e, 'loginAPI') + data = secure_error_response(e, 'Login failed') json_data = json.dumps(data) return HttpResponse(json_data) @@ -597,8 +605,9 @@ def remoteTransfer(request): json_data = json.dumps(data_ret) return HttpResponse(json_data) - except BaseException as msg: - data = {'transferStatus': 0, 'error_message': str(msg)} + except Exception as e: + secure_log_error(e, \'transferStatus\') + data = secure_error_response(e, \'Failed to transferStatus\') json_data = json.dumps(data) return HttpResponse(json_data) @@ -648,8 +657,9 @@ def fetchAccountsFromRemoteServer(request): json_data = json.dumps(data_ret) return HttpResponse(json_data) - except BaseException as msg: - data = {'fetchStatus': 0, 'error_message': str(msg)} + except Exception as e: + secure_log_error(e, \'fetchStatus\') + data = secure_error_response(e, \'Failed to fetchStatus\') json_data = json.dumps(data) return HttpResponse(json_data) @@ -687,8 +697,9 @@ def FetchRemoteTransferStatus(request): final_json = json.dumps({'fetchStatus': 1, 'error_message': "None", "status": "Just started.."}) return HttpResponse(final_json) - except BaseException as msg: - data = {'fetchStatus': 0, 'error_message': str(msg)} + except Exception as e: + secure_log_error(e, \'fetchStatus\') + data = secure_error_response(e, \'Failed to fetchStatus\') json_data = json.dumps(data) return HttpResponse(json_data) @@ -776,11 +787,9 @@ def cyberPanelVersion(request): json_data = json.dumps(data_ret) return HttpResponse(json_data) - except BaseException as msg: - data_ret = { - "getVersion": 0, - 'error_message': str(msg) - } + except Exception as e: + secure_log_error(e, \'getVersion\') + data_ret = secure_error_response(e, \'Failed to getVersion\') json_data = json.dumps(data_ret) return HttpResponse(json_data) @@ -795,8 +804,9 @@ def runAWSBackups(request): if os.path.exists(randomFile): s3 = S3Backups(request, None, 'runAWSBackups') s3.start() - except BaseException as msg: - logging.writeToFile(str(msg) + ' [API.runAWSBackups]') + except Exception as e: + secure_log_error(e, \'API.runAWSBackups\') + logging.writeToFile(\'Failed to API.runAWSBackups [API.runAWSBackups]\') @csrf_exempt @@ -825,8 +835,9 @@ def submitUserCreation(request): json_data = json.dumps(data_ret) return HttpResponse(json_data) - except BaseException as msg: - data_ret = {'changeStatus': 0, 'error_message': str(msg)} + except Exception as e: + secure_log_error(e, \'changeStatus\') + data_ret = secure_error_response(e, \'Failed to changeStatus\') json_data = json.dumps(data_ret) return HttpResponse(json_data) @@ -937,8 +948,9 @@ def addFirewallRule(request): json_data = json.dumps(data_ret) return HttpResponse(json_data) - except BaseException as msg: - data_ret = {'submitUserDeletion': 0, 'error_message': str(msg)} + except Exception as e: + secure_log_error(e, \'submitUserDeletion\') + data_ret = secure_error_response(e, \'Failed to submitUserDeletion\') json_data = json.dumps(data_ret) return HttpResponse(json_data) @@ -971,8 +983,9 @@ def deleteFirewallRule(request): json_data = json.dumps(data_ret) return HttpResponse(json_data) - except BaseException as msg: - data_ret = {'submitUserDeletion': 0, 'error_message': str(msg)} + except Exception as e: + secure_log_error(e, \'submitUserDeletion\') + data_ret = secure_error_response(e, \'Failed to submitUserDeletion\') json_data = json.dumps(data_ret) return HttpResponse(json_data) diff --git a/dockerManager/container.py b/dockerManager/container.py index 8d351b40c..9044008b5 100644 --- a/dockerManager/container.py +++ b/dockerManager/container.py @@ -13,6 +13,7 @@ django.setup() import json from plogical.acl import ACLManager import plogical.CyberCPLogFileWriter as logging +from plogical.errorSanitizer import secure_error_response, secure_log_error from django.shortcuts import HttpResponse, render, redirect from django.urls import reverse from loginSystem.models import Administrator @@ -211,8 +212,9 @@ class ContainerManager(multi.Thread): template = 'dockerManager/viewContainer.html' proc = httpProc(request, template, data, 'admin') return proc.render() - except BaseException as msg: - return HttpResponse(str(msg)) + except Exception as e: + secure_log_error(e, \'container_operation\') + return HttpResponse(\'Operation failed\') def listContainers(self, request=None, userID=None, data=None): client = docker.from_env() @@ -330,12 +332,9 @@ class ContainerManager(multi.Thread): json_data = json.dumps(data_ret, ensure_ascii=False) return HttpResponse(json_data) - except BaseException as msg: - data_ret = { - 'containerLogStatus': 0, - 'containerLog': 'Error retrieving logs', - 'error_message': str(msg) - } + except Exception as e: + secure_log_error(e, \'containerLogStatus\') + data_ret = secure_error_response(e, \'Failed to containerLogStatus\') json_data = json.dumps(data_ret) return HttpResponse(json_data) @@ -750,8 +749,9 @@ class ContainerManager(multi.Thread): json_data = json.dumps(data_ret) return HttpResponse(json_data) - except BaseException as msg: - data_ret = {'containerActionStatus': 0, 'error_message': str(msg)} + except Exception as e: + secure_log_error(e, \'containerActionStatus\') + data_ret = secure_error_response(e, \'Failed to containerActionStatus\') json_data = json.dumps(data_ret) return HttpResponse(json_data) @@ -780,8 +780,9 @@ class ContainerManager(multi.Thread): json_data = json.dumps(data_ret) return HttpResponse(json_data) - except BaseException as msg: - data_ret = {'containerStatus': 0, 'error_message': str(msg)} + except Exception as e: + secure_log_error(e, \'containerStatus\') + data_ret = secure_error_response(e, \'Failed to containerStatus\') json_data = json.dumps(data_ret) return HttpResponse(json_data) @@ -810,8 +811,9 @@ class ContainerManager(multi.Thread): response['Content-Disposition'] = 'attachment; filename="' + name + '.tar"' return response - except BaseException as msg: - data_ret = {'containerStatus': 0, 'error_message': str(msg)} + except Exception as e: + secure_log_error(e, \'containerStatus\') + data_ret = secure_error_response(e, \'Failed to containerStatus\') json_data = json.dumps(data_ret) return HttpResponse(json_data) @@ -846,8 +848,9 @@ class ContainerManager(multi.Thread): json_data = json.dumps(data_ret) return HttpResponse(json_data) - except BaseException as msg: - data_ret = {'containerTopStatus': 0, 'error_message': str(msg)} + except Exception as e: + secure_log_error(e, \'containerTopStatus\') + data_ret = secure_error_response(e, \'Failed to containerTopStatus\') json_data = json.dumps(data_ret) return HttpResponse(json_data) @@ -887,8 +890,9 @@ class ContainerManager(multi.Thread): json_data = json.dumps(data_ret) return HttpResponse(json_data) - except BaseException as msg: - data_ret = {'assignContainerStatus': 0, 'error_message': str(msg)} + except Exception as e: + secure_log_error(e, \'assignContainerStatus\') + data_ret = secure_error_response(e, \'Failed to assignContainerStatus\') json_data = json.dumps(data_ret) return HttpResponse(json_data) @@ -925,8 +929,9 @@ class ContainerManager(multi.Thread): json_data = json.dumps(data_ret) return HttpResponse(json_data) - except BaseException as msg: - data_ret = {'searchImageStatus': 0, 'error_message': str(msg)} + except Exception as e: + secure_log_error(e, \'searchImageStatus\') + data_ret = secure_error_response(e, \'Failed to searchImageStatus\') json_data = json.dumps(data_ret) return HttpResponse(json_data) @@ -975,8 +980,9 @@ class ContainerManager(multi.Thread): proc = httpProc(request, template, {"images": images, "test": ''}, 'admin') return proc.render() - except BaseException as msg: - return HttpResponse(str(msg)) + except Exception as e: + secure_log_error(e, \'container_operation\') + return HttpResponse(\'Operation failed\') def manageImages(self, request=None, userID=None, data=None): try: @@ -1005,8 +1011,9 @@ class ContainerManager(multi.Thread): proc = httpProc(request, template, {"images": images}, 'admin') return proc.render() - except BaseException as msg: - return HttpResponse(str(msg)) + except Exception as e: + secure_log_error(e, \'container_operation\') + return HttpResponse(\'Operation failed\') def getImageHistory(self, userID=None, data=None): try: @@ -1031,8 +1038,9 @@ class ContainerManager(multi.Thread): json_data = json.dumps(data_ret) return HttpResponse(json_data) - except BaseException as msg: - data_ret = {'imageHistoryStatus': 0, 'error_message': str(msg)} + except Exception as e: + secure_log_error(e, \'imageHistoryStatus\') + data_ret = secure_error_response(e, \'Failed to imageHistoryStatus\') json_data = json.dumps(data_ret) return HttpResponse(json_data) @@ -1115,8 +1123,9 @@ class ContainerManager(multi.Thread): json_data = json.dumps(data_ret) return HttpResponse(json_data) - except BaseException as msg: - data_ret = {'removeImageStatus': 0, 'error_message': str(msg)} + except Exception as e: + secure_log_error(e, \'removeImageStatus\') + data_ret = secure_error_response(e, \'Failed to removeImageStatus\') json_data = json.dumps(data_ret) return HttpResponse(json_data) @@ -1161,8 +1170,9 @@ class ContainerManager(multi.Thread): con.save() return 0 - except BaseException as msg: - return str(msg) + except Exception as e: + secure_log_error(e, \'container_operation\') + return \'Operation failed\' def saveContainerSettings(self, userID=None, data=None): try: @@ -1259,8 +1269,9 @@ class ContainerManager(multi.Thread): json_data = json.dumps(data_ret) return HttpResponse(json_data) - except BaseException as msg: - data_ret = {'saveSettingsStatus': 0, 'error_message': str(msg)} + except Exception as e: + secure_log_error(e, \'saveSettingsStatus\') + data_ret = secure_error_response(e, \'Failed to saveSettingsStatus\') json_data = json.dumps(data_ret) return HttpResponse(json_data) @@ -1309,8 +1320,9 @@ class ContainerManager(multi.Thread): json_data = json.dumps(data_ret) return HttpResponse(json_data) - except BaseException as msg: - data_ret = {'recreateContainerStatus': 0, 'error_message': str(msg)} + except Exception as e: + secure_log_error(e, \'recreateContainerStatus\') + data_ret = secure_error_response(e, \'Failed to recreateContainerStatus\') json_data = json.dumps(data_ret) return HttpResponse(json_data) @@ -1340,8 +1352,9 @@ class ContainerManager(multi.Thread): data_ret = {'getTagsStatus': 1, 'list': tagList, 'next': registryData['next'], 'error_message': None} json_data = json.dumps(data_ret) return HttpResponse(json_data) - except BaseException as msg: - data_ret = {'getTagsStatus': 0, 'error_message': str(msg)} + except Exception as e: + secure_log_error(e, \'getTagsStatus\') + data_ret = secure_error_response(e, \'Failed to getTagsStatus\') json_data = json.dumps(data_ret) return HttpResponse(json_data) @@ -1366,8 +1379,9 @@ class ContainerManager(multi.Thread): json_data = json.dumps(data_ret) return HttpResponse(json_data) - except BaseException as msg: - data_ret = {'removeImageStatus': 0, 'error_message': str(msg)} + except Exception as e: + secure_log_error(e, \'removeImageStatus\') + data_ret = secure_error_response(e, \'Failed to removeImageStatus\') json_data = json.dumps(data_ret) return HttpResponse(json_data) @@ -1419,8 +1433,9 @@ class ContainerManager(multi.Thread): json_data = json.dumps(data_ret) return HttpResponse(json_data) - except BaseException as msg: - data_ret = {'status': 0, 'error_message': str(msg)} + except Exception as e: + secure_log_error(e, 'getContainerAppinfo') + data_ret = secure_error_response(e, 'Failed to get container app info') json_data = json.dumps(data_ret) return HttpResponse(json_data) @@ -1466,8 +1481,9 @@ class ContainerManager(multi.Thread): json_data = json.dumps(data_ret) return HttpResponse(json_data) - except BaseException as msg: - data_ret = {'removeImageStatus': 0, 'error_message': str(msg)} + except Exception as e: + secure_log_error(e, \'removeImageStatus\') + data_ret = secure_error_response(e, \'Failed to removeImageStatus\') json_data = json.dumps(data_ret) return HttpResponse(json_data) @@ -1510,8 +1526,9 @@ class ContainerManager(multi.Thread): json_data = json.dumps(data_ret) return HttpResponse(json_data) - except BaseException as msg: - data_ret = {'removeImageStatus': 0, 'error_message': str(msg)} + except Exception as e: + secure_log_error(e, \'removeImageStatus\') + data_ret = secure_error_response(e, \'Failed to removeImageStatus\') json_data = json.dumps(data_ret) return HttpResponse(json_data) @@ -1536,8 +1553,9 @@ class ContainerManager(multi.Thread): json_data = json.dumps(data_ret) return HttpResponse(json_data) - except BaseException as msg: - data_ret = {'removeImageStatus': 0, 'error_message': str(msg)} + except Exception as e: + secure_log_error(e, \'removeImageStatus\') + data_ret = secure_error_response(e, \'Failed to removeImageStatus\') json_data = json.dumps(data_ret) return HttpResponse(json_data) @@ -1562,8 +1580,9 @@ class ContainerManager(multi.Thread): json_data = json.dumps(data_ret) return HttpResponse(json_data) - except BaseException as msg: - data_ret = {'removeImageStatus': 0, 'error_message': str(msg)} + except Exception as e: + secure_log_error(e, \'removeImageStatus\') + data_ret = secure_error_response(e, \'Failed to removeImageStatus\') json_data = json.dumps(data_ret) return HttpResponse(json_data) diff --git a/loginSystem/views.py b/loginSystem/views.py index 549ab3c73..fd5a9f98a 100644 --- a/loginSystem/views.py +++ b/loginSystem/views.py @@ -44,10 +44,9 @@ def verifyLogin(request): username = data.get('username', '') password = data.get('password', '') - # Debug logging - print(f"Login attempt - Username: {username}, Password length: {len(password) if password else 0}") - print(f"Password contains '$': {'$' in password if password else False}") - print(f"Raw password: {repr(password)}") + # Secure logging (no sensitive data) + from plogical.errorSanitizer import secure_log_error + secure_log_error(Exception(f"Login attempt for user: {username}"), 'verifyLogin') try: language_selection = data.get('languageSelection', 'english') @@ -157,8 +156,10 @@ def verifyLogin(request): response.write(json_data) return response - except BaseException as msg: - data = {'userID': 0, 'loginStatus': 0, 'error_message': str(msg)} + except Exception as e: + from plogical.errorSanitizer import secure_log_error, secure_error_response + secure_log_error(e, 'verifyLogin') + data = secure_error_response(e, 'Login failed') json_data = json.dumps(data) return HttpResponse(json_data) diff --git a/plogical/errorSanitizer.py b/plogical/errorSanitizer.py new file mode 100644 index 000000000..79bb1d5a9 --- /dev/null +++ b/plogical/errorSanitizer.py @@ -0,0 +1,273 @@ +# -*- coding: utf-8 -*- + +""" +CyberPanel Error Sanitization Utility +===================================== + +This module provides secure error handling and sanitization to prevent +information disclosure vulnerabilities while maintaining useful error +reporting for debugging purposes. + +Security Features: +- Sanitizes error messages to prevent information disclosure +- Provides user-friendly error messages +- Maintains detailed logging for administrators +- Prevents sensitive data exposure in API responses +""" + +import re +import logging +import traceback +from typing import Optional, Dict, Any +from django.conf import settings +from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging + +class ErrorSanitizer: + """ + Centralized error sanitization and handling utility + """ + + # Sensitive patterns that should be masked in error messages + SENSITIVE_PATTERNS = [ + # File paths + r'/home/[^/]+/', + r'/usr/local/[^/]+/', + r'/var/[^/]+/', + r'/etc/[^/]+/', + # Database credentials + r'password[=\s]*[^\s]+', + r'passwd[=\s]*[^\s]+', + r'pwd[=\s]*[^\s]+', + # API keys and tokens + r'api[_-]?key[=\s]*[^\s]+', + r'token[=\s]*[^\s]+', + r'secret[=\s]*[^\s]+', + # Connection strings + r'mysql://[^@]+@', + r'postgresql://[^@]+@', + r'mongodb://[^@]+@', + # IP addresses (in some contexts) + r'\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b', + # Email addresses + r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b', + ] + + # Generic error messages for different exception types + GENERIC_ERRORS = { + 'DatabaseError': 'Database operation failed. Please try again.', + 'ConnectionError': 'Unable to connect to the service. Please check your connection.', + 'PermissionError': 'Insufficient permissions to perform this operation.', + 'FileNotFoundError': 'Required file not found. Please contact support.', + 'OSError': 'System operation failed. Please try again.', + 'ValueError': 'Invalid input provided. Please check your data.', + 'KeyError': 'Required information is missing. Please try again.', + 'TypeError': 'Invalid data type provided. Please check your input.', + 'AttributeError': 'System configuration error. Please contact support.', + 'ImportError': 'System module error. Please contact support.', + 'TimeoutError': 'Operation timed out. Please try again.', + 'BaseException': 'An unexpected error occurred. Please try again.', + } + + @staticmethod + def sanitize_error_message(error_message: str, exception_type: str = None) -> str: + """ + Sanitize error message by removing sensitive information + + Args: + error_message: The original error message + exception_type: The type of exception that occurred + + Returns: + Sanitized error message safe for user display + """ + if not error_message: + return "An error occurred. Please try again." + + # Convert to string if not already + error_str = str(error_message) + + # Apply sensitive pattern masking + for pattern in ErrorSanitizer.SENSITIVE_PATTERNS: + error_str = re.sub(pattern, '[REDACTED]', error_str, flags=re.IGNORECASE) + + # Additional sanitization for common sensitive patterns + error_str = re.sub(r'[^\x00-\x7F]+', '[NON-ASCII]', error_str) # Remove non-ASCII chars + error_str = re.sub(r'\s+', ' ', error_str) # Normalize whitespace + + # Limit message length + if len(error_str) > 200: + error_str = error_str[:197] + "..." + + return error_str.strip() + + @staticmethod + def get_user_friendly_message(exception: Exception) -> str: + """ + Get a user-friendly error message based on exception type + + Args: + exception: The exception that occurred + + Returns: + User-friendly error message + """ + exception_type = type(exception).__name__ + + # Check for specific exception types first + if exception_type in ErrorSanitizer.GENERIC_ERRORS: + return ErrorSanitizer.GENERIC_ERRORS[exception_type] + + # Handle common Django exceptions + if 'DoesNotExist' in exception_type: + return "The requested resource was not found." + elif 'ValidationError' in exception_type: + return "Invalid data provided. Please check your input." + elif 'PermissionDenied' in exception_type: + return "You do not have permission to perform this operation." + + # Default generic message + return "An unexpected error occurred. Please try again." + + @staticmethod + def create_secure_response(exception: Exception, + user_message: str = None, + include_details: bool = False) -> Dict[str, Any]: + """ + Create a secure error response dictionary + + Args: + exception: The exception that occurred + user_message: Custom user message (optional) + include_details: Whether to include sanitized details for debugging + + Returns: + Dictionary with secure error information + """ + response = { + 'status': 0, + 'error_message': user_message or ErrorSanitizer.get_user_friendly_message(exception) + } + + # Add sanitized details if requested and in debug mode + if include_details and getattr(settings, 'DEBUG', False): + response['debug_info'] = { + 'exception_type': type(exception).__name__, + 'sanitized_message': ErrorSanitizer.sanitize_error_message(str(exception)) + } + + return response + + @staticmethod + def log_error_securely(exception: Exception, + context: str = None, + user_id: str = None, + request_info: Dict = None): + """ + Log error securely without exposing sensitive information + + Args: + exception: The exception that occurred + context: Context where the error occurred + user_id: ID of the user who encountered the error + request_info: Request information (sanitized) + """ + try: + # Create secure log entry + log_entry = { + 'timestamp': logging.get_current_timestamp(), + 'exception_type': type(exception).__name__, + 'context': context or 'Unknown', + 'user_id': user_id or 'Anonymous', + 'sanitized_message': ErrorSanitizer.sanitize_error_message(str(exception)) + } + + # Add request info if provided + if request_info: + log_entry['request_info'] = { + 'method': request_info.get('method', 'Unknown'), + 'path': request_info.get('path', 'Unknown'), + 'ip': request_info.get('ip', 'Unknown') + } + + # Log the error + logging.writeToFile(f"SECURE_ERROR_LOG: {log_entry}") + + # Also log the full traceback for administrators (in secure location) + if getattr(settings, 'DEBUG', False): + full_traceback = traceback.format_exc() + sanitized_traceback = ErrorSanitizer.sanitize_error_message(full_traceback) + logging.writeToFile(f"FULL_TRACEBACK: {sanitized_traceback}") + + except Exception as log_error: + # Fallback logging if the secure logging fails + logging.writeToFile(f"LOGGING_ERROR: Failed to log error - {str(log_error)}") + + @staticmethod + def handle_exception(exception: Exception, + context: str = None, + user_id: str = None, + request_info: Dict = None, + return_response: bool = True) -> Optional[Dict[str, Any]]: + """ + Comprehensive exception handling with secure logging and response + + Args: + exception: The exception to handle + context: Context where the error occurred + user_id: ID of the user who encountered the error + request_info: Request information + return_response: Whether to return a response dictionary + + Returns: + Secure error response dictionary if return_response is True + """ + # Log the error securely + ErrorSanitizer.log_error_securely(exception, context, user_id, request_info) + + if return_response: + return ErrorSanitizer.create_secure_response(exception) + + return None + + +class SecureExceptionHandler: + """ + Context manager for secure exception handling + """ + + def __init__(self, context: str = None, user_id: str = None, request_info: Dict = None): + self.context = context + self.user_id = user_id + self.request_info = request_info + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + if exc_type is not None: + ErrorSanitizer.handle_exception( + exc_val, + self.context, + self.user_id, + self.request_info, + return_response=False + ) + # Return True to suppress the exception (we've handled it) + return True + return False + + +# Convenience functions for common use cases +def secure_error_response(exception: Exception, user_message: str = None) -> Dict[str, Any]: + """Create a secure error response for API endpoints""" + return ErrorSanitizer.create_secure_response(exception, user_message) + + +def secure_log_error(exception: Exception, context: str = None, user_id: str = None): + """Log an error securely without exposing sensitive information""" + ErrorSanitizer.log_error_securely(exception, context, user_id) + + +def handle_secure_exception(exception: Exception, context: str = None) -> Dict[str, Any]: + """Handle an exception securely and return a safe response""" + return ErrorSanitizer.handle_exception(exception, context, return_response=True) diff --git a/plogical/upgrade.py b/plogical/upgrade.py index 564037279..39a6e1f28 100644 --- a/plogical/upgrade.py +++ b/plogical/upgrade.py @@ -9,6 +9,7 @@ import re sys.path.append('/usr/local/CyberCP') os.environ.setdefault("DJANGO_SETTINGS_MODULE", "CyberCP.settings") +from plogical.errorSanitizer import ErrorSanitizer import shlex import subprocess import shutil @@ -567,8 +568,9 @@ class Upgrade: writeToFile.writelines(varTmp) writeToFile.close() - except BaseException as msg: - Upgrade.stdOut(str(msg) + " [mountTemp]", 0) + except Exception as e: + ErrorSanitizer.log_error_securely(e, 'mountTemp') + Upgrade.stdOut("Failed to mount temporary filesystem [mountTemp]", 0) @staticmethod def dockerUsers(): @@ -738,8 +740,9 @@ $cfg['Servers'][$i]['LogoutURL'] = 'phpmyadminsignin.php?logout'; os.chdir(cwd) - except BaseException as msg: - Upgrade.stdOut(str(msg) + " [download_install_phpmyadmin]", 0) + except Exception as e: + ErrorSanitizer.log_error_securely(e, 'download_install_phpmyadmin') + Upgrade.stdOut("Failed to download and install phpMyAdmin [download_install_phpmyadmin]", 0) @staticmethod def setupComposer(): @@ -1028,8 +1031,9 @@ $cfg['Servers'][$i]['LogoutURL'] = 'phpmyadminsignin.php?logout'; Upgrade.stdOut("SnappyMail installation completed.", 0) - except BaseException as msg: - Upgrade.stdOut(str(msg) + " [downoad_and_install_raindloop]", 0) + except Exception as e: + ErrorSanitizer.log_error_securely(e, 'downoad_and_install_raindloop') + Upgrade.stdOut("Failed to download and install Rainloop [downoad_and_install_raindloop]", 0) return 1 @@ -1049,8 +1053,9 @@ $cfg['Servers'][$i]['LogoutURL'] = 'phpmyadminsignin.php?logout'; pass return (version_number + "." + version_build + ".tar.gz") - except BaseException as msg: - Upgrade.stdOut(str(msg) + ' [downloadLink]') + except Exception as e: + ErrorSanitizer.log_error_securely(e, 'downloadLink') + Upgrade.stdOut("Failed to download required files [downloadLink]") os._exit(0) @staticmethod @@ -1063,8 +1068,9 @@ $cfg['Servers'][$i]['LogoutURL'] = 'phpmyadminsignin.php?logout'; command = "chmod +x /usr/local/CyberCP/cli/cyberPanel.py" Upgrade.executioner(command, 'CLI Permissions', 0) - except OSError as msg: - Upgrade.stdOut(str(msg) + " [setupCLI]") + except OSError as e: + ErrorSanitizer.log_error_securely(e, 'setupCLI') + Upgrade.stdOut("Failed to setup CLI [setupCLI]") return 0 @staticmethod @@ -1136,8 +1142,9 @@ $cfg['Servers'][$i]['LogoutURL'] = 'phpmyadminsignin.php?logout'; cursor = conn.cursor() return conn, cursor - except BaseException as msg: - Upgrade.stdOut(str(msg)) + except Exception as e: + ErrorSanitizer.log_error_securely(e, 'database_connection') + Upgrade.stdOut("Failed to establish database connection") return 0, 0 @staticmethod @@ -1381,8 +1388,8 @@ $cfg['Servers'][$i]['LogoutURL'] = 'phpmyadminsignin.php?logout'; try: cursor.execute("UPDATE loginSystem_acl SET config = '%s' where name = 'admin'" % (Upgrade.AdminACL)) - except BaseException as msg: - print(str(msg)) + except Exception as e: + ErrorSanitizer.log_error_securely(e, 'applyLoginSystemMigrations') try: import sleep except: @@ -2983,8 +2990,9 @@ CREATE TABLE `websiteFunctions_backupsv2` (`id` integer AUTO_INCREMENT NOT NULL return 1, None - except BaseException as msg: - return 0, str(msg) + except Exception as e: + ErrorSanitizer.log_error_securely(e, 'installLSCPD') + return 0, "Failed to install LSCPD" @staticmethod def installLSCPD(branch): @@ -3074,8 +3082,9 @@ CREATE TABLE `websiteFunctions_backupsv2` (`id` integer AUTO_INCREMENT NOT NULL Upgrade.stdOut("LSCPD successfully installed!") - except BaseException as msg: - Upgrade.stdOut(str(msg) + " [installLSCPD]") + except Exception as e: + ErrorSanitizer.log_error_securely(e, 'installLSCPD') + Upgrade.stdOut("Failed to install LSCPD [installLSCPD]") ### disable dkim signing in rspamd in ref to https://github.com/usmannasir/cyberpanel/issues/1176 @staticmethod @@ -3363,8 +3372,9 @@ echo $oConfig->Save() ? 'Done' : 'Error'; Upgrade.stdOut("Permissions updated.") - except BaseException as msg: - Upgrade.stdOut(str(msg) + " [fixPermissions]") + except Exception as e: + ErrorSanitizer.log_error_securely(e, 'fixPermissions') + Upgrade.stdOut("Failed to fix permissions [fixPermissions]") @staticmethod def AutoUpgradeAcme(): @@ -3807,8 +3817,9 @@ echo $oConfig->Save() ? 'Done' : 'Error'; Upgrade.stdOut("Dovecot upgraded.") - except BaseException as msg: - Upgrade.stdOut(str(msg) + " [upgradeDovecot]") + except Exception as e: + ErrorSanitizer.log_error_securely(e, 'upgradeDovecot') + Upgrade.stdOut("Failed to upgrade Dovecot [upgradeDovecot]") @staticmethod def installRestic(): @@ -4270,8 +4281,9 @@ slowlog = /var/log/php{version}-fpm-slow.log Upgrade.stdOut(f"PHP symlink updated to PHP {selected_php} successfully.") - except BaseException as msg: - Upgrade.stdOut('[ERROR] ' + str(msg) + " [setupPHPSymlink]") + except Exception as e: + ErrorSanitizer.log_error_securely(e, 'setupPHPSymlink') + Upgrade.stdOut('[ERROR] Failed to setup PHP symlink [setupPHPSymlink]') return 0 return 1 @@ -5083,8 +5095,9 @@ extprocessor proxyApacheBackendSSL { return 1 - except BaseException as msg: - print("[ERROR] installQuota. " + str(msg)) + except Exception as e: + ErrorSanitizer.log_error_securely(e, 'installQuota') + print("[ERROR] installQuota. Failed to install quota") return 0 @staticmethod diff --git a/userManagment/views.py b/userManagment/views.py index cdcaeac3a..a5d26ee6c 100644 --- a/userManagment/views.py +++ b/userManagment/views.py @@ -12,6 +12,7 @@ from plogical.httpProc import httpProc from plogical.virtualHostUtilities import virtualHostUtilities from CyberCP.secMiddleware import secMiddleware from CyberCP.SecurityLevel import SecurityLevel +from plogical.errorSanitizer import secure_error_response, secure_log_error, handle_secure_exception def loadUserHome(request): @@ -114,8 +115,9 @@ def saveChangesAPIAccess(request): finalResponse = {'status': 1} json_data = json.dumps(finalResponse) return HttpResponse(json_data) - except BaseException as msg: - finalResponse = {'status': 0, 'errorMessage': str(msg), 'error_message': str(msg)} + except Exception as e: + secure_log_error(e, 'saveChangesAPIAccess', request.session.get('userID', 'Unknown')) + finalResponse = secure_error_response(e, 'Failed to update API access settings') json_data = json.dumps(finalResponse) return HttpResponse(json_data) @@ -281,15 +283,17 @@ def submitUserCreation(request): ) else: # Log error but don't fail user creation - logging.CyberCPLogFileWriter.writeToFile(f"Failed to create user directory for {userName} in {home_path}") + from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging + logging.writeToFile(f"Failed to create user directory for {userName} in {home_path}") data_ret = {'status': 1, 'createStatus': 1, 'error_message': "None"} final_json = json.dumps(data_ret) return HttpResponse(final_json) - except BaseException as msg: - data_ret = {'status': 0, 'createStatus': 0, 'error_message': str(msg)} + except Exception as e: + secure_log_error(e, 'submitUserCreation', request.session.get('userID', 'Unknown')) + data_ret = secure_error_response(e, 'Failed to create user account') json_data = json.dumps(data_ret) return HttpResponse(json_data) @@ -362,8 +366,9 @@ def fetchUserDetails(request): json_data = json.dumps(data_ret) return HttpResponse(json_data) - except BaseException as msg: - data_ret = {'fetchStatus': 0, 'error_message': str(msg)} + except Exception as e: + secure_log_error(e, 'fetchUserDetails', request.session.get('userID', 'Unknown')) + data_ret = secure_error_response(e, 'Failed to fetch user details') json_data = json.dumps(data_ret) return HttpResponse(json_data) @@ -450,8 +455,9 @@ def saveModifications(request): json_data = json.dumps(data_ret) return HttpResponse(json_data) - except BaseException as msg: - data_ret = {'status': 0, 'saveStatus': 0, 'error_message': str(msg)} + except Exception as e: + secure_log_error(e, 'saveModifications', request.session.get('userID', 'Unknown')) + data_ret = secure_error_response(e, 'Failed to save user modifications') json_data = json.dumps(data_ret) return HttpResponse(json_data) @@ -534,8 +540,9 @@ def submitUserDeletion(request): json_data = json.dumps(data_ret) return HttpResponse(json_data) - except BaseException as msg: - data_ret = {'status': 0, 'deleteStatus': 0, 'error_message': str(msg)} + except Exception as e: + secure_log_error(e, 'submitUserDeletion', request.session.get('userID', 'Unknown')) + data_ret = secure_error_response(e, 'Failed to delete user account') json_data = json.dumps(data_ret) return HttpResponse(json_data) @@ -576,8 +583,9 @@ def createACLFunc(request): json_data = json.dumps(finalResponse) return HttpResponse(json_data) - except BaseException as msg: - finalResponse = {'status': 0, 'errorMessage': str(msg), 'error_message': str(msg)} + except Exception as e: + secure_log_error(e, 'createACLFunc', request.session.get('userID', 'Unknown')) + finalResponse = secure_error_response(e, 'Failed to create ACL') json_data = json.dumps(finalResponse) return HttpResponse(json_data) @@ -610,8 +618,9 @@ def deleteACLFunc(request): json_data = json.dumps(finalResponse) return HttpResponse(json_data) - except BaseException as msg: - finalResponse = {'status': 0, 'errorMessage': str(msg), 'error_message': str(msg)} + except Exception as e: + secure_log_error(e, 'deleteACLFunc', request.session.get('userID', 'Unknown')) + finalResponse = secure_error_response(e, 'Failed to delete ACL') json_data = json.dumps(finalResponse) return HttpResponse(json_data) @@ -642,8 +651,9 @@ def fetchACLDetails(request): json_data = json.dumps(finalResponse) return HttpResponse(json_data) - except BaseException as msg: - finalResponse = {'status': 0, 'errorMessage': str(msg)} + except Exception as e: + secure_log_error(e, 'fetchACLDetails', request.session.get('userID', 'Unknown')) + finalResponse = secure_error_response(e, 'Failed to fetch ACL details') json_data = json.dumps(finalResponse) return HttpResponse(json_data) @@ -682,8 +692,9 @@ def submitACLModifications(request): json_data = json.dumps(finalResponse) return HttpResponse(json_data) - except BaseException as msg: - finalResponse = {'status': 0, 'errorMessage': str(msg), 'error_message': str(msg)} + except Exception as e: + secure_log_error(e, 'submitACLModifications', request.session.get('userID', 'Unknown')) + finalResponse = secure_error_response(e, 'Failed to submit ACL modifications') json_data = json.dumps(finalResponse) return HttpResponse(json_data) @@ -742,8 +753,9 @@ def changeACLFunc(request): json_data = json.dumps(finalResponse) return HttpResponse(json_data) - except BaseException as msg: - finalResponse = {'status': 0, 'errorMessage': str(msg), 'error_message': str(msg)} + except Exception as e: + secure_log_error(e, 'changeACLFunc', request.session.get('userID', 'Unknown')) + finalResponse = secure_error_response(e, 'Failed to change user ACL') json_data = json.dumps(finalResponse) return HttpResponse(json_data) @@ -819,8 +831,9 @@ def saveResellerChanges(request): finalResponse = {'status': 1} json_data = json.dumps(finalResponse) return HttpResponse(json_data) - except BaseException as msg: - finalResponse = {'status': 0, 'errorMessage': str(msg), 'error_message': str(msg)} + except Exception as e: + secure_log_error(e, 'saveResellerChanges', request.session.get('userID', 'Unknown')) + finalResponse = secure_error_response(e, 'Failed to save reseller changes') json_data = json.dumps(finalResponse) return HttpResponse(json_data) @@ -967,8 +980,9 @@ def controlUserState(request): json_data = json.dumps(data_ret) return HttpResponse(json_data) - except BaseException as msg: - data_ret = {'status': 0, 'saveStatus': 0, 'error_message': str(msg)} + except Exception as e: + secure_log_error(e, 'controlUserState', request.session.get('userID', 'Unknown')) + data_ret = secure_error_response(e, 'Failed to control user state') json_data = json.dumps(data_ret) return HttpResponse(json_data) @@ -1024,7 +1038,8 @@ def disable2FA(request): user.secretKey = 'None' user.save() - logging.CyberCPLogFileWriter.writeToFile(f'2FA disabled for user: {accountUsername} by admin: {val}') + from plogical.CyberCPLogFileWriter import CyberCPLogFileWriter as logging + logging.writeToFile(f'2FA disabled for user: {accountUsername} by admin: {val}') data_ret = { 'status': 1, @@ -1044,7 +1059,7 @@ def disable2FA(request): return HttpResponse(json_data) except Exception as e: - logging.CyberCPLogFileWriter.writeToFile(f'Error in disable2FA: {str(e)}') - data_ret = {'status': 0, 'error_message': str(e)} + secure_log_error(e, 'disable2FA', request.session.get('userID', 'Unknown')) + data_ret = secure_error_response(e, 'Failed to disable 2FA') json_data = json.dumps(data_ret) return HttpResponse(json_data)