""" This plugin provides comprehensive HTTP security header analysis. """ import logging import aiohttp import asyncio import simplematrixbotlib as botlib from urllib.parse import urlparse import ssl import socket import datetime from plugins.common import is_public_destination, collapsible_summary, html_escape async def handle_command(room, message, bot, prefix, config): """ Function to handle !headers command for HTTP security header analysis. """ match = botlib.MessageMatch(room, message, bot, prefix) if match.is_not_from_this_bot() and match.prefix() and match.command("headers"): logging.info("Received !headers command") args = match.args() if len(args) < 1: await show_usage(room, bot) return url = args[0].strip() # Add protocol if missing if not url.startswith(('http://', 'https://')): url = 'https://' + url # SSRF protection: refuse internal hosts parsed = urlparse(url) host = parsed.hostname if not is_public_destination(host): await bot.api.send_text_message(room.room_id, "❌ Scanning of private/internal addresses is not allowed.") return await analyze_headers(room, bot, url) async def show_usage(room, bot): """Display headers command usage.""" usage = """ 🔒 HTTP Security Headers Analysis !headers <url> - Comprehensive HTTP security header analysis Examples:!headers example.com!headers https://github.com!headers http://localhost:8080 Analyzes: • Security headers presence and configuration • SSL/TLS certificate information • HTTP to HTTPS redirects • Security scoring and recommendations """ await bot.api.send_markdown_message(room.room_id, usage) async def analyze_headers(room, bot, url): """Perform comprehensive HTTP security header analysis.""" try: await bot.api.send_text_message(room.room_id, f"🔍 Analyzing security headers for: {html_escape(url)}") results = { 'url': url, 'http_headers': {}, 'https_headers': {}, 'redirect_chain': [], 'ssl_info': {}, 'security_score': 0, 'recommendations': [] } # Test HTTP first (if HTTPS was provided, we'll still check redirects) parsed = urlparse(url) http_url = f"http://{parsed.netloc or parsed.path}" https_url = f"https://{parsed.netloc or parsed.path}" # Analyze HTTP response and redirects await analyze_http_response(results, http_url if not url.startswith('https://') else https_url) # Analyze HTTPS response if url.startswith('https://') or results.get('redirects_to_https'): await analyze_https_response(results, https_url) # Analyze SSL certificate if HTTPS if url.startswith('https://') or results.get('redirects_to_https'): await analyze_ssl_certificate(results, parsed.netloc or parsed.path) # Calculate security score await calculate_security_score(results) # Generate recommendations await generate_recommendations(results) # Format and send results output = await format_header_analysis(results) await bot.api.send_markdown_message(room.room_id, output) logging.info(f"Completed header analysis for {url}") except Exception as e: await bot.api.send_text_message(room.room_id, f"Error analyzing headers: {str(e)}") logging.error(f"Error in analyze_headers: {e}") async def analyze_http_response(results, url): """Analyze HTTP response and redirect chain.""" try: async with aiohttp.ClientSession() as session: async with session.get(url, allow_redirects=True, timeout=aiohttp.ClientTimeout(total=10)) as response: results['final_url'] = str(response.url) results['status_code'] = response.status results['http_headers'] = dict(response.headers) results['redirects_to_https'] = response.url.scheme == 'https' # aiohttp doesn't give access to redirect history easily, so we'll mark if final URL differs if str(response.url) != url: results['redirect_chain'] = [{'url': url, 'status_code': 301}] # simplified except aiohttp.ClientError as e: results['http_error'] = str(e) async def analyze_https_response(results, url): """Analyze HTTPS response headers.""" try: async with aiohttp.ClientSession() as session: async with session.get(url, allow_redirects=False, timeout=aiohttp.ClientTimeout(total=10)) as response: results['https_headers'] = dict(response.headers) results['https_status'] = response.status except aiohttp.ClientError as e: results['https_error'] = str(e) async def analyze_ssl_certificate(results, domain): """Analyze SSL certificate information (run in thread to avoid event loop blocking).""" def _get_cert(): try: context = ssl.create_default_context() with socket.create_connection((domain, 443), timeout=10) as sock: with context.wrap_socket(sock, server_hostname=domain) as ssock: cert = ssock.getpeercert() return { 'subject': dict(x[0] for x in cert['subject']), 'issuer': dict(x[0] for x in cert['issuer']), 'not_before': cert['notBefore'], 'not_after': cert['notAfter'], 'san': cert.get('subjectAltName', []), 'version': cert.get('version'), 'serial_number': cert.get('serialNumber') } except Exception as e: return f"Error: {e}" loop = asyncio.get_running_loop() ssl_data = await loop.run_in_executor(None, _get_cert) if isinstance(ssl_data, str): results['ssl_error'] = ssl_data else: results['ssl_info'] = ssl_data async def calculate_security_score(results): """Calculate overall security score based on headers and configuration.""" score = 100 missing_headers = [] critical_headers = [ 'Strict-Transport-Security', 'Content-Security-Policy', 'X-Content-Type-Options', 'X-Frame-Options', 'X-XSS-Protection' ] headers = results.get('https_headers') or results.get('http_headers', {}) for header in critical_headers: if header not in headers: score -= 15 missing_headers.append(header) # Check HSTS configuration hsts = headers.get('Strict-Transport-Security', '') if 'max-age=31536000' not in hsts: score -= 10 if 'includeSubDomains' not in hsts: score -= 5 if 'preload' not in hsts: score -= 5 # Check CSP configuration csp = headers.get('Content-Security-Policy', '') if not csp: score -= 10 elif "default-src 'none'" not in csp and "default-src 'self'" not in csp: score -= 5 # Check for insecure headers insecure_headers = ['Server', 'X-Powered-By', 'X-AspNet-Version'] for header in insecure_headers: if header in headers: score -= 5 # Bonus for good practices if headers.get('Referrer-Policy'): score += 5 if headers.get('Feature-Policy') or headers.get('Permissions-Policy'): score += 5 if headers.get('X-Content-Type-Options') == 'nosniff': score += 5 if headers.get('X-Frame-Options') in ['DENY', 'SAMEORIGIN']: score += 5 # HTTPS enforcement bonus if results.get('redirects_to_https'): score += 10 results['security_score'] = max(0, score) results['missing_headers'] = missing_headers async def generate_recommendations(results): """Generate security recommendations based on analysis.""" recommendations = [] headers = results.get('https_headers') or results.get('http_headers', {}) if 'Strict-Transport-Security' not in headers: recommendations.append("🔒 Implement HSTS header with max-age=31536000, includeSubDomains, and preload") else: hsts = headers['Strict-Transport-Security'] if 'max-age=31536000' not in hsts: recommendations.append("🔒 Increase HSTS max-age to 31536000 (1 year)") if 'includeSubDomains' not in hsts: recommendations.append("🔒 Add includeSubDomains to HSTS header") if 'preload' not in hsts: recommendations.append("🔒 Consider adding preload directive to HSTS for browser preloading") if 'Content-Security-Policy' not in headers: recommendations.append("🛡️ Implement Content Security Policy to prevent XSS attacks") if 'X-Frame-Options' not in headers: recommendations.append("🚫 Add X-Frame-Options header to prevent clickjacking (DENY or SAMEORIGIN)") if 'X-Content-Type-Options' not in headers: recommendations.append("📄 Add X-Content-Type-Options: nosniff to prevent MIME type sniffing") if 'Referrer-Policy' not in headers: recommendations.append("🔗 Implement Referrer-Policy to control referrer information leakage") if 'Server' in headers or 'X-Powered-By' in headers: recommendations.append("🕵️ Remove Server and X-Powered-By headers to avoid information disclosure") if not results.get('redirects_to_https') and not results['url'].startswith('https://'): recommendations.append("🔐 Implement HTTP to HTTPS redirects") results['recommendations'] = recommendations async def format_header_analysis(results): """Format the header analysis results for display.""" safe_url = html_escape(results['url']) output = f"🔒 Security Headers Analysis: {safe_url}

" # Security Score score = results['security_score'] score_emoji = "🟢" if score >= 80 else "🟡" if score >= 60 else "🔴" output += f"{score_emoji} Security Score: {score}/100

" # Basic Information output += "📊 Basic Information
" output += f" • Final URL: {html_escape(results.get('final_url', 'N/A'))}
" output += f" • Status Code: {results.get('status_code', 'N/A')}
" if results.get('redirects_to_https'): output += f" • HTTPS Redirect: ✅ Enforced
" else: output += f" • HTTPS Redirect: ❌ Not enforced
" output += "
" # Security Headers Analysis headers = results.get('https_headers') or results.get('http_headers', {}) output += "🛡️ Security Headers Analysis
" security_headers = { 'Strict-Transport-Security': ('🔒', 'HSTS'), 'Content-Security-Policy': ('🛡️', 'CSP'), 'X-Frame-Options': ('🚫', 'Clickjacking Protection'), 'X-Content-Type-Options': ('📄', 'MIME Sniffing'), 'X-XSS-Protection': ('❌', 'XSS Protection (Deprecated)'), 'Referrer-Policy': ('🔗', 'Referrer Policy'), 'Feature-Policy': ('⚙️', 'Feature Policy'), 'Permissions-Policy': ('🔧', 'Permissions Policy'), } for header, (emoji, description) in security_headers.items(): if header in headers: value = html_escape(str(headers[header]))[:100] output += f" • {emoji} {header}: ✅ {value}
" else: output += f" • {emoji} {header}: ❌ Missing
" output += "
" # Other Headers (Information Disclosure) output += "📋 Other Headers
" for header in ['Server', 'X-Powered-By']: if header in headers: output += f" • 🔍 {header}: {html_escape(str(headers[header]))}
" output += "
" # SSL Certificate Information (if available) if results.get('ssl_info') and 'subject' in results['ssl_info']: output += "🔐 SSL Certificate
" ssl_info = results['ssl_info'] if ssl_info.get('subject'): output += f" • Subject: {html_escape(ssl_info['subject'].get('commonName', 'N/A'))}
" if ssl_info.get('issuer'): output += f" • Issuer: {html_escape(ssl_info['issuer'].get('organizationName', 'N/A'))}
" if ssl_info.get('not_after'): output += f" • Expires: {html_escape(ssl_info['not_after'])}
" output += "
" # Recommendations if results.get('recommendations'): output += "💡 Security Recommendations
" for rec in results['recommendations'][:8]: output += f" • {rec}
" output += "
" # Final rating if score >= 80: rating = "🟢 Excellent" elif score >= 60: rating = "🟡 Good" elif score >= 40: rating = "🟠 Fair" else: rating = "🔴 Poor" output += f"📈 Security Rating: {rating}
" # Wrap in collapsible details return collapsible_summary(f"🔒 Security Headers Analysis: {safe_url} (Score: {score}/100)", output) __version__ = "1.0.2" __author__ = "Funguy Bot" __description__ = "HTTP security header analysis (SSRF‑safe, async)" __help__ = """
!headers – HTTP security header scanner

!headers <url> – Checks HSTS, CSP, X-Frame-Options, etc.
Provides security score (0-100) and recommendations. Also shows SSL certificate info.

"""