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
This commit is contained in:
master3395
2026-02-04 18:30:03 +01:00
parent ba087190eb
commit 4392676b27
6 changed files with 89 additions and 211 deletions

View File

@@ -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);

View File

@@ -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() {

View File

@@ -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:

View File

@@ -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)

View File

@@ -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:

View File

@@ -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")