diff --git a/CyberCP/secMiddleware.py b/CyberCP/secMiddleware.py index 13fd2f366..2f43bd9d3 100644 --- a/CyberCP/secMiddleware.py +++ b/CyberCP/secMiddleware.py @@ -151,14 +151,26 @@ class secMiddleware: final_json = json.dumps(final_dic) return HttpResponse(final_json) - if FinalURL.find( - 'api/remoteTransfer') > -1 or FinalURL.find( - 'api/verifyConn') > -1 or FinalURL.find( - 'webhook') > -1 or FinalURL.find( - 'saveSpamAssassinConfigurations') > -1 or FinalURL.find( - 'docker') > -1 or FinalURL.find( - 'cloudAPI') > -1 or FinalURL.find( - 'verifyLogin') > -1 or FinalURL.find('submitUserCreation') > -1: + # Allow JSON structure characters for API endpoints but keep security checks for dangerous characters + isAPIEndpoint = (FinalURL.find('api/remoteTransfer') > -1 or FinalURL.find('api/verifyConn') > -1 or + FinalURL.find('webhook') > -1 or FinalURL.find('saveSpamAssassinConfigurations') > -1 or + FinalURL.find('docker') > -1 or FinalURL.find('cloudAPI') > -1 or + FinalURL.find('verifyLogin') > -1 or FinalURL.find('submitUserCreation') > -1 or + FinalURL.find('/api/') > -1) + + if isAPIEndpoint: + # For API endpoints, still check for the most dangerous command injection characters + if (value.find('- -') > -1 or value.find('\n') > -1 or value.find(';') > -1 or + value.find('&&') > -1 or value.find('||') > -1 or value.find('|') > -1 or + value.find('...') > -1 or value.find("`") > -1 or value.find("$") > -1 or + value.find('../') > -1 or value.find('../../') > -1): + logging.writeToFile(request.body) + final_dic = { + 'error_message': "API request contains potentially dangerous characters: `;`, `&&`, `||`, `|`, `` ` ``, `$`, `../` are not allowed.", + "errorMessage": "API request contains potentially dangerous characters." + } + final_json = json.dumps(final_dic) + return HttpResponse(final_json) continue if key == 'MainDashboardCSS' or key == 'ownerPassword' or key == 'scriptUrl' or key == 'CLAMAV_VIRUS' or key == "Rspamdserver" or key == 'smtpd_milters' \ or key == 'non_smtpd_milters' or key == 'key' or key == 'cert' or key == 'recordContentAAAA' or key == 'backupDestinations'\ diff --git a/api/views.py b/api/views.py index d1aec1344..d0a243868 100644 --- a/api/views.py +++ b/api/views.py @@ -23,54 +23,138 @@ from userManagment.views import submitUserCreation as suc from userManagment.views import submitUserDeletion as duc # Create your views here. +def validate_api_input(input_value, field_name="field"): + """ + Validate API input for security threats while allowing legitimate data + Returns tuple: (is_valid, error_message) + """ + if not isinstance(input_value, str): + return True, None + + # Check for command injection patterns + dangerous_patterns = [ + ';', '&&', '||', '|', '`', '$', + '../', '../../', '\n', '\r', + '', 'javascript:', + 'eval(', 'exec(', 'system(', 'shell_exec(' + ] + + for pattern in dangerous_patterns: + if pattern in input_value: + return False, f"{field_name} contains invalid characters or patterns." + + return True, None + @csrf_exempt def verifyConn(request): try: if request.method == 'POST': + try: + data = json.loads(request.body) + adminUser = data['adminUser'] + adminPass = data['adminPass'] + + # Additional security: validate input for dangerous characters + is_valid, error_msg = validate_api_input(adminUser, "adminUser") + if not is_valid: + data_ret = {"verifyConn": 0, 'error_message': error_msg} + json_data = json.dumps(data_ret) + return HttpResponse(json_data, status=400) + + except (json.JSONDecodeError, KeyError) as e: + data_ret = {"verifyConn": 0, 'error_message': "Invalid JSON or missing adminUser/adminPass fields."} + json_data = json.dumps(data_ret) + return HttpResponse(json_data, status=400) - data = json.loads(request.body) - adminUser = data['adminUser'] - adminPass = data['adminPass'] - - admin = Administrator.objects.get(userName=adminUser) + try: + admin = Administrator.objects.get(userName=adminUser) + except Administrator.DoesNotExist: + data_ret = {"verifyConn": 0, 'error_message': "Administrator not found."} + json_data = json.dumps(data_ret) + return HttpResponse(json_data, status=404) if admin.api == 0: data_ret = {"verifyConn": 0, 'error_message': "API Access Disabled."} json_data = json.dumps(data_ret) - return HttpResponse(json_data) + return HttpResponse(json_data, status=403) if hashPassword.check_password(admin.password, adminPass): data_ret = {"verifyConn": 1} json_data = json.dumps(data_ret) return HttpResponse(json_data) else: - data_ret = {"verifyConn": 0} + data_ret = {"verifyConn": 0, 'error_message': "Invalid password."} json_data = json.dumps(data_ret) - return HttpResponse(json_data) - except BaseException as msg: - data_ret = {'verifyConn': 0, 'error_message': str(msg)} + return HttpResponse(json_data, status=401) + else: + data_ret = {"verifyConn": 0, 'error_message': "Only POST method allowed."} + json_data = json.dumps(data_ret) + return HttpResponse(json_data, status=405) + except Exception as msg: + data_ret = {'verifyConn': 0, 'error_message': f"Internal server error: {str(msg)}"} json_data = json.dumps(data_ret) - return HttpResponse(json_data) + return HttpResponse(json_data, status=500) @csrf_exempt def createWebsite(request): - data = json.loads(request.body) - adminUser = data['adminUser'] - admin = Administrator.objects.get(userName=adminUser) + try: + if request.method != 'POST': + data_ret = {"existsStatus": 0, 'createWebSiteStatus': 0, + 'error_message': "Only POST method allowed."} + json_data = json.dumps(data_ret) + return HttpResponse(json_data, status=405) - if os.path.exists(ProcessUtilities.debugPath): - logging.writeToFile(f'Create website payload in API {str(data)}') + try: + data = json.loads(request.body) + adminUser = data['adminUser'] + + # Additional security: validate critical fields for dangerous characters + is_valid, error_msg = validate_api_input(adminUser, "adminUser") + if not is_valid: + data_ret = {"existsStatus": 0, 'createWebSiteStatus': 0, 'error_message': error_msg} + json_data = json.dumps(data_ret) + return HttpResponse(json_data, status=400) + + # Validate domain name if provided + if 'domainName' in data: + is_valid, error_msg = validate_api_input(data['domainName'], "domainName") + if not is_valid: + data_ret = {"existsStatus": 0, 'createWebSiteStatus': 0, 'error_message': error_msg} + json_data = json.dumps(data_ret) + return HttpResponse(json_data, status=400) + + except (json.JSONDecodeError, KeyError): + data_ret = {"existsStatus": 0, 'createWebSiteStatus': 0, + 'error_message': "Invalid JSON or missing adminUser field."} + json_data = json.dumps(data_ret) + return HttpResponse(json_data, status=400) - if admin.api == 0: + try: + admin = Administrator.objects.get(userName=adminUser) + except Administrator.DoesNotExist: + data_ret = {"existsStatus": 0, 'createWebSiteStatus': 0, + 'error_message': "Administrator not found."} + json_data = json.dumps(data_ret) + return HttpResponse(json_data, status=404) + + if os.path.exists(ProcessUtilities.debugPath): + logging.writeToFile(f'Create website payload in API {str(data)}') + + if admin.api == 0: + data_ret = {"existsStatus": 0, 'createWebSiteStatus': 0, + 'error_message': "API Access Disabled."} + json_data = json.dumps(data_ret) + return HttpResponse(json_data, status=403) + + wm = WebsiteManager() + return wm.createWebsiteAPI(data) + except Exception as msg: data_ret = {"existsStatus": 0, 'createWebSiteStatus': 0, - 'error_message': "API Access Disabled."} + 'error_message': f"Internal server error: {str(msg)}"} json_data = json.dumps(data_ret) - return HttpResponse(json_data) - - wm = WebsiteManager() - return wm.createWebsiteAPI(json.loads(request.body)) + return HttpResponse(json_data, status=500) @csrf_exempt diff --git a/baseTemplate/templates/baseTemplate/index.html b/baseTemplate/templates/baseTemplate/index.html index 1f51db851..ce3ca6876 100644 --- a/baseTemplate/templates/baseTemplate/index.html +++ b/baseTemplate/templates/baseTemplate/index.html @@ -556,6 +556,205 @@ } } + /* AI Scanner Security Banner */ + .ai-scanner-banner { + position: fixed; + top: 80px; + left: 260px; + right: 0; + background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 50%, #a855f7 100%); + border-bottom: 2px solid #4f46e5; + padding: 16px 30px; + z-index: 998; + box-shadow: 0 4px 20px rgba(99, 102, 241, 0.25); + animation: slideDown 0.4s ease-out; + display: none; + } + + .ai-scanner-banner.show { + display: block; + } + + .ai-scanner-content { + display: flex; + align-items: center; + gap: 1.5rem; + max-width: 1400px; + margin: 0 auto; + } + + .ai-scanner-icon { + background: rgba(255, 255, 255, 0.2); + border-radius: 12px; + padding: 12px; + backdrop-filter: blur(10px); + border: 1px solid rgba(255, 255, 255, 0.3); + } + + .ai-scanner-icon i { + color: white; + font-size: 1.5rem; + display: block; + } + + .ai-scanner-text { + flex: 1; + display: flex; + flex-direction: column; + gap: 4px; + } + + .ai-scanner-main-text { + color: white; + font-size: 1rem; + font-weight: 600; + line-height: 1.4; + } + + .ai-scanner-sub-text { + color: rgba(255, 255, 255, 0.8); + font-size: 0.875rem; + font-weight: 400; + } + + .ai-scanner-actions { + display: flex; + gap: 12px; + } + + .ai-scanner-btn { + background: white; + color: #4f46e5; + padding: 12px 24px; + border-radius: 8px; + text-decoration: none; + font-weight: 600; + font-size: 0.875rem; + display: flex; + align-items: center; + gap: 8px; + transition: all 0.3s ease; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + position: relative; + overflow: hidden; + } + + .ai-scanner-btn:hover { + background: #f8fafc; + color: #4338ca; + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + text-decoration: none; + } + + .ai-scanner-btn::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient(45deg, rgba(255,255,255,0.1), rgba(255,255,255,0.3), rgba(255,255,255,0.1)); + transform: translateX(-100%); + transition: transform 0.6s ease; + border-radius: 8px; + } + + .ai-scanner-btn:hover::before { + transform: translateX(100%); + } + + .ai-scanner-btn i { + font-size: 0.875rem; + } + + .ai-scanner-close { + background: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.2); + color: white; + font-size: 1rem; + cursor: pointer; + padding: 8px; + border-radius: 6px; + transition: all 0.2s ease; + backdrop-filter: blur(10px); + } + + .ai-scanner-close:hover { + background: rgba(255, 255, 255, 0.2); + transform: scale(1.05); + } + + /* Adjust main content when AI scanner banner is shown */ + .ai-scanner-shown #main-content { + padding-top: 180px; + } + + /* Both banners shown */ + .notification-shown.ai-scanner-shown #main-content { + padding-top: 220px; + } + + .notification-shown .ai-scanner-banner { + top: 130px; + } + + /* Mobile responsive styles for AI Scanner banner */ + @media (max-width: 768px) { + .ai-scanner-banner { + left: 0; + padding: 12px 20px; + } + + .ai-scanner-content { + gap: 1rem; + flex-wrap: wrap; + } + + .ai-scanner-text { + min-width: 200px; + } + + .ai-scanner-main-text { + font-size: 0.9rem; + } + + .ai-scanner-sub-text { + font-size: 0.8rem; + } + + .ai-scanner-btn { + padding: 10px 20px; + font-size: 0.8rem; + } + + .ai-scanner-icon { + padding: 10px; + } + + .ai-scanner-icon i { + font-size: 1.25rem; + } + } + + @media (max-width: 480px) { + .ai-scanner-content { + flex-direction: column; + align-items: stretch; + gap: 12px; + } + + .ai-scanner-actions { + justify-content: center; + } + + .ai-scanner-close { + position: absolute; + top: 8px; + right: 8px; + } + } + /* Scrollbar */ ::-webkit-scrollbar { width: 8px; @@ -1429,6 +1628,28 @@ + +
+
+
+ +
+
+ 🛡️ Secure your websites with AI-powered malware detection + Advanced threat detection • Real-time scanning • Zero false positives +
+ + +
+
+
{% block content %}{% endblock %} @@ -1542,9 +1763,40 @@ sessionStorage.setItem('backupNotificationDismissed', 'true'); } - // Check backup status when page loads + // AI Scanner Notification Functions + function checkAIScannerStatus() { + // Check if user has dismissed the notification in this session + if (sessionStorage.getItem('aiScannerNotificationDismissed') === 'true') { + return; + } + + // Check if we're not already on the AI Scanner page + if (!window.location.href.includes('aiscanner')) { + showAIScannerNotification(); + } + } + + function showAIScannerNotification() { + const banner = document.getElementById('ai-scanner-notification'); + const body = document.body; + banner.classList.add('show'); + body.classList.add('ai-scanner-shown'); + } + + function dismissAIScannerNotification() { + const banner = document.getElementById('ai-scanner-notification'); + const body = document.body; + banner.classList.remove('show'); + body.classList.remove('ai-scanner-shown'); + // Remember dismissal for this session + sessionStorage.setItem('aiScannerNotificationDismissed', 'true'); + } + + // Check both notification statuses when page loads document.addEventListener('DOMContentLoaded', function() { checkBackupStatus(); + // Show AI Scanner notification with a slight delay for better UX + setTimeout(checkAIScannerStatus, 1000); });