Configuration Security Audit
Configuration Security Audit
The configuration audit examines SSH daemon settings to ensure they align with security best practices. This phase identifies misconfigurations that could lead to unauthorized access or weakened security.
Perform comprehensive configuration analysis:
#!/usr/bin/env python3
# ssh-config-auditor.py
# Automated SSH configuration security audit
import re
import os
import json
import subprocess
from datetime import datetime
class SSHConfigAuditor:
def __init__(self, config_file='/etc/ssh/sshd_config'):
self.config_file = config_file
self.findings = []
self.score = 100 # Start with perfect score
# Security requirements
self.required_settings = {
'PermitRootLogin': ['no', 'prohibit-password', 'forced-commands-only'],
'PasswordAuthentication': ['no'],
'PermitEmptyPasswords': ['no'],
'ChallengeResponseAuthentication': ['no'],
'UsePAM': ['yes'],
'X11Forwarding': ['no'],
'StrictModes': ['yes'],
'IgnoreRhosts': ['yes'],
'HostbasedAuthentication': ['no'],
'Protocol': ['2']
}
self.recommended_settings = {
'MaxAuthTries': lambda x: int(x) <= 3,
'ClientAliveInterval': lambda x: 0 < int(x) <= 300,
'ClientAliveCountMax': lambda x: int(x) <= 3,
'LoginGraceTime': lambda x: int(x) <= 60,
'MaxStartups': lambda x: True, # Just should be set
'MaxSessions': lambda x: int(x) <= 10
}
self.weak_algorithms = {
'ciphers': ['3des-cbc', 'aes128-cbc', 'aes192-cbc', 'aes256-cbc',
'arcfour', 'arcfour128', 'arcfour256', 'blowfish-cbc',
'cast128-cbc'],
'macs': ['hmac-md5', 'hmac-md5-96', 'hmac-sha1', 'hmac-sha1-96',
'[email protected]'],
'kexalgorithms': ['diffie-hellman-group1-sha1',
'diffie-hellman-group14-sha1',
'diffie-hellman-group-exchange-sha1']
}
def parse_config(self):
"""Parse SSH configuration file"""
config = {}
try:
# Get effective configuration
result = subprocess.run(['sshd', '-T'],
capture_output=True, text=True)
if result.returncode == 0:
for line in result.stdout.splitlines():
if ' ' in line:
key, value = line.split(' ', 1)
config[key.lower()] = value.lower()
else:
# Fallback to parsing config file
with open(self.config_file, 'r') as f:
for line in f:
line = line.strip()
if line and not line.startswith('#'):
parts = line.split()
if len(parts) >= 2:
config[parts[0].lower()] = ' '.join(parts[1:]).lower()
except Exception as e:
self.add_finding('ERROR', f'Failed to parse configuration: {str(e)}', 10)
return config
def add_finding(self, severity, description, points=0):
"""Add audit finding"""
self.findings.append({
'severity': severity,
'description': description,
'timestamp': datetime.now().isoformat()
})
self.score -= points
def audit_required_settings(self, config):
"""Check required security settings"""
for setting, acceptable_values in self.required_settings.items():
current_value = config.get(setting.lower())
if not current_value:
self.add_finding('HIGH',
f'{setting} not configured (should be one of: {acceptable_values})',
5)
elif current_value not in acceptable_values:
self.add_finding('HIGH',
f'{setting} is "{current_value}" (should be one of: {acceptable_values})',
5)
def audit_recommended_settings(self, config):
"""Check recommended settings"""
for setting, validator in self.recommended_settings.items():
current_value = config.get(setting.lower())
if not current_value:
self.add_finding('MEDIUM',
f'{setting} not configured',
3)
else:
try:
if not validator(current_value):
self.add_finding('MEDIUM',
f'{setting} value "{current_value}" may be insecure',
3)
except:
self.add_finding('LOW',
f'{setting} value "{current_value}" could not be validated',
1)
def audit_cryptography(self, config):
"""Audit cryptographic settings"""
# Check ciphers
ciphers = config.get('ciphers', '').split(',')
for cipher in ciphers:
if cipher in self.weak_algorithms['ciphers']:
self.add_finding('HIGH',
f'Weak cipher enabled: {cipher}',
5)
# Check MACs
macs = config.get('macs', '').split(',')
for mac in macs:
if mac in self.weak_algorithms['macs']:
self.add_finding('HIGH',
f'Weak MAC enabled: {mac}',
5)
# Check Key Exchange
kex = config.get('kexalgorithms', '').split(',')
for algorithm in kex:
if algorithm in self.weak_algorithms['kexalgorithms']:
self.add_finding('HIGH',
f'Weak key exchange algorithm enabled: {algorithm}',
5)
def audit_access_controls(self, config):
"""Audit access control settings"""
# Check for user restrictions
if not config.get('allowusers') and not config.get('allowgroups'):
self.add_finding('MEDIUM',
'No user access restrictions (AllowUsers/AllowGroups) configured',
3)
# Check for permit directives
if config.get('permittunnel') == 'yes':
self.add_finding('MEDIUM',
'SSH tunneling is enabled globally',
3)
if config.get('permitopen'):
self.add_finding('INFO',
f'Port forwarding restrictions: {config.get("permitopen")}',
0)
def audit_logging(self, config):
"""Audit logging configuration"""
log_level = config.get('loglevel', 'info')
if log_level not in ['verbose', 'debug', 'debug1', 'debug2', 'debug3']:
self.add_finding('MEDIUM',
f'Logging level "{log_level}" may not capture enough detail',
2)
# Check syslog facility
facility = config.get('syslogfacility', 'auth')
if facility != 'auth' and facility != 'authpriv':
self.add_finding('LOW',
f'Non-standard syslog facility: {facility}',
1)
def check_file_permissions(self):
"""Check SSH file permissions"""
critical_files = {
'/etc/ssh/sshd_config': '600',
'/etc/ssh/ssh_host_rsa_key': '600',
'/etc/ssh/ssh_host_ecdsa_key': '600',
'/etc/ssh/ssh_host_ed25519_key': '600'
}
for file_path, expected_perms in critical_files.items():
if os.path.exists(file_path):
stat_info = os.stat(file_path)
actual_perms = oct(stat_info.st_mode)[-3:]
if actual_perms != expected_perms:
self.add_finding('HIGH',
f'{file_path} has permissions {actual_perms} (should be {expected_perms})',
5)
# Check ownership
if stat_info.st_uid != 0: # Not owned by root
self.add_finding('HIGH',
f'{file_path} not owned by root',
5)
def generate_report(self):
"""Generate audit report"""
report = {
'audit_date': datetime.now().isoformat(),
'config_file': self.config_file,
'score': max(0, self.score),
'findings': self.findings,
'summary': {
'total_findings': len(self.findings),
'high_severity': len([f for f in self.findings if f['severity'] == 'HIGH']),
'medium_severity': len([f for f in self.findings if f['severity'] == 'MEDIUM']),
'low_severity': len([f for f in self.findings if f['severity'] == 'LOW'])
}
}
# Add recommendations
report['recommendations'] = self.generate_recommendations()
return report
def generate_recommendations(self):
"""Generate remediation recommendations"""
recommendations = []
if any(f['description'].startswith('PermitRootLogin') for f in self.findings):
recommendations.append({
'priority': 'HIGH',
'action': 'Disable root login',
'command': 'sed -i "s/^PermitRootLogin.*/PermitRootLogin no/" /etc/ssh/sshd_config'
})
if any('Weak cipher' in f['description'] for f in self.findings):
recommendations.append({
'priority': 'HIGH',
'action': 'Update cipher configuration',
'config': 'Ciphers [email protected],[email protected],[email protected]'
})
if any('access restrictions' in f['description'] for f in self.findings):
recommendations.append({
'priority': 'MEDIUM',
'action': 'Implement user access restrictions',
'config': 'AllowGroups ssh-users'
})
return recommendations
def run_audit(self):
"""Execute complete audit"""
print("Starting SSH configuration audit...")
# Parse configuration
config = self.parse_config()
# Run audit checks
self.audit_required_settings(config)
self.audit_recommended_settings(config)
self.audit_cryptography(config)
self.audit_access_controls(config)
self.audit_logging(config)
self.check_file_permissions()
# Generate report
report = self.generate_report()
# Save report
with open(f'ssh_audit_report_{datetime.now().strftime("%Y%m%d_%H%M%S")}.json', 'w') as f:
json.dump(report, f, indent=2)
return report
# Run audit
if __name__ == '__main__':
auditor = SSHConfigAuditor()
report = auditor.run_audit()
print(f"\nAudit Complete - Score: {report['score']}/100")
print(f"Total Findings: {report['summary']['total_findings']}")
print(f" High: {report['summary']['high_severity']}")
print(f" Medium: {report['summary']['medium_severity']}")
print(f" Low: {report['summary']['low_severity']}")