mirror of
https://github.com/usmannasir/cyberpanel.git
synced 2025-12-16 05:19:43 +01:00
@@ -980,6 +980,10 @@ app.controller('dashboardStatsController', function ($scope, $http, $timeout) {
|
||||
$scope.showAddonRequired = false;
|
||||
$scope.addonInfo = {};
|
||||
|
||||
// IP Blocking functionality
|
||||
$scope.blockingIP = null;
|
||||
$scope.blockedIPs = {};
|
||||
|
||||
$scope.analyzeSSHSecurity = function() {
|
||||
$scope.loadingSecurityAnalysis = true;
|
||||
$scope.showAddonRequired = false;
|
||||
@@ -999,6 +1003,64 @@ app.controller('dashboardStatsController', function ($scope, $http, $timeout) {
|
||||
$scope.loadingSecurityAnalysis = false;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.blockIPAddress = function(ipAddress) {
|
||||
if (!$scope.blockingIP) {
|
||||
$scope.blockingIP = ipAddress;
|
||||
|
||||
var data = {
|
||||
ip_address: ipAddress
|
||||
};
|
||||
|
||||
var config = {
|
||||
headers: {
|
||||
'X-CSRFToken': getCookie('csrftoken')
|
||||
}
|
||||
};
|
||||
|
||||
$http.post('/base/blockIPAddress', data, config).then(function (response) {
|
||||
$scope.blockingIP = null;
|
||||
if (response.data && response.data.status === 1) {
|
||||
// Mark IP as blocked
|
||||
$scope.blockedIPs[ipAddress] = true;
|
||||
|
||||
// Show success notification
|
||||
new PNotify({
|
||||
title: 'Success',
|
||||
text: `IP address ${ipAddress} has been blocked successfully using ${response.data.firewall.toUpperCase()}`,
|
||||
type: 'success',
|
||||
delay: 5000
|
||||
});
|
||||
|
||||
// Refresh security analysis to update alerts
|
||||
$scope.analyzeSSHSecurity();
|
||||
} else {
|
||||
// Show error notification
|
||||
new PNotify({
|
||||
title: 'Error',
|
||||
text: response.data && response.data.error ? response.data.error : 'Failed to block IP address',
|
||||
type: 'error',
|
||||
delay: 5000
|
||||
});
|
||||
}
|
||||
}, function (err) {
|
||||
$scope.blockingIP = null;
|
||||
var errorMessage = 'Failed to block IP address';
|
||||
if (err.data && err.data.error) {
|
||||
errorMessage = err.data.error;
|
||||
} else if (err.data && err.data.message) {
|
||||
errorMessage = err.data.message;
|
||||
}
|
||||
|
||||
new PNotify({
|
||||
title: 'Error',
|
||||
text: errorMessage,
|
||||
type: 'error',
|
||||
delay: 5000
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Initial fetch
|
||||
$scope.refreshTopProcesses();
|
||||
|
||||
@@ -663,6 +663,23 @@
|
||||
<strong style="font-size: 12px; color: #1e293b;">Recommendation:</strong>
|
||||
<p style="margin: 4px 0 0 0; font-size: 12px; color: #475569; white-space: pre-line;">{$ alert.recommendation $}</p>
|
||||
</div>
|
||||
<!-- Add to Firewall Button for Brute Force Attacks -->
|
||||
<div ng-if="alert.title === 'Brute Force Attack Detected' && alert.details && alert.details['IP Address']" style="margin-top: 12px;">
|
||||
<button ng-click="blockIPAddress(alert.details['IP Address'])"
|
||||
ng-disabled="blockingIP === alert.details['IP Address']"
|
||||
style="background: #dc2626; color: white; border: none; padding: 8px 16px; border-radius: 6px; font-size: 12px; font-weight: 600; cursor: pointer; display: inline-flex; align-items: center; gap: 6px;"
|
||||
onmouseover="this.style.background='#b91c1c'"
|
||||
onmouseout="this.style.background='#dc2626'">
|
||||
<i class="fas fa-ban" ng-if="blockingIP !== alert.details['IP Address']"></i>
|
||||
<i class="fas fa-spinner fa-spin" ng-if="blockingIP === alert.details['IP Address']"></i>
|
||||
<span ng-if="blockingIP !== alert.details['IP Address']">Block IP</span>
|
||||
<span ng-if="blockingIP === alert.details['IP Address']">Blocking...</span>
|
||||
</button>
|
||||
<span ng-if="blockedIPs && blockedIPs[alert.details['IP Address']]"
|
||||
style="margin-left: 10px; color: #10b981; font-size: 12px; font-weight: 600;">
|
||||
<i class="fas fa-check-circle"></i> Blocked
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<span style="background: {$ alert.severity === 'high' ? '#fee2e2' : (alert.severity === 'medium' ? '#fef3c7' : (alert.severity === 'low' ? '#dbeafe' : '#d1fae5')) $};
|
||||
color: {$ alert.severity === 'high' ? '#dc2626' : (alert.severity === 'medium' ? '#f59e0b' : (alert.severity === 'low' ? '#3b82f6' : '#10b981')) $};
|
||||
|
||||
@@ -20,6 +20,9 @@
|
||||
{{ cosmetic.MainDashboardCSS | safe }}
|
||||
</style>
|
||||
|
||||
<!-- Mobile Responsive CSS -->
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'baseTemplate/assets/mobile-responsive.css' %}?v={{ CP_VERSION }}">
|
||||
|
||||
<!-- Core Scripts -->
|
||||
<script src="{% static 'baseTemplate/angularjs.1.6.5.js' %}?v={{ CP_VERSION }}"></script>
|
||||
<script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
|
||||
@@ -955,7 +958,7 @@
|
||||
<body>
|
||||
<!-- Header -->
|
||||
<div id="header">
|
||||
<button id="mobile-menu-toggle" onclick="toggleSidebar()">
|
||||
<button id="mobile-menu-toggle" class="mobile-menu-toggle" onclick="toggleSidebar()" style="display: none;">
|
||||
<i class="fas fa-bars"></i>
|
||||
</button>
|
||||
<div class="logo">
|
||||
@@ -1177,6 +1180,9 @@
|
||||
<a href="{% url 'listChildDomains' %}" class="menu-item">
|
||||
<span>List Sub/Addon Domains</span>
|
||||
</a>
|
||||
<a href="{% url 'fixSubdomainLogs' %}" class="menu-item">
|
||||
<span>Fix Subdomain Logs</span>
|
||||
</a>
|
||||
{% if admin or modifyWebsite %}
|
||||
<a href="{% url 'modifyWebsite' %}" class="menu-item">
|
||||
<span>Modify Website</span>
|
||||
@@ -1457,6 +1463,10 @@
|
||||
<a href="{% url 'manageSSL' %}" class="menu-item">
|
||||
<span>Manage SSL</span>
|
||||
</a>
|
||||
<a href="{% url 'sslReconcile' %}" class="menu-item">
|
||||
<span>SSL Reconciliation</span>
|
||||
<span class="badge">NEW</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if admin or hostnameSSL %}
|
||||
<a href="{% url 'sslForHostName' %}" class="menu-item">
|
||||
@@ -1786,9 +1796,51 @@
|
||||
<!-- Scripts -->
|
||||
<script>
|
||||
function toggleSidebar() {
|
||||
document.getElementById('sidebar').classList.toggle('show');
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
const mainContent = document.getElementById('main-content');
|
||||
const mobileToggle = document.getElementById('mobile-menu-toggle');
|
||||
|
||||
sidebar.classList.toggle('show');
|
||||
|
||||
// Add/remove sidebar-open class to main content for mobile
|
||||
if (window.innerWidth <= 768) {
|
||||
if (mainContent) {
|
||||
mainContent.classList.toggle('sidebar-open');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Show/hide mobile menu toggle based on screen size
|
||||
function handleResize() {
|
||||
const mobileToggle = document.getElementById('mobile-menu-toggle');
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
const mainContent = document.getElementById('main-content');
|
||||
|
||||
if (window.innerWidth <= 768) {
|
||||
mobileToggle.style.display = 'block';
|
||||
// Close sidebar by default on mobile
|
||||
sidebar.classList.remove('show');
|
||||
if (mainContent) {
|
||||
mainContent.classList.remove('sidebar-open');
|
||||
}
|
||||
} else {
|
||||
mobileToggle.style.display = 'none';
|
||||
// Show sidebar on desktop
|
||||
sidebar.classList.remove('show');
|
||||
if (mainContent) {
|
||||
mainContent.classList.remove('sidebar-open');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add event listener for window resize
|
||||
window.addEventListener('resize', handleResize);
|
||||
|
||||
// Initialize on page load
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
handleResize();
|
||||
});
|
||||
|
||||
function toggleSubmenu(id, element) {
|
||||
const submenu = document.getElementById(id);
|
||||
const allSubmenus = document.querySelectorAll('.submenu');
|
||||
|
||||
@@ -24,6 +24,7 @@ urlpatterns = [
|
||||
re_path(r'^getSSHUserActivity$', views.getSSHUserActivity, name='getSSHUserActivity'),
|
||||
re_path(r'^getTopProcesses$', views.getTopProcesses, name='getTopProcesses'),
|
||||
re_path(r'^analyzeSSHSecurity$', views.analyzeSSHSecurity, name='analyzeSSHSecurity'),
|
||||
re_path(r'^blockIPAddress$', views.blockIPAddress, name='blockIPAddress'),
|
||||
re_path(r'^dismiss_backup_notification$', views.dismiss_backup_notification, name='dismiss_backup_notification'),
|
||||
re_path(r'^dismiss_ai_scanner_notification$', views.dismiss_ai_scanner_notification, name='dismiss_ai_scanner_notification'),
|
||||
re_path(r'^get_notification_preferences$', views.get_notification_preferences, name='get_notification_preferences'),
|
||||
|
||||
@@ -820,25 +820,18 @@ def analyzeSSHSecurity(request):
|
||||
|
||||
alerts = []
|
||||
|
||||
# Detect which firewall is in use
|
||||
firewall_cmd = ''
|
||||
# Use firewalld (CSF has been discontinued)
|
||||
firewall_cmd = 'firewalld'
|
||||
try:
|
||||
# Check for CSF
|
||||
csf_check = ProcessUtilities.outputExecutioner('which csf')
|
||||
if csf_check and '/csf' in csf_check:
|
||||
firewall_cmd = 'csf'
|
||||
# Verify firewalld is active
|
||||
firewalld_check = ProcessUtilities.outputExecutioner('systemctl is-active firewalld')
|
||||
if not (firewalld_check and 'active' in firewalld_check):
|
||||
# Firewalld not active, but continue analysis with firewalld commands
|
||||
pass
|
||||
except:
|
||||
# Continue with firewalld as default
|
||||
pass
|
||||
|
||||
if not firewall_cmd:
|
||||
try:
|
||||
# Check for firewalld
|
||||
firewalld_check = ProcessUtilities.outputExecutioner('systemctl is-active firewalld')
|
||||
if firewalld_check and 'active' in firewalld_check:
|
||||
firewall_cmd = 'firewalld'
|
||||
except:
|
||||
firewall_cmd = 'firewalld' # Default to firewalld
|
||||
|
||||
# Determine log path
|
||||
distro = ProcessUtilities.decideDistro()
|
||||
if distro in [ProcessUtilities.ubuntu, ProcessUtilities.ubuntu20]:
|
||||
@@ -941,10 +934,7 @@ def analyzeSSHSecurity(request):
|
||||
# High severity: Brute force attacks
|
||||
for ip, count in failed_passwords.items():
|
||||
if count >= 10:
|
||||
if firewall_cmd == 'csf':
|
||||
recommendation = f'Block this IP immediately:\ncsf -d {ip} "Brute force attack - {count} failed attempts"'
|
||||
else:
|
||||
recommendation = f'Block this IP immediately:\nfirewall-cmd --permanent --add-rich-rule="rule family=ipv4 source address={ip} drop" && firewall-cmd --reload'
|
||||
recommendation = f'Block this IP immediately:\nfirewall-cmd --permanent --add-rich-rule="rule family=ipv4 source address={ip} drop" && firewall-cmd --reload'
|
||||
|
||||
alerts.append({
|
||||
'title': 'Brute Force Attack Detected',
|
||||
@@ -1108,6 +1098,144 @@ def analyzeSSHSecurity(request):
|
||||
except Exception as e:
|
||||
return HttpResponse(json.dumps({'error': str(e)}), content_type='application/json', status=500)
|
||||
|
||||
@csrf_exempt
|
||||
@require_POST
|
||||
def blockIPAddress(request):
|
||||
"""
|
||||
Block an IP address using the appropriate firewall (CSF or firewalld)
|
||||
"""
|
||||
try:
|
||||
user_id = request.session.get('userID')
|
||||
if not user_id:
|
||||
return HttpResponse(json.dumps({'error': 'Not logged in'}), content_type='application/json', status=403)
|
||||
|
||||
currentACL = ACLManager.loadedACL(user_id)
|
||||
if not currentACL.get('admin', 0):
|
||||
return HttpResponse(json.dumps({'error': 'Admin only'}), content_type='application/json', status=403)
|
||||
|
||||
# Check if user has CyberPanel addons
|
||||
if not ACLManager.CheckForPremFeature('all'):
|
||||
return HttpResponse(json.dumps({
|
||||
'status': 0,
|
||||
'error': 'Premium feature required'
|
||||
}), content_type='application/json', status=403)
|
||||
|
||||
data = json.loads(request.body)
|
||||
ip_address = data.get('ip_address', '').strip()
|
||||
|
||||
if not ip_address:
|
||||
return HttpResponse(json.dumps({
|
||||
'status': 0,
|
||||
'error': 'IP address is required'
|
||||
}), content_type='application/json', status=400)
|
||||
|
||||
# Validate IP address format and check for private/reserved ranges
|
||||
import re
|
||||
import ipaddress
|
||||
ip_pattern = r'^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'
|
||||
if not re.match(ip_pattern, ip_address):
|
||||
return HttpResponse(json.dumps({
|
||||
'status': 0,
|
||||
'error': 'Invalid IP address format'
|
||||
}), content_type='application/json', status=400)
|
||||
|
||||
# Check for private/reserved IP ranges to prevent self-blocking
|
||||
try:
|
||||
ip_obj = ipaddress.ip_address(ip_address)
|
||||
if ip_obj.is_private or ip_obj.is_loopback or ip_obj.is_link_local or ip_obj.is_reserved:
|
||||
return HttpResponse(json.dumps({
|
||||
'status': 0,
|
||||
'error': 'Cannot block private, loopback, link-local, or reserved IP addresses'
|
||||
}), content_type='application/json', status=400)
|
||||
|
||||
# Additional check for common problematic ranges
|
||||
if (ip_address.startswith('127.') or # Loopback
|
||||
ip_address.startswith('169.254.') or # Link-local
|
||||
ip_address.startswith('224.') or # Multicast
|
||||
ip_address.startswith('255.') or # Broadcast
|
||||
ip_address in ['0.0.0.0', '::1']): # Invalid/loopback
|
||||
return HttpResponse(json.dumps({
|
||||
'status': 0,
|
||||
'error': 'Cannot block system or reserved IP addresses'
|
||||
}), content_type='application/json', status=400)
|
||||
|
||||
except ValueError:
|
||||
return HttpResponse(json.dumps({
|
||||
'status': 0,
|
||||
'error': 'Invalid IP address'
|
||||
}), content_type='application/json', status=400)
|
||||
|
||||
# Use firewalld (CSF has been discontinued)
|
||||
firewall_cmd = 'firewalld'
|
||||
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)
|
||||
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)}'
|
||||
|
||||
if success:
|
||||
# Log the action
|
||||
import plogical.CyberCPLogFileWriter as logging
|
||||
logging.CyberCPLogFileWriter.writeToFile(f'IP address {ip_address} blocked via CyberPanel dashboard by user {user_id}')
|
||||
|
||||
return HttpResponse(json.dumps({
|
||||
'status': 1,
|
||||
'message': f'Successfully blocked IP address {ip_address}',
|
||||
'firewall': firewall_cmd
|
||||
}), content_type='application/json')
|
||||
else:
|
||||
return HttpResponse(json.dumps({
|
||||
'status': 0,
|
||||
'error': error_message or 'Failed to block IP address'
|
||||
}), content_type='application/json', status=500)
|
||||
|
||||
except Exception as e:
|
||||
return HttpResponse(json.dumps({
|
||||
'status': 0,
|
||||
'error': f'Server error: {str(e)}'
|
||||
}), content_type='application/json', status=500)
|
||||
|
||||
@csrf_exempt
|
||||
@require_POST
|
||||
def getSSHUserActivity(request):
|
||||
|
||||
Reference in New Issue
Block a user