From 4392676b2708de16bb6aca3adbcacb33e12c4193 Mon Sep 17 00:00:00 2001 From: master3395 Date: Wed, 4 Feb 2026 18:30:03 +0100 Subject: [PATCH] Fix Ban IP from Recent SSH Logs and Firewall Banned IPs - plogical/firewallUtilities: fix inverted success/failure (result==1 = success); write blocked_ips.log under CyberCP/data for cyberpanel write access - plogical/processUtilities: when root, use normalExecutioner return value so executioner reflects actual command success/failure - firewall/firewallManager: addBannedIP uses FirewallUtilities.blockIP; ACL and all errors return JSON with error_message/error; rollback store if block fails - baseTemplate/views: blockIPAddress uses FirewallUtilities.blockIP instead of subprocess - baseTemplate/homePage: inline Ban IP calls /firewall/addBannedIP with ip/reason/duration; show server error in notifications - baseTemplate/system-status.js: handle string response and show server error_message in success and error callbacks --- .../baseTemplate/custom-js/system-status.js | 47 +++---- .../templates/baseTemplate/homePage.html | 48 +++---- baseTemplate/views.py | 53 ++------ firewall/firewallManager.py | 117 +++++------------- plogical/firewallUtilities.py | 31 +++-- plogical/processUtilities.py | 4 +- 6 files changed, 89 insertions(+), 211 deletions(-) diff --git a/baseTemplate/static/baseTemplate/custom-js/system-status.js b/baseTemplate/static/baseTemplate/custom-js/system-status.js index fab9a6274..76353b1e4 100644 --- a/baseTemplate/static/baseTemplate/custom-js/system-status.js +++ b/baseTemplate/static/baseTemplate/custom-js/system-status.js @@ -1130,19 +1130,12 @@ app.controller('dashboardStatsController', function ($scope, $http, $timeout) { console.log('Parsed responseData from string:', responseData); } catch (e) { console.error('Failed to parse response as JSON:', e); - console.error('Raw response string:', responseData); - // Try to extract error from string - if (responseData.includes('error')) { - if (typeof PNotify !== 'undefined') { - new PNotify({ - title: 'Error', - text: 'Failed to block IP address: ' + responseData, - type: 'error', - delay: 5000 - }); - } - return; + var errorMsg = responseData && responseData.length ? responseData : 'Failed to block IP address'; + if (typeof PNotify !== 'undefined') { + new PNotify({ title: 'Error', text: errorMsg, type: 'error', delay: 5000 }); } + $scope.blockingIP = null; + return; } } @@ -1217,28 +1210,20 @@ app.controller('dashboardStatsController', function ($scope, $http, $timeout) { $scope.lastErrorTime = Date.now(); var errorMessage = 'Failed to block IP address'; - if (err.data) { - var errData = err.data; - if (typeof errData === 'string') { - try { - errData = JSON.parse(errData); - } catch (e) { - errorMessage = errData || errorMessage; + var errData = err.data; + if (typeof errData === 'string') { + try { + errData = JSON.parse(errData); + } catch (e) { + if (errData && errData.length) { + errorMessage = errData.length > 200 ? errData.substring(0, 200) + '...' : errData; } } - if (errData && typeof errData === 'object') { - if (errData.error_message) { - errorMessage = errData.error_message; - } else if (errData.error) { - errorMessage = errData.error; - } else if (errData.message) { - errorMessage = errData.message; - } - } - } else if (err.statusText) { - errorMessage = err.statusText; + } + if (errData && typeof errData === 'object') { + errorMessage = errData.error_message || errData.error || errData.message || errorMessage; } else if (err.status) { - errorMessage = `HTTP ${err.status}: ${err.statusText || 'Unknown error'}`; + errorMessage = 'HTTP ' + err.status + ': ' + (errorMessage); } console.error('Final error message:', errorMessage); diff --git a/baseTemplate/templates/baseTemplate/homePage.html b/baseTemplate/templates/baseTemplate/homePage.html index 2373f10ae..3c078bf0f 100644 --- a/baseTemplate/templates/baseTemplate/homePage.html +++ b/baseTemplate/templates/baseTemplate/homePage.html @@ -1380,56 +1380,38 @@ } $.ajax({ - url: '/base/blockIPAddress', + url: '/firewall/addBannedIP', type: 'POST', contentType: 'application/json', headers: { 'X-CSRFToken': getCookie('csrftoken') }, data: JSON.stringify({ - 'ip_address': ipAddress, - 'reason': 'Security alert detected from dashboard' + 'ip': ipAddress, + 'reason': 'Brute force attack detected from SSH Security Analysis', + 'duration': 'permanent' }), success: function(data) { // Handle both success and error responses - if (data.status === 1) { - showNotification('success', data.message || 'IP address blocked successfully'); + if (data && data.status === 1) { + showNotification('success', data.message || 'IP address blocked successfully. Manage in Firewall > Banned IPs.'); // Refresh the page to update the blocked IPs list - setTimeout(() => { - location.reload(); - }, 1000); + setTimeout(function() { location.reload(); }, 1000); } else { - // Handle error response - check for both 'error' and 'error_message' fields - var errorMsg = data.error || data.error_message || data.message || 'Failed to block IP address'; + var errorMsg = (data && (data.error_message || data.error || data.message)) || 'Failed to block IP address'; showNotification('error', errorMsg); } }, error: function(xhr, status, error) { - // Handle network errors and parse JSON errors - console.error('Ban IP error:', xhr, status, error); + console.error('Ban IP error:', xhr.status, xhr.responseText); var errorMsg = 'Failed to block IP address. Please try again.'; - - // Log full response for debugging - console.log('Response status:', xhr.status); - console.log('Response text:', xhr.responseText); - - if (xhr.responseJSON) { - errorMsg = xhr.responseJSON.error || xhr.responseJSON.error_message || xhr.responseJSON.message || errorMsg; - console.log('Parsed error from JSON:', errorMsg); - } else if (xhr.responseText) { - try { - var errorData = JSON.parse(xhr.responseText); - errorMsg = errorData.error || errorData.error_message || errorData.message || errorMsg; - console.log('Parsed error from text:', errorMsg); - } catch(e) { - console.error('Failed to parse error response:', e); - // If parsing fails, try to extract error from response text - if (xhr.responseText.includes('error')) { - errorMsg = xhr.responseText.substring(0, 200); - } - } + var data = xhr.responseJSON; + if (!data && xhr.responseText) { + try { data = JSON.parse(xhr.responseText); } catch(e) {} + } + if (data && (data.error_message || data.error || data.message)) { + errorMsg = data.error_message || data.error || data.message; } - showNotification('error', errorMsg); }, complete: function() { diff --git a/baseTemplate/views.py b/baseTemplate/views.py index 1c1c637c2..77ccc5509 100644 --- a/baseTemplate/views.py +++ b/baseTemplate/views.py @@ -16,6 +16,7 @@ from plogical.acl import ACLManager from manageServices.models import PDNSStatus from django.views.decorators.csrf import ensure_csrf_cookie, csrf_exempt from plogical.processUtilities import ProcessUtilities +from plogical.firewallUtilities import FirewallUtilities from plogical.httpProc import httpProc from websiteFunctions.models import Websites, WPSites from databases.models import Databases @@ -1318,54 +1319,14 @@ def blockIPAddress(request): 'error': 'Invalid IP address' }), content_type='application/json', status=400) - # Use firewalld (CSF has been discontinued) + # Use FirewallUtilities so firewall-cmd runs with proper privileges (root/lscpd) firewall_cmd = 'firewalld' + reason = data.get('reason', 'Security alert detected from dashboard') try: - # Verify firewalld is active using subprocess for better security - import subprocess - firewalld_check = subprocess.run(['systemctl', 'is-active', 'firewalld'], - capture_output=True, text=True, timeout=10) - if not (firewalld_check.returncode == 0 and 'active' in firewalld_check.stdout): - return HttpResponse(json.dumps({ - 'status': 0, - 'error': 'Firewalld is not active. Please enable firewalld service.' - }), content_type='application/json', status=500) - except subprocess.TimeoutExpired: - return HttpResponse(json.dumps({ - 'status': 0, - 'error': 'Timeout checking firewalld status' - }), content_type='application/json', status=500) + success, msg = FirewallUtilities.blockIP(ip_address, reason) except Exception as e: - return HttpResponse(json.dumps({ - 'status': 0, - 'error': f'Cannot check firewalld status: {str(e)}' - }), content_type='application/json', status=500) - - # Block the IP address using firewalld with subprocess for better security - success = False - error_message = '' - - try: - # Use subprocess with explicit argument lists to prevent injection - rich_rule = f'rule family=ipv4 source address={ip_address} drop' - add_rule_cmd = ['firewall-cmd', '--permanent', '--add-rich-rule', rich_rule] - - # Execute the add rule command - result = subprocess.run(add_rule_cmd, capture_output=True, text=True, timeout=30) - if result.returncode == 0: - # Reload firewall rules - reload_cmd = ['firewall-cmd', '--reload'] - reload_result = subprocess.run(reload_cmd, capture_output=True, text=True, timeout=30) - if reload_result.returncode == 0: - success = True - else: - error_message = f'Failed to reload firewall rules: {reload_result.stderr}' - else: - error_message = f'Failed to add firewall rule: {result.stderr}' - except subprocess.TimeoutExpired: - error_message = 'Firewall command timed out' - except Exception as e: - error_message = f'Firewall command failed: {str(e)}' + success = False + msg = str(e) if success: # Add to banned IPs JSON file for consistency with firewall page @@ -1430,7 +1391,7 @@ def blockIPAddress(request): else: return HttpResponse(json.dumps({ 'status': 0, - 'error': error_message or 'Failed to block IP address' + 'error': msg or 'Failed to block IP address' }), content_type='application/json', status=500) except json.JSONDecodeError as e: diff --git a/firewall/firewallManager.py b/firewall/firewallManager.py index 097e5c0f3..0a50250e4 100644 --- a/firewall/firewallManager.py +++ b/firewall/firewallManager.py @@ -1935,14 +1935,15 @@ class FirewallManager: try: admin = Administrator.objects.get(pk=userID) if admin.acl.adminStatus != 1: - return ACLManager.loadError() + final_dic = {'status': 0, 'error_message': 'You are not authorized to access this resource.', 'error': 'You are not authorized to access this resource.'} + return HttpResponse(json.dumps(final_dic), content_type='application/json', status=403) ip = data.get('ip', '').strip() reason = data.get('reason', '').strip() duration = data.get('duration', '24h') if not ip or not reason: - final_dic = {'status': 0, 'error_message': 'IP address and reason are required'} + final_dic = {'status': 0, 'error_message': 'IP address and reason are required', 'error': 'IP address and reason are required'} return HttpResponse(json.dumps(final_dic), content_type='application/json') # Validate IP address format @@ -1950,7 +1951,7 @@ class FirewallManager: try: ipaddress.ip_address(ip.split('/')[0]) # Handle CIDR notation except ValueError: - final_dic = {'status': 0, 'error_message': 'Invalid IP address format'} + final_dic = {'status': 0, 'error_message': 'Invalid IP address format', 'error': 'Invalid IP address format'} return HttpResponse(json.dumps(final_dic), content_type='application/json') # Calculate expiration time @@ -1973,7 +1974,8 @@ class FirewallManager: # Check if IP is already banned for banned_ip in banned_ips: if banned_ip.get('ip') == ip and banned_ip.get('active', True): - final_dic = {'status': 0, 'error_message': 'IP address %s is already banned' % ip} + msg = 'IP address %s is already banned' % ip + final_dic = {'status': 0, 'error_message': msg, 'error': msg} return HttpResponse(json.dumps(final_dic), content_type='application/json') # Add new banned IP @@ -1991,54 +1993,38 @@ class FirewallManager: # Save to file self._save_banned_ips_store(banned_ips) - # Apply firewall rule to block the IP using firewalld + # Apply firewall rule using FirewallUtilities (runs with proper privileges via ProcessUtilities/lscpd) try: - import subprocess - # Verify firewalld is active - firewalld_check = subprocess.run(['systemctl', 'is-active', 'firewalld'], - capture_output=True, text=True, timeout=10) - if not (firewalld_check.returncode == 0 and 'active' in firewalld_check.stdout): - final_dic = {'status': 0, 'error_message': 'Firewalld is not active. Please enable firewalld service.'} + block_ok, block_msg = FirewallUtilities.blockIP(ip, reason) + if not block_ok: + # Rollback: remove the IP we just added from the store + banned_ips_rollback = [b for b in banned_ips if b.get('ip') != ip or not b.get('active', True)] + if len(banned_ips_rollback) < len(banned_ips): + self._save_banned_ips_store(banned_ips_rollback) + logging.CyberCPLogFileWriter.writeToFile('Failed to add firewalld rule for %s: %s' % (ip, block_msg)) + err_msg = block_msg or 'Failed to add firewall rule' + final_dic = {'status': 0, 'error_message': err_msg, 'error': err_msg} return HttpResponse(json.dumps(final_dic), content_type='application/json') - - # Add firewalld rich rule to block the IP - rich_rule = f'rule family=ipv4 source address={ip} drop' - add_rule_cmd = ['firewall-cmd', '--permanent', '--add-rich-rule', rich_rule] - - # Execute the add rule command - result = subprocess.run(add_rule_cmd, capture_output=True, text=True, timeout=30) - if result.returncode == 0: - # Reload firewall rules - reload_cmd = ['firewall-cmd', '--reload'] - reload_result = subprocess.run(reload_cmd, capture_output=True, text=True, timeout=30) - if reload_result.returncode == 0: - logging.CyberCPLogFileWriter.writeToFile(f'Banned IP {ip} with reason: {reason}') - else: - logging.CyberCPLogFileWriter.writeToFile('Failed to reload firewalld for %s: %s' % (ip, reload_result.stderr)) - final_dic = {'status': 0, 'error_message': 'Failed to reload firewall rules: %s' % reload_result.stderr} - return HttpResponse(json.dumps(final_dic), content_type='application/json') - else: - # Check if rule already exists (this is not an error) - if result.stderr and ('ALREADY_ENABLED' in result.stderr or 'already exists' in result.stderr.lower()): - logging.CyberCPLogFileWriter.writeToFile('IP %s already blocked in firewalld' % ip) - else: - logging.CyberCPLogFileWriter.writeToFile('Failed to add firewalld rule for %s: %s' % (ip, result.stderr or '')) - final_dic = {'status': 0, 'error_message': 'Failed to add firewall rule: %s' % (result.stderr or 'Unknown error')} - return HttpResponse(json.dumps(final_dic), content_type='application/json') - except subprocess.TimeoutExpired: - logging.CyberCPLogFileWriter.writeToFile('Timeout adding firewalld rule for %s' % ip) - final_dic = {'status': 0, 'error_message': 'Firewall command timed out'} - return HttpResponse(json.dumps(final_dic), content_type='application/json') + logging.CyberCPLogFileWriter.writeToFile(f'Banned IP {ip} with reason: {reason}') except Exception as e: + # Rollback store on any exception + try: + banned_ips_rollback = [b for b in banned_ips if b.get('ip') != ip or not b.get('active', True)] + if len(banned_ips_rollback) < len(banned_ips): + self._save_banned_ips_store(banned_ips_rollback) + except Exception: + pass logging.CyberCPLogFileWriter.writeToFile('Failed to add firewalld rule for %s: %s' % (ip, str(e))) - final_dic = {'status': 0, 'error_message': 'Firewall command failed: %s' % str(e)} + err_msg = 'Firewall command failed: %s' % str(e) + final_dic = {'status': 0, 'error_message': err_msg, 'error': err_msg} return HttpResponse(json.dumps(final_dic), content_type='application/json') final_dic = {'status': 1, 'message': 'IP address %s has been banned successfully' % ip} return HttpResponse(json.dumps(final_dic), content_type='application/json') except BaseException as msg: - final_dic = {'status': 0, 'error_message': str(msg)} + err_msg = str(msg) + final_dic = {'status': 0, 'error_message': err_msg, 'error': err_msg} return HttpResponse(json.dumps(final_dic), content_type='application/json') def removeBannedIP(self, userID=None, data=None): @@ -2085,16 +2071,9 @@ class FirewallManager: ip_to_unban = banned_ip.ip_address banned_ip.active = False banned_ip.save() - # Remove firewalld rule + # Remove firewalld rule using FirewallUtilities (runs with proper privileges) try: - import subprocess - firewalld_check = subprocess.run(['systemctl', 'is-active', 'firewalld'], - capture_output=True, text=True, timeout=10) - if firewalld_check.returncode == 0 and 'active' in firewalld_check.stdout: - rich_rule = f'rule family=ipv4 source address={ip_to_unban} drop' - subprocess.run(['firewall-cmd', '--permanent', '--remove-rich-rule', rich_rule], - capture_output=True, text=True, timeout=30) - subprocess.run(['firewall-cmd', '--reload'], capture_output=True, text=True, timeout=30) + FirewallUtilities.unblockIP(ip_to_unban) except Exception as e: logging.CyberCPLogFileWriter.writeToFile(f'Warning removing firewalld rule: {str(e)}') final_dic = {'status': 1, 'message': f'IP address {ip_to_unban} has been unbanned successfully'} @@ -2124,40 +2103,12 @@ class FirewallManager: # Save updated banned IPs self._save_banned_ips_store(banned_ips) - # Remove firewalld rule to unblock the IP + # Remove firewalld rule using FirewallUtilities (runs with proper privileges) try: - import subprocess - # Verify firewalld is active - firewalld_check = subprocess.run(['systemctl', 'is-active', 'firewalld'], - capture_output=True, text=True, timeout=10) - if not (firewalld_check.returncode == 0 and 'active' in firewalld_check.stdout): - # Firewalld not active, but still mark as unbanned in JSON - logging.CyberCPLogFileWriter.writeToFile(f'Warning: Firewalld not active when unbanned IP {ip_to_unban}') - else: - # Remove firewalld rich rule - rich_rule = f'rule family=ipv4 source address={ip_to_unban} drop' - remove_rule_cmd = ['firewall-cmd', '--permanent', '--remove-rich-rule', rich_rule] - - # Execute the remove rule command - result = subprocess.run(remove_rule_cmd, capture_output=True, text=True, timeout=30) - if result.returncode == 0: - # Reload firewall rules - reload_cmd = ['firewall-cmd', '--reload'] - reload_result = subprocess.run(reload_cmd, capture_output=True, text=True, timeout=30) - if reload_result.returncode == 0: - logging.CyberCPLogFileWriter.writeToFile(f'Unbanned IP {ip_to_unban}') - else: - logging.CyberCPLogFileWriter.writeToFile(f'Warning: Failed to reload firewalld after unbanning {ip_to_unban}: {reload_result.stderr}') - else: - # Rule might not exist, which is okay - if 'NOT_ENABLED' in result.stderr or 'not found' in result.stderr.lower(): - logging.CyberCPLogFileWriter.writeToFile(f'IP {ip_to_unban} rule not found in firewalld (may have been removed already)') - else: - logging.CyberCPLogFileWriter.writeToFile(f'Warning: Failed to remove firewalld rule for {ip_to_unban}: {result.stderr}') - except subprocess.TimeoutExpired: - logging.CyberCPLogFileWriter.writeToFile(f'Timeout removing firewalld rule for {ip_to_unban}') + FirewallUtilities.unblockIP(ip_to_unban) + logging.CyberCPLogFileWriter.writeToFile(f'Unbanned IP {ip_to_unban}') except Exception as e: - logging.CyberCPLogFileWriter.writeToFile(f'Failed to remove firewalld rule for {ip_to_unban}: {str(e)}') + logging.CyberCPLogFileWriter.writeToFile(f'Warning removing firewalld rule for {ip_to_unban}: {str(e)}') final_dic = {'status': 1, 'message': f'IP address {ip_to_unban} has been unbanned successfully'} final_json = json.dumps(final_dic) diff --git a/plogical/firewallUtilities.py b/plogical/firewallUtilities.py index fe4295554..78c8995c0 100644 --- a/plogical/firewallUtilities.py +++ b/plogical/firewallUtilities.py @@ -118,22 +118,23 @@ class FirewallUtilities: logging.CyberCPLogFileWriter.writeToFile(f"Blocking IP address: {ip_address} - Reason: {reason}") + # executioner returns 1 on success, 0 on failure result = ProcessUtilities.executioner(command) - if result == 0: + if result == 1: logging.CyberCPLogFileWriter.writeToFile(f"Successfully blocked IP: {ip_address}") - - # Reload firewall to apply changes ProcessUtilities.executioner('firewall-cmd --reload') - - # Log the block in a dedicated file - block_log_path = "/usr/local/lscp/logs/blocked_ips.log" - with open(block_log_path, "a") as f: - from datetime import datetime - f.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - {ip_address} - {reason}\n") - + block_log_path = "/usr/local/CyberCP/data/blocked_ips.log" + try: + import os + os.makedirs(os.path.dirname(block_log_path), exist_ok=True) + with open(block_log_path, "a") as f: + from datetime import datetime + f.write(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')} - {ip_address} - {reason}\n") + except Exception as log_err: + logging.CyberCPLogFileWriter.writeToFile(f"Warning: could not write blocked_ips.log: {log_err}") return True, f"IP {ip_address} blocked successfully" else: - logging.CyberCPLogFileWriter.writeToFile(f"Failed to block IP: {ip_address}") + logging.CyberCPLogFileWriter.writeToFile(f"Failed to block IP: {ip_address} (executioner returned %s)" % result) return False, f"Failed to block IP: {ip_address}" except Exception as e: @@ -154,16 +155,14 @@ class FirewallUtilities: logging.CyberCPLogFileWriter.writeToFile(f"Unblocking IP address: {ip_address}") + # executioner returns 1 on success, 0 on failure result = ProcessUtilities.executioner(command) - if result == 0: + if result == 1: logging.CyberCPLogFileWriter.writeToFile(f"Successfully unblocked IP: {ip_address}") - - # Reload firewall to apply changes ProcessUtilities.executioner('firewall-cmd --reload') - return True, f"IP {ip_address} unblocked successfully" else: - logging.CyberCPLogFileWriter.writeToFile(f"Failed to unblock IP: {ip_address}") + logging.CyberCPLogFileWriter.writeToFile(f"Failed to unblock IP: {ip_address} (executioner returned %s)" % result) return False, f"Failed to unblock IP: {ip_address}" except Exception as e: diff --git a/plogical/processUtilities.py b/plogical/processUtilities.py index ff1322e8f..e9557f5a1 100644 --- a/plogical/processUtilities.py +++ b/plogical/processUtilities.py @@ -378,8 +378,8 @@ class ProcessUtilities(multi.Thread): if getpass.getuser() == 'root': if os.path.exists(ProcessUtilities.debugPath): logging.writeToFile(f"[executioner] Running as root, using normalExecutioner") - ProcessUtilities.normalExecutioner(command, shell, user) - return 1 + res = ProcessUtilities.normalExecutioner(command, shell, user) + return 1 if res == 1 else 0 if os.path.exists(ProcessUtilities.debugPath): logging.writeToFile(f"[executioner] Not root, using sendCommand via lscpd")