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']}")