diff --git a/README.md b/README.md
index 47eb87a..5c764e5 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@ Time went by, I was adding support for more and more SMTP headers - and here we
## Info
-This tool accepts on input an `*.EML` or `*.txt` file with all the SMTP headers. It will then extract a subset of interesting headers and using **79+** tests will attempt to decode them as much as possible.
+This tool accepts on input an `*.EML` or `*.txt` file with all the SMTP headers. It will then extract a subset of interesting headers and using **95+** tests will attempt to decode them as much as possible.
This script also extracts all IPv4 addresses and domain names and performs full DNS resolution of them.
@@ -40,7 +40,7 @@ In order to embellish your Phishing HTML code before sending it to your client,
### Processed headers
-Processed headers (more than **67+** headers are parsed):
+Processed headers (more than **76+** headers are parsed):
- `X-forefront-antispam-report`
- `X-exchange-antispam`
@@ -109,6 +109,16 @@ Processed headers (more than **67+** headers are parsed):
- `X-microsoft-antispam-untrusted`
- `X-sophos-senderhistory`
- `X-sophos-rescan`
+- `X-MS-Exchange-CrossTenant-Id`
+- `X-OriginatorOrg`
+- `IronPort-Data`
+- `IronPort-HdrOrdr`
+- `X-DKIM`
+- `DKIM-Filter`
+- `X-SpamExperts-Class`
+- `X-SpamExperts-Evidence`
+- `X-Recommended-Action`
+- `X-AppInfo`
Most of these headers are not fully documented, therefore the script is unable to pinpoint all the details, but at least it collects all I could find on them.
@@ -201,7 +211,7 @@ Having sent more than 60 mails already, this is what I can tell by now about Mic
# Message contained tag with URL containing GET parameter with value of another URL: ex. href="https://foo.bar/file?aaa=https://baz.xyz/"',
+ '45080400002' : 'Something about tag\'s URL. Possibly it contained GET parameter with value of another URL: ex. href="https://foo.bar/file?aaa=https://baz.xyz/"',
# Message contained with href pointing to a file with dangerous extension, such as file.exe
'460985005' : 'Mail body contained HTML tag with href URL pointing to a file with dangerous extension (such as .exe)',
@@ -216,6 +226,29 @@ Having sent more than 60 mails already, this is what I can tell by now about Mic
#
'121216002' : 'First Hop MTA SMTP Server used as a SMTP Relay. It\'s known to originate e-mails, but here it acted as a Relay. Or maybe due to use of "with ESMTPSA" instead of ESMTPS?',
+ # Triggered on message with added to HTML: https://www.reddit.com/
+ '966005' : 'Mail body contained link tag with potentially masqueraded URL: https://example.com',
+
+ #
+ # Message1: GoPhish EC2 -> another EC2 with socat to smtp.gmail.com:587 (authenticated) -> Target
+ # Message2: GoPhish EC2 -> Gsuite -> Target
+ #
+ # Subject, mail body were exactly the same.
+ #
+ # Below two rules were added to the second message. My understanding is that they're somehow referring
+ # to the reputation of the first-hop server, maybe reverse-DNS resolution.
+ #
+ '5002400100002' : "(GUESSING) Somehow related to First Hop server reputation, it's reverse-PTR resolution or domain impersonation",
+ '58800400005' : "(GUESSING) Somehow related to First Hop server reputation, it's reverse-PTR resolution or domain impersonation",
+
+ '19625305002' : '(GUESSING) Something to do with the HTML code and used tags/structures',
+ '43540500002' : '(GUESSING) Something to do with the HTML code and used tags/structures',
+
+ '460985005' : '(GUESSING) Something to do with either more-complex HTML code or with the tag and its URL.',
+
+ # Triggered on an empty text message, subject "test" - that was marked with "Domain Impersonation", however
+ # ForeFront Anti-Spam headers did not support that Domain Impersonation. Weird.
+ '22186003' : '(GUESSING) Something to do with either Text message (non-HTML) or probable Domain Impersonation'
}
```
@@ -347,9 +380,22 @@ C:\> py decode-spam-headers.py -l tests
77 - Other interesting headers
78 - Security Appliances Spotted
79 - Email Providers Infrastructure Clues
- 80 - X-Microsoft-Antispam-Message-Info
- 81 - Decoded Mail-encoded header values
+ 80 - X-Microsoft-Antispam-Message-Info (use -a to show its results)
+ 81 - Decoded Mail-encoded header values (use -a to show its results)
82 - Header Containing Client IP
+ 83 - Office365 Tenant ID
+ 84 - Organization Name
+ 85 - MS Defender For Office365 Safe Links Version
+ 86 - Suspicious Words in Headers
+ 87 - AWS SES Outgoing
+ 88 - IronPort-Data
+ 89 - IronPort-HdrOrder
+ 90 - X-DKIM
+ 91 - DKIM-Filter
+ 92 - X-SpamExperts-Class
+ 93 - X-SpamExperts-Evidence
+ 94 - X-Recommended-Action
+ 95 - X-AppInfo
```
diff --git a/decode-spam-headers.py b/decode-spam-headers.py
index 553f47e..1735563 100644
--- a/decode-spam-headers.py
+++ b/decode-spam-headers.py
@@ -79,6 +79,14 @@
# - X-sophos-rescan
# - X-MS-Exchange-CrossTenant-Id
# - X-OriginatorOrg
+# - IronPort-Data
+# - IronPort-HdrOrdr
+# - X-DKIM
+# - DKIM-Filter
+# - X-SpamExperts-Class
+# - X-SpamExperts-Evidence
+# - X-Recommended-Action
+# - X-AppInfo
#
# Usage:
# ./decode-spam-headers [options]
@@ -91,6 +99,7 @@
# authored by: Nick Quinlan (nick@nicholasquinlan.com)
#
# Requirements:
+# - python-dateutil
# - packaging
# - dnspython
# - requests
@@ -107,13 +116,21 @@ import textwrap
import socket
import time
import base64
-from html import escape
-from dateutil import parser
+from html import escape
from email import header as emailheader
from datetime import *
from dateutil.tz import *
+try:
+ from dateutil import parser
+except ImportError:
+ print('''
+[!] You need to install python-dateutil:
+ # pip3 install python-dateutil
+''')
+ sys.exit(1)
+
try:
import packaging.version
@@ -133,7 +150,6 @@ except ImportError:
''')
sys.exit(1)
-
try:
import dns.resolver
@@ -148,12 +164,14 @@ except ImportError:
''')
sys.exit(1)
+
options = {
'debug': False,
'verbose': False,
'nocolor' : False,
'log' : sys.stderr,
'format' : 'text',
+ 'dont_resolve' : False,
}
class Logger:
@@ -373,7 +391,7 @@ class SMTPHeadersAnalysis:
'yandex', 'yandexbot', 'zillya', 'zonealarm', 'zscaler', '-sea-', 'perlmx', 'trustwave',
'mailmarshal', 'tmase', 'startscan', 'fe-etp', 'jemd', 'suspicious', 'grey', 'infected', 'unscannable',
'dlp-', 'sanitize', 'mailscan', 'barracuda', 'clearswift', 'messagelabs', 'msw-jemd', 'fe-etp', 'symc-ess',
- 'starscan', 'mailcontrol'
+ 'starscan', 'mailcontrol', 'spamexpert'
)
Interesting_Headers = (
@@ -385,7 +403,7 @@ class SMTPHeadersAnalysis:
'X-ICPINFO', 'x-locaweb-id', 'X-MC-User', 'mailersend', 'MailiGen', 'Mandrill', 'MarketoID', 'X-Messagebus-Info',
'Mixmax', 'X-PM-Message-Id', 'postmark', 'X-rext', 'responsys', 'X-SFDC-User', 'salesforce', 'x-sg-', 'x-sendgrid-',
'silverpop', '.mkt', 'X-SMTPCOM-Tracking-Number', 'X-vrfbldomain', 'verticalresponse',
- 'yesmail', 'logon', 'safelink', 'safeattach',
+ 'yesmail', 'logon', 'safelink', 'safeattach', 'appinfo',
)
Security_Appliances_And_Their_Headers = \
@@ -412,6 +430,7 @@ class SMTPHeadersAnalysis:
('MS Defender Advanced Threat Protection' , 'X-MS.+-Atp'),
('MS Defender Advanced Threat Protection - Safe Links' , '-ATPSafeLinks'),
('Cisco Advanced Malware Protection (AMP)' , 'X-Amp-'),
+ ('n-able Mail Assure (SpamExperts)' , 'SpamExperts-'),
('MS ForeFront Anti-Spam' , 'X-Microsoft-Antispam'),
('MS ForeFront Anti-Spam' , 'X-Forefront-Antispam'),
)
@@ -822,6 +841,17 @@ class SMTPHeadersAnalysis:
)
}
+ SpamExperts_Classes = {
+ 'spam' : logger.colored("system was not confident enough to block the message", "red"),
+ 'unsure' : logger.colored("system was not confident enough to block the message", "magenta"),
+ 'ham' : logger.colored("", "yellow"),
+ }
+
+ SpamExperts_Actions = {
+ 'accept' : logger.colored("Message is accepted.", "green"),
+ 'drop' : logger.colored("Message is dropped.", "red"),
+ }
+
SEA_Spam_Fields = {
'gauge' : 'Spam Message Gauge result',
'probability' : 'Spam Probability (100% - certain spam)',
@@ -1502,8 +1532,16 @@ class SMTPHeadersAnalysis:
('83', 'Office365 Tenant ID', self.testO365TenantID),
('84', 'Organization Name', self.testOrganizationIsO365Tenant),
('85', 'MS Defender For Office365 Safe Links Version',self.testSafeLinksKeyVer),
- ('86', 'Suspicious Words in Subject line', self.testSuspiciousWordsInSubject),
- ('87', 'Suspicious Words in Thread-Topic line', self.testSuspiciousWordsInThreadTopic),
+ ('87', 'AWS SES Outgoing', self.testXSESOutgoing),
+ ('88', 'IronPort-Data', self.testIronPortData),
+ ('89', 'IronPort-HdrOrder', self.testIronPortHdrOrdr),
+ ('90', 'X-DKIM', self.testXDKIM),
+ ('91', 'DKIM-Filter', self.testDKIMFilter),
+ ('92', 'X-SpamExperts-Class', self.testXSpamExpertsClass),
+ ('93', 'X-SpamExperts-Evidence', self.testXSpamExpertsEvidence),
+ ('94', 'X-Recommended-Action', self.testXRecommendedAction),
+ ('95', 'X-AppInfo', self.testXAppInfo),
+
#
# These tests shall be the last ones.
@@ -1523,6 +1561,7 @@ class SMTPHeadersAnalysis:
testsReturningArray = (
('82', 'Header Containing Client IP', self.testAnyOtherIP),
+ ('86', 'Suspicious Words in Headers', self.testSuspiciousWordsInHeaders),
)
ids = set()
@@ -1552,12 +1591,45 @@ class SMTPHeadersAnalysis:
@staticmethod
def resolveAddress(addr):
+ return SMTPHeadersAnalysis.gethostbyaddr(addr)
+
+ resolved = {}
+
+ @staticmethod
+ def gethostbyaddr(addr, important = True):
+ if not important or options['dont_resolve']:
+ return ''
+
+ if addr in SMTPHeadersAnalysis.resolved.keys():
+ return SMTPHeadersAnalysis.resolved[addr]
+
try:
res = socket.gethostbyaddr(addr)
- return ', '.join([x for x in res if len(x) > 0])
-
+ if len(res) > 0:
+ SMTPHeadersAnalysis.resolved[addr] = res[0]
+ return res[0]
except:
+ pass
+
+ return ''
+
+ @staticmethod
+ def gethostbyname(name, important = True):
+ if not important or options['dont_resolve']:
return ''
+
+ if name.lower() in SMTPHeadersAnalysis.resolved.keys():
+ return SMTPHeadersAnalysis.resolved[name]
+
+ try:
+ res = socket.gethostbyname(name)
+ if len(res) > 0:
+ SMTPHeadersAnalysis.resolved[name.lower()] = res
+ return res
+ except:
+ pass
+
+ return ''
@staticmethod
def parseExchangeVersion(lookup):
@@ -1603,6 +1675,13 @@ class SMTPHeadersAnalysis:
for (num, header, value) in self.headers:
if header.lower() == _header.lower():
+ m1 = re.search(r'\=\?[a-z0-9\-]+\?Q\?', value, re.I)
+ if m1:
+ v1d = emailheader.decode_header(value)[0][0]
+ if type(v1d) == bytes:
+ v1d = v1d.decode()
+ value = v1d
+
return (num, header, value)
similar_headers = (
@@ -1616,6 +1695,12 @@ class SMTPHeadersAnalysis:
for (num, header, value) in self.headers:
if header.lower() == _header.lower():
+ m1 = re.search(r'\=\?[a-z0-9\-]+\?Q\?', value, re.I)
+ if m1:
+ v1d = emailheader.decode_header(value)[0][0]
+ if type(v1d) == bytes:
+ v1d = v1d.decode()
+ value = v1d
return (num, header, value)
return (-1, '', '')
@@ -1837,7 +1922,7 @@ Results will be unsound. Make sure you have pasted your headers with correct spa
return ''
parts = fqdn.split('.')
- return '.'.join(parts[-2:])
+ return '.'.join(parts[-2:]).replace('<','').replace('>','')
@staticmethod
def decodeSpamcause(msg):
@@ -2366,6 +2451,82 @@ Results will be unsound. Make sure you have pasted your headers with correct spa
self.securityAppliances.add('Proofpoint Email Protection')
return self._parseProofpoint(result, '', num, header, value)
+ def testXSpamExpertsClass(self):
+ (num, header, value) = self.getHeader('X-SpamExperts-Class')
+ if num == -1: return []
+
+ result = f'- n-able Mail Assure (SpamExperts) Class: {self.logger.colored(value, "yellow")}\n'
+
+ if value.lower() in SMTPHeadersAnalysis.SpamExperts_Classes.keys():
+ result += f'\n\t- {value}: ' + SMTPHeadersAnalysis.SpamExperts_Classes[value.lower()] + '\n'
+
+ self.securityAppliances.add('n-able Mail Assure (SpamExperts)')
+
+ return {
+ 'header': header,
+ 'value' : value,
+ 'analysis' : result,
+ 'description' : '',
+ }
+
+ def testXSpamExpertsEvidence(self):
+ (num, header, value) = self.getHeader('X-SpamExperts-Evidence')
+ if num == -1: return []
+
+ result = f'- n-able Mail Assure (SpamExperts) Evidence:\n\t- {self.logger.colored(value, "magenta")}\n'
+
+ m = re.match(r'.+\s+\(([\.\d]+)\).*', value)
+ if m:
+ try:
+ score = float(m.group(1))
+ col = 'yellow'
+ msg = ''
+
+ if score < 0.5:
+ col = 'green'
+ msg = self.logger.colored('Message not quarantined and considered harmless.', col)
+
+ elif score < 0.9:
+ col = 'yellow'
+ msg = self.logger.colored('Message not quarantined but raised some suspicions', col)
+
+ else:
+ col = 'red'
+ msg = self.logger.colored('Message quarantined.', col)
+
+ result += f'\t- Score: {self.logger.colored(score, col)}\n'
+ result += f'\t- Verdict: {msg}\n'
+
+ except:
+ pass
+
+ self.securityAppliances.add('n-able Mail Assure (SpamExperts)')
+
+ return {
+ 'header': header,
+ 'value' : value,
+ 'analysis' : result,
+ 'description' : '',
+ }
+
+ def testXRecommendedAction(self):
+ (num, header, value) = self.getHeader('X-Recommended-Action')
+ if num == -1: return []
+
+ result = f'- n-able Mail Assure (SpamExperts) Recommended Action on e-mail: {self.logger.colored(value, "yellow")}\n'
+
+ if value.lower() in SMTPHeadersAnalysis.SpamExperts_Actions.keys():
+ result += f'\n\t- {value}: ' + SMTPHeadersAnalysis.SpamExperts_Actions[value.lower()] + '\n'
+
+ self.securityAppliances.add('n-able Mail Assure (SpamExperts)')
+
+ return {
+ 'header': header,
+ 'value' : value,
+ 'analysis' : result,
+ 'description' : '',
+ }
+
def testXTMVersion(self):
(num, header, value) = self.getHeader('X-TMASE-Version')
if num == -1: return []
@@ -2440,17 +2601,24 @@ Results will be unsound. Make sure you have pasted your headers with correct spa
return self._parseSpamAssassinStatus(result, '', num, header, value, thresholds)
- def testSuspiciousWordsInSubject(self):
- (num, header, value) = self.getHeader('Subject')
- if num == -1: return []
+ def testSuspiciousWordsInHeaders(self):
+ outputs = []
+ headers = set({
+ 'From', 'To', 'Subject', 'Topic',
+ })
- return self._findSuspiciousWords(num, header, value)
+ for (num, header, value) in self.headers:
+ #if header.lower().endswith('-to'): headers.add(header)
+ #if header.lower().endswith('-topic'): headers.add(header)
+ #if header.lower().endswith('-subject'): headers.add(header)
+ headers.add(header.lower())
- def testSuspiciousWordsInThreadTopic(self):
- (num, header, value) = self.getHeader('Thread-Topic')
- if num == -1: return []
+ for header in headers:
+ (num, hdr, value) = self.getHeader(header)
+ if num != -1:
+ outputs.append(self._findSuspiciousWords(num, hdr, value))
- return self._findSuspiciousWords(num, header, value)
+ return outputs
def _findSuspiciousWords(self, num, header, value):
foundWords = set()
@@ -2459,34 +2627,21 @@ Results will be unsound. Make sure you have pasted your headers with correct spa
result = ''
+ false_positives = (
+ 'unsubscribe',
+ )
+
for title, words in SMTPHeadersAnalysis.Suspicious_Words.items():
found = set()
for word in words[1]:
- if word.lower() in foundWords:
+ if word.lower() in foundWords or word.lower() in false_positives:
continue
totalChecked += 1
if re.search(r'\b' + re.escape(word) + r'\b', value, re.I):
found.add(word.lower())
-
foundWords.add(word.lower())
- pos = value.find(word.lower())
-
- if pos != -1:
- line = ''
- N = 50
- if pos > N:
- line = value[pos-N:pos]
-
- line += value[pos:pos+N]
- pos2 = line.find(word.lower())
-
- line = line[:pos2] + logger.colored(line[pos2:pos2+len(word)], "red") + line[pos2+len(word):]
- line = line.replace('\n', '')
- line = re.sub(r' {2,}', ' ', line)
-
- context += '\n' + line + '\n'
if len(found) > 0:
totalFound += len(found)
@@ -3027,6 +3182,17 @@ Results will be unsound. Make sure you have pasted your headers with correct spa
self.securityAppliances.add('Cisco IronPort')
return self._originatingIPTest(result, '', num, header, value)
+ def testXSESOutgoing(self):
+ (num, header, value) = self.getHeader('X-SES-Outgoing')
+ if num == -1: return []
+
+ result = f'- E-Mail sent through Amazon SES. Outgoing: \n\n'
+ vals = SMTPHeadersAnalysis.flattenLine(value).replace(' ', '').split('-')
+
+ result += f'\t- Date: {vals[0]}'
+
+ return self._originatingIPTest(result, '', num, header, vals[1])
+
def _originatingIPTest(self, topicLine, description, num, header, value):
result = ''
@@ -3206,6 +3372,50 @@ Results will be unsound. Make sure you have pasted your headers with correct spa
'description' : '',
}
+ def testIronPortHdrOrdr(self):
+ (num, header, value) = self.getHeader('IronPort-HdrOrdr')
+ if num == -1: return []
+
+ self.securityAppliances.add('Cisco IronPort / Email Security Appliance (ESA)')
+
+ if self.decode_all:
+ dumped = SMTPHeadersAnalysis.hexdump(SMTPHeadersAnalysis.safeBase64Decode(value))
+
+ result = f'- Cisco IronPort Data encrypted blob:\n\n'
+ result += dumped + '\n'
+
+ else:
+ result = f'- Cisco IronPort Data encrypted blob. Use --decode-all to print its hexdump.'
+
+ return {
+ 'header' : header,
+ 'value': value,
+ 'analysis' : result,
+ 'description' : '',
+ }
+
+ def testIronPortData(self):
+ (num, header, value) = self.getHeader('IronPort-Data')
+ if num == -1: return []
+
+ self.securityAppliances.add('Cisco IronPort / Email Security Appliance (ESA)')
+
+ if self.decode_all:
+ dumped = SMTPHeadersAnalysis.hexdump(SMTPHeadersAnalysis.safeBase64Decode(value))
+
+ result = f'- Cisco IronPort Data encrypted blob:\n\n'
+ result += dumped + '\n'
+
+ else:
+ result = f'- Cisco IronPort Data encrypted blob. Use --decode-all to print its hexdump.'
+
+ return {
+ 'header' : header,
+ 'value': value,
+ 'analysis' : result,
+ 'description' : '',
+ }
+
def testXIronPortSenderGroup(self):
(num, header, value) = self.getHeader('X-IronPort-SenderGroup')
if num == -1: return []
@@ -3601,7 +3811,7 @@ Src: https://www.cisco.com/c/en/us/td/docs/security/esa/esa11-1/user_guide/b_ESA
out = self._parseAsteriskRiskScore('', '', num, header, value)
headers.append(header)
values.append(value)
- SMTPHeadersAnalysis.Handled_Spam_Headers.append(header)
+ SMTPHeadersAnalysis.Handled_Spam_Headers.append(header.lower())
tmp += f'\t({num0:02}) {self.logger.colored("Header", "magenta")}: {header}\n'
tmp += out['analysis']
@@ -3651,6 +3861,7 @@ Src: https://www.cisco.com/c/en/us/td/docs/security/esa/esa11-1/user_guide/b_ESA
tmp += f'\t Keyword: {dodgy}\n'
tmp += f'\t Value: {value[:120]}\n\n'
shown.add(header)
+ SMTPHeadersAnalysis.Handled_Spam_Headers.append(header.lower())
break
elif dodgy in value.lower() and header.lower():
@@ -3675,6 +3886,7 @@ Src: https://www.cisco.com/c/en/us/td/docs/security/esa/esa11-1/user_guide/b_ESA
tmp += f'\t Keyword: {dodgy}\n'
tmp += f'\t {self.logger.colored("Value", "magenta")}:\n\n{ctx}\n\n'
shown.add(header)
+ SMTPHeadersAnalysis.Handled_Spam_Headers.append(header.lower())
break
if len(tmp) > 0:
@@ -3809,7 +4021,7 @@ Src: https://www.cisco.com/c/en/us/td/docs/security/esa/esa11-1/user_guide/b_ESA
if num == -1: return []
result = ''
- m = re.search(r'<([^<@\s]+)@([^\s]+)>', value)
+ m = re.search(r'([^<@\s]+)@([^\s]+)>?', value)
domain = ''
if m and len(self.received_path) < 3:
@@ -3827,16 +4039,16 @@ Src: https://www.cisco.com/c/en/us/td/docs/security/esa/esa11-1/user_guide/b_ESA
firstSenderAddr = ''
try:
- mailDomainAddr = socket.gethostbyname(domain)
- revMailDomain = socket.gethostbyaddr(mailDomainAddr)[0]
+ mailDomainAddr = SMTPHeadersAnalysis.gethostbyname(domain)
+ revMailDomain = SMTPHeadersAnalysis.gethostbyaddr(mailDomainAddr)
if(len(firstHop['ip'])) > 0 and len(revFirstSenderDomain) == 0:
- revFirstSenderDomain = socket.gethostbyaddr(firstHop['ip'])[0]
+ revFirstSenderDomain = SMTPHeadersAnalysis.gethostbyaddr(firstHop['ip'])
if(len(firstHop['host'])) > 0:
- firstSenderAddr = socket.gethostbyname(firstHop['host'])
+ firstSenderAddr = SMTPHeadersAnalysis.gethostbyname(firstHop['host'])
if len(revFirstSenderDomain) == 0:
- revFirstSenderDomain = socket.gethostbyaddr(firstSenderAddr)[0]
+ revFirstSenderDomain = SMTPHeadersAnalysis.gethostbyaddr(firstSenderAddr)
except:
pass
@@ -3846,6 +4058,9 @@ Src: https://www.cisco.com/c/en/us/td/docs/security/esa/esa11-1/user_guide/b_ESA
if len(senderDomain) == 0: senderDomain = domain
if len(firstHopDomain1) == 0: firstHopDomain1 = firstHop["host"]
+ senderDomain = senderDomain.replace('<','').replace('>','').strip()
+ firstHopDomain1 = firstHopDomain1.replace('<','').replace('>','').strip()
+
result += f'\t- Mail From: <{email}>\n\n'
result += f'\t- Mail Domain: {domain}\n'
result += f'\t --> resolves to: {mailDomainAddr}\n'
@@ -3863,7 +4078,7 @@ Src: https://www.cisco.com/c/en/us/td/docs/security/esa/esa11-1/user_guide/b_ESA
if domain.endswith('.'): domain = domain[:-1]
response = dns.resolver.resolve(domain, 'TXT')
- except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer) as e:
+ except Exception as e:
response = []
spf = False
@@ -4145,9 +4360,9 @@ Src: https://www.cisco.com/c/en/us/td/docs/security/esa/esa11-1/user_guide/b_ESA
if obj['host'] == '' and obj['ip'] != '':
try:
- res = socket.gethostbyaddr(obj['ip'])
+ res = SMTPHeadersAnalysis.gethostbyaddr(obj['ip'])
if len(res) > 0:
- obj['host'] = res[0]
+ obj['host'] = res
except:
pass
@@ -4160,9 +4375,9 @@ Src: https://www.cisco.com/c/en/us/td/docs/security/esa/esa11-1/user_guide/b_ESA
if len(obj['host2']) == 0:
if obj['ip'] != None and len(obj['ip']) > 0:
try:
- res = socket.gethostbyaddr(obj['ip'])
+ res = SMTPHeadersAnalysis.gethostbyaddr(obj['ip'])
if len(res) > 0:
- obj['host2'] = res[0]
+ obj['host2'] = res
except:
obj['host2'] = self.logger.colored('NXDomain', 'red')
@@ -4270,7 +4485,7 @@ Src: https://www.cisco.com/c/en/us/td/docs/security/esa/esa11-1/user_guide/b_ESA
if obj and (obj['ip'] == None or len(obj['ip']) == 0):
if obj['host'] != None and len(obj['host']) > 0:
try:
- obj['ip'] = socket.gethostbyname(obj['host'])
+ obj['ip'] = SMTPHeadersAnalysis.gethostbyname(obj['host'])
except:
pass
@@ -4867,6 +5082,38 @@ Src: https://www.cisco.com/c/en/us/td/docs/security/esa/esa11-1/user_guide/b_ESA
'description' : '',
}
+ def testXDKIM(self):
+ (num, header, value) = self.getHeader('X-DKIM')
+ if num == -1: return []
+
+ vvv = self.logger.colored(value, 'magenta')
+ self.securityAppliances.add(value)
+ result = f'- X-DKIM header was present and contained value: {vvv}\n'
+ result + ' This header typically indicates DKIM verification filter version.'
+
+ return {
+ 'header' : header,
+ 'value': value,
+ 'analysis' : result,
+ 'description' : '',
+ }
+
+ def testDKIMFilter(self):
+ (num, header, value) = self.getHeader('DKIM-Filter')
+ if num == -1: return []
+
+ vvv = self.logger.colored(value, 'magenta')
+ self.securityAppliances.add(value)
+ result = f'- DKIM-Filter header was present and contained value: {vvv}\n'
+ result + ' This header typically indicates DKIM verification filter version.'
+
+ return {
+ 'header' : header,
+ 'value': value,
+ 'analysis' : result,
+ 'description' : '',
+ }
+
def testXMailer(self):
(num, header, value) = self.getHeader('X-Mailer')
if num == -1: return []
@@ -4883,6 +5130,22 @@ Src: https://www.cisco.com/c/en/us/td/docs/security/esa/esa11-1/user_guide/b_ESA
'description' : '',
}
+ def testXAppInfo(self):
+ (num, header, value) = self.getHeader('X-AppInfo')
+ if num == -1: return []
+
+ vvv = self.logger.colored(value, 'magenta')
+ self.securityAppliances.add(value)
+ result = f'- X-AppInfo header was present and contained value: {vvv}\n'
+ result + ' This header typically indicates sending client\'s name (similar to User-Agent).'
+
+ return {
+ 'header' : header,
+ 'value': value,
+ 'analysis' : result,
+ 'description' : '',
+ }
+
def testUserAgent(self):
(num, header, value) = self.getHeader('User-Agent')
if num == -1: return []
@@ -5162,10 +5425,7 @@ Src: https://www.cisco.com/c/en/us/td/docs/security/esa/esa11-1/user_guide/b_ESA
if self.resolve:
self.logger.dbg(f'testResolveIntoIP: Resolving {d}...')
- out = socket.gethostbyname(d)
-
- if type(out) == list:
- out = out[0]
+ out = SMTPHeadersAnalysis.gethostbyname(d)
tmp += f'\t- Found Domain: {d2}\n\t\t- that resolves to: {out}\n'
else:
@@ -5239,6 +5499,7 @@ def opts(argv):
tst.add_argument('-i', '--include-tests', default='', metavar='tests', help='Comma-separated list of test IDs to run. Ex. --include-tests 1,3,7')
tst.add_argument('-e', '--exclude-tests', default='', metavar='tests', help='Comma-separated list of test IDs to skip. Ex. --exclude-tests 1,3,7')
tst.add_argument('-r', '--resolve', default=False, action='store_true', help='Resolve IPv4 addresses / Domain names.')
+ tst.add_argument('-R', '--dont-resolve', default=False, action='store_true', help='Do not resolve anything.')
tst.add_argument('-a', '--decode-all', default=False, action='store_true', help='Decode all =?us-ascii?Q? mail encoded messages and print their contents.')
args = o.parse_args()
@@ -5356,9 +5617,10 @@ def main(argv):
an = SMTPHeadersAnalysis(logger)
(a, b, c) = an.getAllTests()
- tests = a+b+c
+ d = a+b+c
+ e = [x for x in sorted(d, key=lambda item: int(item[0]))]
- for test in tests:
+ for test in e:
(testId, testName, testFunc) = test
if test in b:
@@ -5437,6 +5699,5 @@ Experiencing a bad-looking output with unprintable characters?
Use -N flag to disable console colors, or switch your console for better UI experience.
''')
-
if __name__ == '__main__':
main(sys.argv)