2014-12-10 02:19:26 +03:00
|
|
|
#!/usr/bin/env python
|
|
|
|
|
|
|
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
# Copyright (c) 2014 Mozilla Corporation
|
|
|
|
|
|
|
|
import json
|
|
|
|
import logging
|
|
|
|
import os
|
|
|
|
import re
|
|
|
|
import sys
|
|
|
|
from datetime import datetime
|
|
|
|
from configlib import getConfig, OptionParser
|
|
|
|
from logging.handlers import SysLogHandler
|
|
|
|
from hashlib import md5
|
|
|
|
|
2016-10-18 00:48:02 +03:00
|
|
|
import sys
|
|
|
|
import os
|
2018-10-16 22:45:04 +03:00
|
|
|
from mozdef_util.utilities.toUTC import toUTC
|
|
|
|
from mozdef_util.elasticsearch_client import ElasticsearchClient, ElasticsearchBadServer
|
|
|
|
from mozdef_util.query_models import SearchQuery, TermMatch, PhraseMatch
|
2016-10-18 00:48:02 +03:00
|
|
|
|
2014-12-10 02:19:26 +03:00
|
|
|
logger = logging.getLogger(sys.argv[0])
|
|
|
|
|
|
|
|
|
|
|
|
def loggerTimeStamp(self, record, datefmt=None):
|
|
|
|
return toUTC(datetime.now()).isoformat()
|
|
|
|
|
|
|
|
|
|
|
|
def initLogger():
|
|
|
|
logger.level = logging.INFO
|
|
|
|
formatter = logging.Formatter(
|
|
|
|
'%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
|
|
formatter.formatTime = loggerTimeStamp
|
|
|
|
if options.output == 'syslog':
|
|
|
|
logger.addHandler(
|
|
|
|
SysLogHandler(address=(options.sysloghostname,
|
|
|
|
options.syslogport)))
|
|
|
|
else:
|
|
|
|
sh = logging.StreamHandler(sys.stderr)
|
|
|
|
sh.setFormatter(formatter)
|
|
|
|
logger.addHandler(sh)
|
|
|
|
|
|
|
|
|
|
|
|
def getDocID(usermacaddress):
|
|
|
|
# create a hash to use as the ES doc id
|
|
|
|
hash = md5()
|
|
|
|
hash.update('{0}.mozdefintel.usernamemacaddress'.format(usermacaddress))
|
|
|
|
return hash.hexdigest()
|
|
|
|
|
|
|
|
|
|
|
|
def readOUIFile(ouifilename):
|
|
|
|
'''
|
|
|
|
Expects the OUI file from IEEE:
|
|
|
|
wget http://www.ieee.org/netstorage/standards/oui.txt
|
|
|
|
Reads the (hex) line and extracts the hex prefix and the vendor name
|
|
|
|
to store as part of the intelligence record about what device the user
|
|
|
|
was seen using.
|
|
|
|
'''
|
|
|
|
ouifile=open(ouifilename)
|
|
|
|
macassignments={}
|
|
|
|
for i in ouifile.readlines()[0::]:
|
|
|
|
i=i.strip()
|
|
|
|
if '(hex)' in i:
|
|
|
|
#print(i)
|
|
|
|
fields=i.split('\t')
|
|
|
|
macprefix=fields[0][0:8].replace('-',':').lower()
|
|
|
|
entity=fields[2]
|
|
|
|
macassignments[macprefix]=entity
|
|
|
|
return macassignments
|
|
|
|
|
|
|
|
|
2016-10-25 00:17:31 +03:00
|
|
|
def esSearch(es, macassignments=None):
|
2014-12-10 02:19:26 +03:00
|
|
|
'''
|
|
|
|
Search ES for an event that ties a username to a mac address
|
|
|
|
This example searches for junos wifi correlations on authentication success
|
|
|
|
Expecting an event like: user: username@somewhere.com; mac: 5c:f9:38:b1:de:cf; author reason: roamed session; ssid: ANSSID; AP 46/2\n
|
|
|
|
'''
|
|
|
|
usermacre=re.compile(r'''user: (?P<username>.*?); mac: (?P<macaddress>.*?); ''',re.IGNORECASE)
|
|
|
|
correlations={} # list of dicts to populate hits we find
|
|
|
|
|
2016-10-25 00:17:31 +03:00
|
|
|
search_query = SearchQuery(minutes=options.correlationminutes)
|
2017-01-12 00:49:43 +03:00
|
|
|
search_query.add_must(TermMatch('details.program', 'AUTHORIZATION-SUCCESS'))
|
2016-10-25 00:17:31 +03:00
|
|
|
search_query.add_must_not(PhraseMatch('summary', 'last-resort'))
|
|
|
|
|
2014-12-10 02:19:26 +03:00
|
|
|
try:
|
2016-10-25 00:17:31 +03:00
|
|
|
full_results = search_query.execute(es)
|
|
|
|
results = full_results['hits']
|
|
|
|
|
|
|
|
for r in results:
|
2014-12-10 02:19:26 +03:00
|
|
|
fields = re.search(usermacre,r['_source']['summary'])
|
|
|
|
if fields:
|
|
|
|
if '{0} {1}'.format(fields.group('username'),fields.group('macaddress')) not in correlations.keys():
|
|
|
|
if fields.group('macaddress')[0:8].lower() in macassignments.keys():
|
|
|
|
entity=macassignments[fields.group('macaddress')[0:8].lower()]
|
|
|
|
else:
|
|
|
|
entity='unknown'
|
|
|
|
correlations['{0} {1}'.format(fields.group('username'),fields.group('macaddress'))]=dict(username=fields.group('username'),
|
|
|
|
macaddress=fields.group('macaddress'),
|
|
|
|
entity=entity,
|
|
|
|
utctimestamp=r['_source']['utctimestamp'])
|
|
|
|
return correlations
|
2016-10-18 00:48:02 +03:00
|
|
|
|
2016-10-25 00:17:31 +03:00
|
|
|
except ElasticsearchBadServer:
|
2014-12-10 02:19:26 +03:00
|
|
|
logger.error('Elastic Search server could not be reached, check network connectivity')
|
|
|
|
|
|
|
|
def esStoreCorrelations(es, correlations):
|
|
|
|
for c in correlations:
|
|
|
|
event=dict(
|
|
|
|
utctimestamp=correlations[c]['utctimestamp'],
|
|
|
|
summary=c,
|
|
|
|
details=dict(
|
|
|
|
username=correlations[c]['username'],
|
|
|
|
macaddress=correlations[c]['macaddress'],
|
|
|
|
entity=correlations[c]['entity']
|
|
|
|
),
|
|
|
|
category='indicators'
|
|
|
|
)
|
|
|
|
try:
|
2016-10-25 00:17:31 +03:00
|
|
|
es.save_object(index='intelligence', doc_id=getDocID(c), doc_type='usernamemacaddress', body=json.dumps(event))
|
2014-12-10 02:19:26 +03:00
|
|
|
except Exception as e:
|
2016-10-18 00:48:02 +03:00
|
|
|
logger.error("Exception %r when posting correlation " % e)
|
2014-12-10 02:19:26 +03:00
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
'''
|
|
|
|
Look for events that contain username and a mac address
|
|
|
|
Add the correlation to the intelligence index.
|
|
|
|
'''
|
|
|
|
logger.debug('starting')
|
|
|
|
logger.debug(options)
|
|
|
|
|
2016-10-25 00:17:31 +03:00
|
|
|
es = ElasticsearchClient((list('{0}'.format(s) for s in options.esservers)))
|
2014-12-10 02:19:26 +03:00
|
|
|
# create intelligence index if it's not already there
|
2018-03-07 02:10:18 +03:00
|
|
|
es.create_index('intelligence')
|
2016-10-18 00:48:02 +03:00
|
|
|
|
2014-12-10 02:19:26 +03:00
|
|
|
# read in the OUI file for mac prefix to vendor dictionary
|
|
|
|
macassignments = readOUIFile(options.ouifilename)
|
2016-10-18 00:48:02 +03:00
|
|
|
|
2014-12-10 02:19:26 +03:00
|
|
|
# search ES for events containing username and mac address
|
|
|
|
correlations = esSearch(es, macassignments=macassignments)
|
2016-10-18 00:48:02 +03:00
|
|
|
|
2014-12-10 02:19:26 +03:00
|
|
|
# store the correlation in the intelligence index
|
|
|
|
esStoreCorrelations(es, correlations)
|
|
|
|
|
|
|
|
logger.debug('finished')
|
|
|
|
|
|
|
|
|
|
|
|
def initConfig():
|
|
|
|
# output our log to stdout or syslog
|
|
|
|
options.output = getConfig('output', 'stdout', options.configfile)
|
|
|
|
# syslog hostname
|
|
|
|
options.sysloghostname = getConfig('sysloghostname',
|
|
|
|
'localhost',
|
|
|
|
options.configfile)
|
|
|
|
# syslog port
|
|
|
|
options.syslogport = getConfig('syslogport', 514, options.configfile)
|
|
|
|
|
|
|
|
# elastic search server settings
|
|
|
|
options.esservers = list(getConfig('esservers',
|
|
|
|
'http://localhost:9200',
|
|
|
|
options.configfile).split(','))
|
2016-10-18 00:48:02 +03:00
|
|
|
|
2014-12-10 02:19:26 +03:00
|
|
|
# default time period in minutes to look back in time for the aggregation
|
|
|
|
options.correlationminutes = getConfig('correlationminutes',
|
|
|
|
150,
|
|
|
|
options.configfile)
|
2016-10-18 00:48:02 +03:00
|
|
|
|
2014-12-10 02:19:26 +03:00
|
|
|
# default location of the OUI file from IEEE for resolving mac prefixes
|
|
|
|
# Expects the OUI file from IEEE:
|
2016-10-18 00:48:02 +03:00
|
|
|
# wget http://www.ieee.org/netstorage/standards/oui.txt
|
2014-12-10 02:19:26 +03:00
|
|
|
options.ouifilename = getConfig('ouifilename',
|
|
|
|
'oui.txt',
|
|
|
|
options.configfile)
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
parser = OptionParser()
|
|
|
|
parser.add_option(
|
|
|
|
"-c",
|
|
|
|
dest='configfile',
|
|
|
|
default=sys.argv[0].replace('.py', '.conf'),
|
|
|
|
help="configuration file to use")
|
|
|
|
(options, args) = parser.parse_args()
|
|
|
|
initConfig()
|
|
|
|
initLogger()
|
|
|
|
main()
|