mail_to_misp/mail_to_misp.py

271 lines
8.8 KiB
Python
Raw Normal View History

2017-06-01 17:00:32 +02:00
#!/usr/bin/env python3
2017-05-24 11:02:28 +02:00
# -*- coding: utf-8 -*-
2017-04-27 13:58:49 +02:00
2017-05-31 16:47:08 +02:00
import os
2017-06-01 09:02:11 +02:00
import sys
try:
configfile = os.path.basename(sys.argv[0]).split(".py")[0] + "_config"
except Exception as e:
print("Couldn't locate config file {0}".format(configfile))
sys.exit(-1)
try:
import urlmarker
import hashmarker
import re
from pyfaup.faup import Faup
from pymisp import PyMISP
from defang import refang
import dns.resolver
import email
from email.generator import Generator
import tempfile
import socket
import syslog
import ftfy
config = __import__(configfile)
except ImportError as e:
print("(!) Problem loading module:")
print(e)
sys.exit(-1)
2017-05-30 11:24:30 +02:00
syslog.openlog(logoption=syslog.LOG_PID, facility=syslog.LOG_USER)
2017-05-24 15:44:14 +02:00
def is_valid_ipv4_address(address):
try:
socket.inet_pton(socket.AF_INET, address)
except AttributeError: # no inet_pton here, sorry
try:
socket.inet_aton(address)
except socket.error:
return False
return address.count('.') == 3
except socket.error: # not a valid address
return False
return True
def is_valid_ipv6_address(address):
try:
socket.inet_pton(socket.AF_INET6, address)
except socket.error: # not a valid address
return False
return True
2017-04-27 13:58:49 +02:00
debug = config.debug
2017-05-23 15:09:25 +02:00
stdin_used = False
2017-05-30 11:24:30 +02:00
email_subject = config.email_subject_prefix
2017-05-23 15:09:25 +02:00
try:
2017-05-24 15:50:05 +02:00
if not sys.stdin.isatty():
mailcontent = sys.stdin.buffer.read().decode("utf-8", "ignore")
2017-06-01 16:15:39 +02:00
else:
mailcontent = sys.argv[1]
if len(sys.argv) >= 3:
2017-06-01 17:00:32 +02:00
mail_subject = sys.argv[2]
2017-06-01 16:15:39 +02:00
email_data = b''
syslog.syslog(mailcontent)
msg = email.message_from_string(mailcontent)
mail_subject = msg.get('Subject').encode("utf-8", "ignore")
for part in msg.walk():
if part.get_content_charset() is None:
# This could probably be detected
charset = 'utf-8'
else:
charset = part.get_content_charset()
if part.get_content_maintype() == 'multipart':
continue
if part.get_content_maintype() == 'text':
part.set_charset(charset)
email_data += part.get_payload(decode=True)
2017-06-01 17:00:32 +02:00
print("here")
2017-06-01 16:15:39 +02:00
email_subject += mail_subject
stdin_used = True
except Exception as e:
2017-04-27 13:58:49 +02:00
if debug:
2017-05-30 11:24:30 +02:00
syslog.syslog("FATAL ERROR: Not all required input received")
2017-06-01 17:00:32 +02:00
print(str(e))
2017-06-01 16:15:39 +02:00
syslog.syslog(str(e))
2017-04-27 13:58:49 +02:00
sys.exit(1)
2017-06-01 12:16:32 +02:00
#if debug:
# syslog.syslog("Encoding of subject: {0}".format(ftfy.guess_bytes(email_subject)[1]))
# syslog.syslog("Encoding of body: {0}".format(ftfy.guess_bytes(email_data)[1]))
2017-06-01 12:16:32 +02:00
try:
email_data = ftfy.fix_text(email_data.decode("utf-8", "ignore"))
except:
email_data = ftfy.fix_text(email_data)
try:
email_subject = ftfy.fix_text(email_subject.decode("utf-8", "ignore"))
except:
email_subject = ftfy.fix_text(email_subject)
2017-04-27 13:58:49 +02:00
if debug:
syslog.syslog(email_subject)
syslog.syslog(email_data)
2017-04-27 13:58:49 +02:00
misp_url = config.misp_url
misp_key = config.misp_key
misp_verifycert = config.misp_verifycert
resolver = dns.resolver.Resolver(configure=False)
resolver.nameservers = config.nameservers
excludelist = config.excludelist
externallist = config.externallist
2017-05-29 17:06:46 +02:00
internallist = config.internallist
2017-04-28 10:00:58 +02:00
noidsflaglist = config.noidsflaglist
ignorelist = config.ignorelist
removelist = config.removelist
2017-04-27 13:58:49 +02:00
malwaretags = config.malwaretags
dependingtags = config.dependingtags
tlptag_default = config.tlptag_default
2017-05-17 09:51:47 +02:00
stopword = config.stopword
hash_only_tags = config.hash_only_tags
2017-05-29 15:35:56 +02:00
forward_identifiers = config.forward_identifiers
2017-05-31 15:45:53 +02:00
attach_original_mail = config.attach_original_mail
2017-04-27 13:58:49 +02:00
original_email_data = email_data
2017-04-27 13:58:49 +02:00
# Ignore lines in body of message
for ignoreline in ignorelist:
email_data = re.sub(ignoreline, "", email_data)
2017-04-27 13:58:49 +02:00
# Remove words from subject
for removeword in removelist:
email_subject = re.sub(removeword, "", email_subject)
2017-04-27 13:58:49 +02:00
def init(url, key):
return PyMISP(url, key, misp_verifycert, 'json')
# Evaluate classification
tlp_tag = tlptag_default
2017-04-27 13:58:49 +02:00
tlptags = config.tlptags
for tag in tlptags:
for alternativetag in tlptags[tag]:
if alternativetag in email_data.lower():
2017-04-27 13:58:49 +02:00
tlp_tag = tag
# Create the MISP event
misp = init(misp_url, misp_key)
new_event = misp.new_event(info=email_subject, distribution=0, threat_level_id=3, analysis=1)
2017-04-27 13:58:49 +02:00
misp.add_tag(new_event, tlp_tag)
2017-05-31 15:45:53 +02:00
if attach_original_mail and original_email_data:
misp.add_named_attribute(new_event, 'email-body', original_email_data, category='Payload delivery', to_ids=False)
2017-04-27 13:58:49 +02:00
# Add additional tags depending on others
for tag in dependingtags:
if tag in tlp_tag:
for dependingtag in dependingtags[tag]:
misp.add_tag(new_event, dependingtag)
2017-05-29 15:35:56 +02:00
## Prepare extraction of IOCs
# Limit the input if the stopword is found
2017-05-17 09:51:47 +02:00
email_data = email_data.split(stopword, 1)[0]
2017-05-29 15:35:56 +02:00
# Find the first forwarding message and use that content
position = 99999
t_email_data = email_data
for identifier in forward_identifiers:
new_position = email_data.find(identifier)
2017-05-29 17:06:46 +02:00
if new_position == -1:
new_position = position
2017-05-29 15:35:56 +02:00
if new_position < position:
t_before, t_split, t_email_data = email_data.partition(identifier)
position = new_position
email_data = t_email_data
# Refang email data
email_data = refang(email_data)
2017-05-29 15:35:56 +02:00
## Extract various IOCs
2017-05-24 15:44:14 +02:00
urllist = list()
urllist += re.findall(urlmarker.WEB_URL_REGEX, email_data)
2017-04-28 10:00:58 +02:00
urllist += re.findall(urlmarker.IP_REGEX, email_data)
2017-04-27 13:58:49 +02:00
if debug:
2017-05-30 11:24:30 +02:00
syslog.syslog(str(urllist))
2017-04-27 13:58:49 +02:00
# Init Faup
f = Faup()
2017-04-27 17:50:50 +02:00
# Add tags according to configuration
2017-04-27 13:58:49 +02:00
for malware in malwaretags:
if malware in email_subject.lower():
2017-04-27 13:58:49 +02:00
for tag in malwaretags[malware]:
misp.add_tag(new_event, tag)
2017-04-28 10:00:58 +02:00
# Extract and add hashes
hashlist_md5 = re.findall(hashmarker.MD5_REGEX, email_data)
hashlist_sha1 = re.findall(hashmarker.SHA1_REGEX, email_data)
hashlist_sha256 = re.findall(hashmarker.SHA256_REGEX, email_data)
for h in hashlist_md5:
misp.add_hashes(new_event, md5=h)
for h in hashlist_sha1:
misp.add_hashes(new_event, sha1=h)
for h in hashlist_sha256:
misp.add_hashes(new_event, sha256=h)
if (len(hashlist_md5) > 0) or (len(hashlist_sha1) > 0) or (len(hashlist_sha256) > 0):
for tag in hash_only_tags:
misp.add_tag(new_event, tag)
2017-04-27 13:58:49 +02:00
# Add IOCs and expanded information to MISP
for entry in urllist:
2017-04-28 10:00:58 +02:00
ids_flag = True
2017-04-27 13:58:49 +02:00
f.decode(entry)
domainname = f.get_domain().decode('utf-8', 'ignore')
hostname = f.get_host().decode('utf-8', 'ignore')
2017-04-27 13:58:49 +02:00
if debug:
syslog.syslog(domainname)
2017-04-27 13:58:49 +02:00
if domainname not in excludelist:
2017-05-29 17:06:46 +02:00
if domainname in internallist:
misp.add_named_attribute(new_event, 'link', entry, category='Internal reference', to_ids=False, distribution=0)
elif domainname in externallist:
2017-05-08 15:47:47 +02:00
misp.add_named_attribute(new_event, 'link', entry, category='External analysis', to_ids=False)
2017-04-27 13:58:49 +02:00
else:
2017-04-28 10:00:58 +02:00
if (domainname in noidsflaglist) or (hostname in noidsflaglist):
ids_flag = False
2017-04-27 13:58:49 +02:00
if debug:
2017-05-30 11:24:30 +02:00
syslog.syslog(str(entry))
2017-05-24 15:44:14 +02:00
if hostname:
2017-05-24 16:39:21 +02:00
if is_valid_ipv4_address(entry):
misp.add_url(new_event, entry, category='Network activity', to_ids=False)
else:
misp.add_url(new_event, entry, category='Network activity', to_ids=ids_flag)
2017-04-27 13:58:49 +02:00
if debug:
syslog.syslog(hostname)
2017-05-24 15:44:14 +02:00
port = f.get_port()
comment = ""
if port:
comment = "on port: " + str(port)
if is_valid_ipv4_address(hostname):
misp.add_ipdst(new_event, hostname, comment=comment, category='Network activity', to_ids=False)
2017-05-24 15:44:14 +02:00
else:
misp.add_hostname(new_event, hostname, comment=comment, category='Network activity', to_ids=ids_flag)
2017-05-24 15:44:14 +02:00
try:
for rdata in dns.resolver.query(hostname, 'A'):
2017-05-24 15:44:14 +02:00
if debug:
2017-05-30 11:24:30 +02:00
syslog.syslog(str(rdata))
misp.add_ipdst(new_event, rdata.to_text(), category='Network activity', to_ids=False, comment=hostname)
2017-05-24 15:44:14 +02:00
except Exception as e:
if debug:
2017-05-31 16:47:08 +02:00
syslog.syslog(str(e))
2017-05-24 11:02:28 +02:00
# Try to add attachments
if stdin_used:
for part in msg.walk():
if part.get_content_maintype() == 'multipart':
continue
if part.get_content_maintype() != 'text':
filename = part.get_filename()
_, output_path = tempfile.mkstemp()
output = open(output_path, 'wb')
output.write(part.get_payload(decode=True))
misp.upload_sample(filename, output_path, new_event, distribution=None, to_ids=True, category=None, comment=None, info='My Info', analysis=None, threat_level_id=None)
2017-05-24 11:02:28 +02:00
output.close()
syslog.syslog("Job finished.")