-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathautomation-CheckEmailAuthenticity.yml
327 lines (295 loc) · 12.4 KB
/
automation-CheckEmailAuthenticity.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
args:
- auto: PREDEFINED
description: A list of dictionaries of headers in the form of "Header name":"Header
value".
isArray: true
name: headers
predefined:
- admin
- description: Override value for SPF=None. Possible values are "Fail", "Suspicious",
and "Undetermined"). / Pass
name: SPF_override_none
- description: Override value for SPF=neutral. Possible values are "Fail", "Suspicious",
and "Undetermined"). / Pass
name: SPF_override_neutral
- description: Override value for SPF=pass. Possible values are "Fail", "Suspicious",
and "Undetermined"). / Pass
name: SPF_override_pass
- description: Override value for SPF=fail. Possible values are "Fail", "Suspicious",
and "Undetermined"). /Pass
name: SPF_override_fail
- description: Override value for SPF=softfail. Possible values are "Fail", "Suspicious",
and "Undetermined"). /Pass
name: SPF_override_softfail
- description: Override value for SPF=temperror. Possible values are "Fail", "Suspicious",
and "Undetermined"). /Pass
name: SPF_override_temperror
- description: Override value for SPF=permerror. Possible values are "Fail", "Suspicious",
and "Undetermined"). /Pass
name: SPF_override_permerror
- description: Override value for DKIM=none. Possible values are "Fail", "Suspicious",
and "Undetermined"). /Pass
name: DKIM_override_none
- description: Override value for DKIM=pass. Possible values are "Fail", "Suspicious",
and "Undetermined"). /Pass
name: DKIM_override_pass
- description: Override value for DKIM=fail. Possible values are "Fail", "Suspicious",
and "Undetermined"). /Pass
name: DKIM_override_fail
- description: Override value for DKIM=policy. Possible values are "Fail", "Suspicious",
and "Undetermined"). /Pass
name: DKIM_override_policy
- description: Override value for DKIM=neutral. Possible values are "Fail", "Suspicious",
and "Undetermined"). /Pass
name: DKIM_override_neutral
- description: Override value for DKIM=temperror. Possible values are "Fail", "Suspicious",
and "Undetermined"). /Pass
name: DKIM_override_temperror
- description: Override value for DKIM=permerror. Possible values are "Fail", "Suspicious",
and "Undetermined"). /Pass
name: DKIM_override_permerror
- description: Override value for DMARC=none. Possible values are "Fail", "Suspicious",
and "Undetermined"). /Pass
name: DMARC_override_none
- description: Override value for DMARC=pass. Possible values are "Fail", "Suspicious",
and "Undetermined"). /Pass
name: DMARC_override_pass
- description: Override value for DMARC=fail. Possible values are "Fail", "Suspicious",
and "Undetermined"). /Pass
name: DMARC_override_fail
- description: Override value for DMARC=temperror. Possible values are "Fail", "Suspicious",
and "Undetermined"). /Pass
name: DMARC_override_temperror
- description: Override value for DMARC=permerror. Possible values are "Fail", "Suspicious",
and "Undetermined"). /Pass
name: DMARC_override_permerror
comment: Checks the authenticity of an email based on the email's SPF, DMARC, and
DKIM.
commonfields:
id: CheckEmailAuthenticity
version: -1
enabled: true
name: CheckEmailAuthenticity
outputs:
- contextPath: Email.SPF.MessageID
description: SPF ID
type: String
- contextPath: Email.SPF.Validation-Result
description: 'Validation Result. Possible values are "None", "Neutral", "Pass",
"Fail", "SoftFail", "TempError", and "PermError". '
type: String
- contextPath: Email.SPF.Reason
description: Reason for the SPF result, which is located in the headers of the email.
type: String
- contextPath: Email.SPF.Sender-IP
description: Email sender IP address.
type: String
- contextPath: Email.DKIM.Message-ID
description: Validation result. Possible values are "None", "Pass", "Fail", "Policy",
"Neutral", "Temperror", and "Permerror".
type: String
- contextPath: Email.DKIM.Reason
description: DKIM reason (if found).
type: String
- contextPath: Email.DMARC.Message-ID
description: DMARC ID.
type: String
- contextPath: Email.DMARC.Validation-Result
description: DMARC reason. Possible values are "None", "Pass", "Fail", "Temperror",
and "Permerror".
type: String
- contextPath: Email.DMARC.Tags
description: DMARC Tags (if found)
type: String
- contextPath: Email.DMARC.From-Domain
description: Sender's Domain
type: String
- contextPath: Email.DKIM.Signing-Domain
description: Sender's Domain
type: String
- contextPath: Email.AuthenticityCheck
description: 'Possible values are be: Fail / Suspicious / Undetermined / Pass'
type: Unknown
- contextPath: Email.DKIM
description: DKIM information extracted from the email.
type: Unknown
- contextPath: Email.SPF
description: SPF information extracted from the email.
type: Unknown
- contextPath: Email.DMARC
description: DMARC information extracted from the email.
type: Unknown
runas: DBotWeakRole
runonce: false
script: |2-
import re
import traceback
'''HELPER FUNCTIONS'''
def get_spf(auth, spf):
"""
Get SPF validation information
:param auth: authentication header value (if exist), contains the validation result and sender ip.
:param spf: spf header value (if exist), contains the validation result and sender ip.
:return: SPF validation information
"""
spf_context = {}
if auth is None:
spf_context['Validation-Result'] = spf.split(' ')[0].lower()
sender_ip = re.findall(r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', spf)
else:
result = re.search(r'spf=(\w+)', auth)
if result is not None:
spf_context['Validation-Result'] = result.group(1).lower()
sender_ip = re.findall(r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', auth)
if sender_ip:
spf_context['Sender-IP'] = sender_ip[0]
if spf is not None:
spf_context['Reason'] = re.findall(r'\((.+)\)', spf)[0]
return spf_context
def get_dkim(auth):
"""
Get DKIM validation information
:param auth: authentication header value (if exist), contains the validation result.
:return: DKIM validation information
"""
dkim_context = {}
if auth is not None:
result = re.search(r'dkim=(\w+)', auth)
if result is not None:
dkim_context['Validation-Result'] = result.group(1).lower()
reason = re.search(r'dkim=\w+ \((.+?)\)', auth)
if reason is not None:
dkim_context['Reason'] = reason.group(1)
domain = re.findall(r'dkim=[\w\W]+?[=@](\w+\.[^ ]+)', auth)
if domain:
dkim_context['Signing-Domain'] = domain[0]
return dkim_context
def get_dmarc(auth):
"""
Get DMARC validation information
:param auth: authentication header value (if exist), contains the validation result and sender ip.
:return: DMARC validation information
"""
dmarc_context = {}
if auth is not None:
result = re.search(r'dmarc=(\w+)', auth)
if result is not None:
dmarc_context['Validation-Result'] = result.group(1).lower()
reason = re.findall(r'dmarc=\w+ \((.+?)\)', auth)
if reason:
tags = reason[0]
tags_data = {}
for tag in tags.split(' '):
values = tag.split('=')
tags_data[values[0]] = values[1]
dmarc_context['Tags'] = tags_data
domain = re.findall(r'dmarc=[\w\W]+header.from=(\w+\.[^ ]+)', auth)
if domain:
dmarc_context['Signing-Domain'] = domain[0]
return dmarc_context
def auth_check(spf_data, dkim_data, dmarc_data, override_dict):
spf = spf_data.get('Validation-Result')
dmarc = dmarc_data.get('Validation-Result')
dkim = dkim_data.get('Validation-Result')
if 'spf-{}'.format(spf) in override_dict:
return override_dict.get('spf-{}'.format(spf))
if 'dkim-{}'.format(dkim) in override_dict:
return override_dict.get('dkim-{}'.format(dkim))
if 'dmarc-{}'.format(dmarc) in override_dict:
return override_dict.get('dmarc-{}'.format(dmarc))
if 'fail' in [spf, dkim, dmarc]:
return 'Fail'
if spf == 'softfail' or dkim == 'policy':
return 'Suspicious'
undetermined = [None, 'none', 'temperror', 'permerror']
if dmarc in undetermined or spf in undetermined or dkim in undetermined \
or dkim == 'neutral':
return 'Undetermined'
return 'Pass'
'''MAIN FUNCTION'''
def main():
try:
args = demisto.args()
headers = argToList(demisto.args().get('headers'))
auth = None
spf = None
message_id = ''
# getting override options from user
override_dict = {}
override_options = ['fail', 'suspicious', 'undetermined', 'pass', 'Fail', 'Suspicious', 'Undetermined', 'Pass']
override_fields = {
'SPF_override_none': 'spf-none',
'SPF_override_neutral': 'spf-neutral',
'SPF_override_pass': 'spf-pass',
'SPF_override_fail': 'spf-fail',
'SPF_override_softfail': 'spf-softfail',
'SPF_override_temperror': 'spf-temperror',
'SPF_override_perm': 'spf-permerror',
'DKIM_override_none': 'dkim-none',
'DKIM_override_pass': 'dkim-pass',
'DKIM_override_fail': 'dkim-fail',
'DKIM_override_policy': 'dkim-policy',
'DKIM_override_neutral': 'dkim-neutral',
'DKIM_override_temperror': 'dkim-temperror',
'DKIM_override_permerror': 'dkim-permerror',
'DMARC_override_none': 'dmarc-none',
'DMARC_override_pass': 'dmarc-pass',
'DMARC_override_fail': 'dmarc-fail',
'DMARC_override_temperror': 'dmarc-temperror',
'DMARC_override_permerror': 'dmarc-permerror',
}
for field, value in override_fields.items():
override = args.get(field)
if override in override_options:
override_dict[value] = override.lower()
else:
if override is not None:
return_error('Invalid override input for argument {}: got {}, expected one of {}.'.format(
field,
override,
override_options
))
for header in headers:
if isinstance(header, dict):
if header.get('name') == 'Authentication-Results':
auth = header.get('value')
if header.get('name') == 'Received-SPF':
spf = header.get('value')
if header.get('name') == 'Message-ID':
message_id = header.get('value') # type: ignore
email_key = "Email(val.Headers.filter(function(header) {{ return header && header.name === 'Message-ID' && " \
"header.value === '{}';}}))".format(message_id)
if auth is None and spf is None:
context = {
'{}.AuthenticityCheck'.format(email_key): 'undetermined'
}
return_outputs('No header information was found.', context)
sys.exit(0)
spf_data = get_spf(auth, spf)
dkim_data = get_dkim(auth)
dmarc_data = get_dmarc(auth)
authenticity = auth_check(spf_data, dkim_data, dmarc_data, override_dict)
md = "Email's authenticity is: **{}**\n".format(authenticity)
md += tableToMarkdown('SPF', spf_data, ['Validation-Result', 'Reason', 'Sender-IP'])
md += tableToMarkdown('DKIM', dkim_data, ['Validation-Result', 'Reason', 'Signing-Domain'])
md += tableToMarkdown('DMARC', dmarc_data, ['Validation-Result', 'Tags', 'Signing-Domain'])
ec = {
'{}.SPF'.format(email_key): spf_data,
'{}.DMARC'.format(email_key): dmarc_data,
'{}.DKIM'.format(email_key): dkim_data,
'{}.AuthenticityCheck'.format(email_key): authenticity
}
return_outputs(md, ec)
except Exception as ex:
demisto.error(str(ex) + '\n\nTrace:\n' + traceback.format_exc())
return_error(str(ex))
# python2 uses __builtin__ python3 uses builtins
if __name__ == '__builtin__' or __name__ == 'builtins':
main()
scripttarget: 0
system: true
tags:
- phishing
- ews
- email
type: python