Skip to content
Threats Guide

email_analyzer.py

Email Analyzer This script analyzes email headers, SPF/DKIM/DMARC records, and performs deep inspection of email content for phishing indicators.

  • detection
  • python
  • 515 lines

Run security tools only on systems you own or have written permission to test.

Run it

From the repository root, after pip install -r tools/requirements.txt
cd 04-social-engineering/phishing/detection
python3 email_analyzer.py --help

Source

View on GitHub
#!/usr/bin/env python3"""Email AnalyzerThis script analyzes email headers, SPF/DKIM/DMARC records, and performsdeep inspection of email content for phishing indicators.""" import osimport sysimport reimport jsonimport argparseimport dns.resolverimport smtplibimport socketfrom email import policyfrom email.parser import BytesParserfrom datetime import datetimeimport hashlibimport base64from colorama import init, Fore, Style init(autoreset=True) class EmailAnalyzer:    """    Deep Email Analysis Tool    """        def __init__(self):        self.results = {            'headers': {},            'authentication': {},            'security': {},            'analysis': {}        }            def parse_email(self, email_file):        """Parse email file"""        try:            with open(email_file, 'rb') as f:                msg = BytesParser(policy=policy.default).parse(f)            return msg        except Exception as e:            print(f"{Fore.RED}[!] Error parsing email: {e}{Style.RESET_ALL}")            return None        def analyze_headers_deep(self, msg):        """Deep header analysis"""        headers = {}                # Essential headers        essential = ['From', 'To', 'Subject', 'Date', 'Message-ID',                      'Return-Path', 'Reply-To', 'Sender']                for header in essential:            value = msg.get(header, '')            headers[header] = value                        # Analyze From header            if header == 'From':                from_domain = self.extract_domain(value)                headers['from_domain'] = from_domain                                # Check for display name spoofing                if '"' in value and '<' in value:                    headers['display_name_spoof'] = True                # Get all received headers        headers['received'] = msg.get_all('Received', [])                # Analyze received path        headers['received_analysis'] = self.analyze_received_path(headers['received'])                # Get authentication results        headers['auth_results'] = msg.get('Authentication-Results', '')                # Get DKIM signature        headers['dkim_signature'] = msg.get('DKIM-Signature', '')                # Get ARC headers        headers['arc'] = {            'seal': msg.get('ARC-Seal', ''),            'message-signature': msg.get('ARC-Message-Signature', ''),            'authentication-results': msg.get('ARC-Authentication-Results', '')        }                return headers        def analyze_received_path(self, received_headers):        """Analyze the Received header path"""        analysis = {            'hops': len(received_headers),            'servers': [],            'suspicious': []        }                for received in received_headers:            # Extract server info            server_info = {}                        # Look for "from" server            from_match = re.search(r'from\s+([^\s]+)', received, re.IGNORECASE)            if from_match:                server_info['from'] = from_match.group(1)                        # Look for "by" server            by_match = re.search(r'by\s+([^\s]+)', received, re.IGNORECASE)            if by_match:                server_info['by'] = by_match.group(1)                        # Look for "with" protocol            with_match = re.search(r'with\s+([^\s]+)', received, re.IGNORECASE)            if with_match:                server_info['with'] = with_match.group(1)                        # Look for IP            ip_match = re.search(r'\[(\d+\.\d+\.\d+\.\d+)\]', received)            if ip_match:                server_info['ip'] = ip_match.group(1)                        # Look for timestamp            time_match = re.search(r';\s*(.+)$', received)            if time_match:                server_info['time'] = time_match.group(1)                        analysis['servers'].append(server_info)                        # Check for suspicious patterns            if 'unknown' in received.lower():                analysis['suspicious'].append('Unknown server in path')            if 'untrusted' in received.lower():                analysis['suspicious'].append('Untrusted server in path')                return analysis        def check_spf(self, domain, sender_ip=None):        """Check SPF record for domain"""        result = {            'record': None,            'valid': False,            'error': None        }                try:            # Query SPF record            answers = dns.resolver.resolve(domain, 'TXT')            for rdata in answers:                txt = rdata.to_text()                if 'v=spf1' in txt:                    result['record'] = txt.strip('"')                    result['valid'] = True                    break        except dns.resolver.NoAnswer:            result['error'] = 'No SPF record found'        except dns.resolver.NXDOMAIN:            result['error'] = 'Domain does not exist'        except Exception as e:            result['error'] = str(e)                return result        def check_dkim(self, domain, selector='default'):        """Check DKIM record for domain"""        result = {            'record': None,            'valid': False,            'error': None        }                try:            # Query DKIM record            dkim_domain = f'{selector}._domainkey.{domain}'            answers = dns.resolver.resolve(dkim_domain, 'TXT')            for rdata in answers:                txt = rdata.to_text()                if 'v=DKIM1' in txt:                    result['record'] = txt.strip('"')                    result['valid'] = True                    break        except dns.resolver.NoAnswer:            result['error'] = 'No DKIM record found'        except Exception as e:            result['error'] = str(e)                return result        def check_dmarc(self, domain):        """Check DMARC record for domain"""        result = {            'record': None,            'policy': None,            'valid': False,            'error': None        }                try:            # Query DMARC record            dmarc_domain = f'_dmarc.{domain}'            answers = dns.resolver.resolve(dmarc_domain, 'TXT')            for rdata in answers:                txt = rdata.to_text()                if 'v=DMARC1' in txt:                    result['record'] = txt.strip('"')                    result['valid'] = True                                        # Extract policy                    policy_match = re.search(r'p=(\w+)', txt)                    if policy_match:                        result['policy'] = policy_match.group(1)                    break        except dns.resolver.NoAnswer:            result['error'] = 'No DMARC record found'        except Exception as e:            result['error'] = str(e)                return result        def verify_ssl_certificate(self, domain):        """Verify SSL certificate of mail server"""        result = {            'valid': False,            'issuer': None,            'expiry': None,            'error': None        }                try:            # Get MX records            mx_records = dns.resolver.resolve(domain, 'MX')            mx_record = str(mx_records[0].exchange).rstrip('.')                        # Try to connect to mail server            import ssl            import socket                        context = ssl.create_default_context()            with socket.create_connection((mx_record, 587), timeout=5) as sock:                with context.wrap_socket(sock, server_hostname=mx_record) as ssock:                    cert = ssock.getpeercert()                                        # Get issuer                    issuer = dict(x[0] for x in cert['issuer'])                    result['issuer'] = issuer.get('organizationName', 'Unknown')                                        # Get expiry                    from datetime import datetime                    not_after = cert.get('notAfter')                    if not_after:                        expiry = datetime.strptime(not_after, '%b %d %H:%M:%S %Y %Z')                        result['expiry'] = expiry.isoformat()                        result['valid'] = expiry > datetime.now()                            except Exception as e:            result['error'] = str(e)                return result        def extract_domain(self, email):        """Extract domain from email address"""        match = re.search(r'@([^>\s]+)', email)        if match:            return match.group(1).lower()        return None        def analyze_phishing_patterns(self, msg):        """Analyze content for phishing patterns"""        patterns = {            'urgency': [                r'urgent',                r'immediate action',                r'account.*suspended',                r'security.*alert',                r'unusual activity'            ],            'threats': [                r'will be closed',                r'will be deleted',                r'legal action',                r'prosecution'            ],            'requests': [                r'verify.*account',                r'confirm.*information',                r'update.*details',                r'click.*link'            ]        }                findings = {            'urgency': [],            'threats': [],            'requests': []        }                # Get email body        body = ''        if msg.is_multipart():            for part in msg.walk():                if part.get_content_type() in ['text/plain', 'text/html']:                    body += part.get_payload(decode=True).decode('utf-8', errors='ignore')        else:            body = msg.get_payload(decode=True).decode('utf-8', errors='ignore')                body_lower = body.lower()                for category, pattern_list in patterns.items():            for pattern in pattern_list:                if re.search(pattern, body_lower):                    findings[category].append(pattern)                return findings        def analyze_email(self, email_file):        """Complete email analysis"""        print(f"\n{Fore.CYAN}[*] Analyzing email: {email_file}{Style.RESET_ALL}")                msg = self.parse_email(email_file)        if not msg:            return None                # Analyze headers        self.results['headers'] = self.analyze_headers_deep(msg)                # Extract domain        from_header = self.results['headers'].get('From', '')        domain = self.extract_domain(from_header)                if domain:            # Check SPF            print(f"{Fore.CYAN}  [*] Checking SPF for {domain}...{Style.RESET_ALL}")            self.results['authentication']['spf'] = self.check_spf(domain)                        # Check DKIM            print(f"{Fore.CYAN}  [*] Checking DKIM for {domain}...{Style.RESET_ALL}")            self.results['authentication']['dkim'] = self.check_dkim(domain)                        # Check DMARC            print(f"{Fore.CYAN}  [*] Checking DMARC for {domain}...{Style.RESET_ALL}")            self.results['authentication']['dmarc'] = self.check_dmarc(domain)                        # Check SSL certificate            print(f"{Fore.CYAN}  [*] Verifying SSL certificate...{Style.RESET_ALL}")            self.results['security']['ssl'] = self.verify_ssl_certificate(domain)                # Analyze phishing patterns        self.results['analysis']['phishing_patterns'] = self.analyze_phishing_patterns(msg)                # Calculate risk score        self.results['analysis']['risk_score'] = self.calculate_risk_score()        self.results['analysis']['risk_level'] = self.get_risk_level(            self.results['analysis']['risk_score']        )                return self.results        def calculate_risk_score(self):        """Calculate overall risk score"""        score = 0                # Check SPF        spf = self.results.get('authentication', {}).get('spf', {})        if not spf.get('valid'):            score += 20                # Check DKIM        dkim = self.results.get('authentication', {}).get('dkim', {})        if not dkim.get('valid'):            score += 20                # Check DMARC        dmarc = self.results.get('authentication', {}).get('dmarc', {})        if not dmarc.get('valid'):            score += 20        elif dmarc.get('policy') == 'none':            score += 10                # Check SSL        ssl = self.results.get('security', {}).get('ssl', {})        if not ssl.get('valid'):            score += 15                # Check phishing patterns        patterns = self.results.get('analysis', {}).get('phishing_patterns', {})        score += len(patterns.get('urgency', [])) * 5        score += len(patterns.get('threats', [])) * 10        score += len(patterns.get('requests', [])) * 5                # Check received path        received_analysis = self.results.get('headers', {}).get('received_analysis', {})        score += len(received_analysis.get('suspicious', [])) * 10                return min(score, 100)        def get_risk_level(self, score):        """Get risk level from score"""        if score >= 70:            return 'CRITICAL'        elif score >= 50:            return 'HIGH'        elif score >= 30:            return 'MEDIUM'        else:            return 'LOW'        def print_report(self):        """Print analysis report"""        print(f"\n{Fore.CYAN}{'='*60}{Style.RESET_ALL}")        print(f"{Fore.CYAN}📊 EMAIL ANALYSIS REPORT{Style.RESET_ALL}")        print(f"{Fore.CYAN}{'='*60}{Style.RESET_ALL}")                # Risk level        risk_level = self.results.get('analysis', {}).get('risk_level', 'UNKNOWN')        risk_score = self.results.get('analysis', {}).get('risk_score', 0)                risk_color = {            'LOW': Fore.GREEN,            'MEDIUM': Fore.YELLOW,            'HIGH': Fore.RED,            'CRITICAL': Fore.RED + Style.BRIGHT        }.get(risk_level, Fore.WHITE)                print(f"\n{risk_color}Risk Level: {risk_level} (Score: {risk_score}){Style.RESET_ALL}")                # Headers        print(f"\n{Fore.CYAN}📨 Email Headers:{Style.RESET_ALL}")        headers = self.results.get('headers', {})        print(f"  From: {headers.get('From', 'N/A')}")        print(f"  To: {headers.get('To', 'N/A')}")        print(f"  Subject: {headers.get('Subject', 'N/A')}")        print(f"  Date: {headers.get('Date', 'N/A')}")        print(f"  Hops: {headers.get('received_analysis', {}).get('hops', 0)}")                # Authentication        print(f"\n{Fore.CYAN}🔐 Authentication:{Style.RESET_ALL}")                spf = self.results.get('authentication', {}).get('spf', {})        spf_color = Fore.GREEN if spf.get('valid') else Fore.RED        print(f"  {spf_color}SPF: {'✓ Valid' if spf.get('valid') else '✗ Invalid'}{Style.RESET_ALL}")        if spf.get('record'):            print(f"    Record: {spf['record']}")                dkim = self.results.get('authentication', {}).get('dkim', {})        dkim_color = Fore.GREEN if dkim.get('valid') else Fore.RED        print(f"  {dkim_color}DKIM: {'✓ Valid' if dkim.get('valid') else '✗ Invalid'}{Style.RESET_ALL}")                dmarc = self.results.get('authentication', {}).get('dmarc', {})        dmarc_color = Fore.GREEN if dmarc.get('valid') else Fore.RED        print(f"  {dmarc_color}DMARC: {'✓ Valid' if dmarc.get('valid') else '✗ Invalid'}{Style.RESET_ALL}")        if dmarc.get('policy'):            print(f"    Policy: {dmarc['policy']}")                # Security        print(f"\n{Fore.CYAN}🛡️ Security:{Style.RESET_ALL}")        ssl = self.results.get('security', {}).get('ssl', {})        ssl_color = Fore.GREEN if ssl.get('valid') else Fore.RED        print(f"  {ssl_color}SSL Certificate: {'✓ Valid' if ssl.get('valid') else '✗ Invalid'}{Style.RESET_ALL}")        if ssl.get('issuer'):            print(f"    Issuer: {ssl['issuer']}")                # Phishing patterns        patterns = self.results.get('analysis', {}).get('phishing_patterns', {})        if any(patterns.values()):            print(f"\n{Fore.RED}⚠️ Phishing Indicators:{Style.RESET_ALL}")                        if patterns.get('urgency'):                print(f"  Urgency patterns: {', '.join(patterns['urgency'])}")            if patterns.get('threats'):                print(f"  Threats: {', '.join(patterns['threats'])}")            if patterns.get('requests'):                print(f"  Suspicious requests: {', '.join(patterns['requests'])}")                # Received path issues        suspicious = headers.get('received_analysis', {}).get('suspicious', [])        if suspicious:            print(f"\n{Fore.RED}⚠️ Received Path Issues:{Style.RESET_ALL}")            for issue in suspicious:                print(f"  • {issue}")                print(f"\n{Fore.CYAN}{'='*60}{Style.RESET_ALL}")        def save_report(self, output_file):        """Save report to JSON"""        try:            with open(output_file, 'w') as f:                json.dump(self.results, f, indent=2)            print(f"{Fore.GREEN}[✓] Report saved to {output_file}{Style.RESET_ALL}")        except Exception as e:            print(f"{Fore.RED}[✗] Error saving report: {e}{Style.RESET_ALL}") def main():    parser = argparse.ArgumentParser(description='Email Analyzer')    parser.add_argument('-f', '--file', required=True, help='Email file (.eml) to analyze')    parser.add_argument('-o', '--output', help='Output report file')        args = parser.parse_args()        print(f"""    {Fore.CYAN}╔═══════════════════════════════════════╗    ║        Email Analyzer v1.0            ║    ║     Deep Email Security Analysis      ║    ╚═══════════════════════════════════════╝{Style.RESET_ALL}    """)        analyzer = EmailAnalyzer()    results = analyzer.analyze_email(args.file)        if results:        analyzer.print_report()        if args.output:            analyzer.save_report(args.output) if __name__ == "__main__":    main()